当前位置: 首页 > news >正文

【FastDDS】Layer Transport ( 04-TCP Transport )

6.3 TCP 传输

TCP 是一种面向连接的传输方式,因此域参与者(DomainParticipant)在发送数据消息之前必须与远程对等方建立 TCP 连接。所以,进行通信的其中一个域参与者(作为服务器)必须打开一个 TCP 端口以监听传入的连接,而另一个(作为客户端)则必须连接到这个端口。

注意
服务器和客户端的概念独立于 DDS 中的发布者(Publisher)、订阅者(Subscriber)、数据写入器(DataWriter)和数据读取器(DataReader)等概念。此外,这些概念也独立于 eProsima 发现服务器(Discovery Server)的服务器和客户端(发现服务器设置)。在建立连接时,它们中的任何一个都可以充当 TCP 服务器或 TCP 客户端,并且 DDS 通信将在该连接上进行。

警告
本文档假设读者具备 TCP/IP 概念的基础知识,因为像生存时间(TTL)、循环冗余校验(CRC)、传输层安全(TLS)、套接字缓冲区和端口编号等术语不会详细解释。不过,即使没有这些知识,也可以在 Fast DDS 上配置基本的 TCP 传输。

6.3.1 TCPTransportDescriptor

eProsima Fast DDS 为 TCPv4 和 TCPv6 都实现了 TCP 传输。这些传输中的每一种都是相互独立的,并且都有自己的 TransportDescriptorInterface。不过,它们共享许多功能,并且 TransportDescriptorInterface 的大多数数据成员是通用的。

下表描述了 TCPv4 和 TCPv6 共有的数据成员。

成员数据类型默认值描述
sendBufferSizeuint32_t0套接字的发送缓冲区大小(字节)。
receiveBufferSizeuint32_t0套接字的接收缓冲区大小(字节)。
netmask_filterNetmaskFilterKindAUTO参见网络掩码过滤。
allowlistvector<pair<string,NetmaskFilterKind>>空向量具有网络掩码过滤配置的允许接口列表。参见允许列表。
blocklistvector<string>空向量被阻塞接口的列表。参见阻止列表。
interfaceWhiteListvector<string>空向量允许的接口列表,参见接口白名单。
TTLuint8_t1生存时间,以跳数为单位。
listening_portsvector<uint16_t>空向量作为服务器监听的端口列表。如果端口设置为 0,将自动分配一个可用端口。
keep_alive_frequency_msuint32_t5000RTCP 保活请求的频率(毫秒)。
keep_alive_timeout_msuint32_t15000发送最后一个保活请求后,判定连接断开的时间(毫秒)。
max_logical_portuint16_t100RTCP 协商期间尝试的最大逻辑端口数。
logical_port_rangeuint16_t20RTCP 协商期间每个请求尝试的最大逻辑端口数。
logical_port_incrementuint16_t2RTCP 协商期间尝试的逻辑端口之间的增量。
enable_tcp_nodelayboolfalse启用 TCP_NODELAY 套接字选项。
non_blocking_sendboolfalse发送操作不阻塞(*)。
calculate_crcbooltrue为消息头计算并发送 CRC 时为 true。
check_crcbooltrue检查传入消息头的 CRC 时为 true。
apply_securityboolfalse使用 TLS 时为 true。参见 TCP 上的 TLS。
tls_configTLSConfigTLS 的配置。参见 TCP 上的 TLS。
default_reception_threadsThreadSettings接收线程的默认 ThreadSettings。
reception_threadsstd::map<uint32_t,ThreadSettings>特定端口上接收线程的 ThreadSettings。
keep_alive_threadThreadSettings保持 TCP 连接活动的线程的 ThreadSettings。
accept_threadThreadSettings处理传入 TCP 连接请求的线程的 ThreadSettings。
tcp_negotiation_timeoutuint32_t0等待逻辑端口协商的时间(毫秒)。如果逻辑端口正在协商,在尝试向该端口发送消息之前,它会等待协商完成,最长等待此超时时间。将此选项设置为非零值会增加发现时间。设置为零意味着不等待,但可能导致第一条消息丢失。

警告
尽管成员 listening_ports 接受多个端口,但实际上只会使用第一个监听端口。其余端口将被忽略。

注意
如果 listening_ports 为空,参与者将无法接收传入的连接,但能够连接到其他已配置监听端口的参与者。

注意
non_blocking_send 设置为 true 时,如果发送缓冲区可能满了,发送操作将立即返回,但不会向上层返回错误。这意味着应用程序会表现得像数据包已发送但丢失了一样。
当设置为 false 时,发送操作将阻塞,直到网络缓冲区有空间容纳数据包。

6.3.1.1 TCPv4TransportDescriptor

下表描述了 TCPv4TransportDescriptor 独有的数据成员。

成员数据类型默认值描述
wan_addroctet[4][0, 0, 0, 0]广域网(WAN)的配置。参见基于 TCPv4 的广域网(WAN)或互联网通信。

注意
TCPv4TransportDescriptorkind 值由 LOCATOR_KIND_TCPv4 给出。

6.3.1.2 TCPv6TransportDescriptor

TCPv6TransportDescriptor 没有除 TCPTransportDescriptor 中描述的通用数据成员之外的其他数据成员。

注意
TCPv6TransportDescriptorkind 值由 LOCATOR_KIND_TCPv6 给出。

6.3.2 启用 TCP 传输

在 eprosima Fast DDS 中启用 TCP 传输有多种方法。根据每个场景的特点,一种方法可能比其他方法更适合。

6.3.2.1 内置传输的配置

第一种选择是修改负责创建域参与者传输的内置传输。启用 TCP 传输的现有配置是 LARGE_DATA。此选项分别实例化 UDPv4、TCPv4 和 SHM 传输。在参与者发现阶段(参见发现阶段),UDP 协议将用于多播通告,而参与者的活跃度和应用程序数据的传递则通过 TCP 或 SHM 进行。此配置支持自动发现,不需要手动设置每个参与者的 IP 和监听端口。因此,避免了典型的服务器-客户端配置。

可以使用 FASTDDS_BUILTIN_TRANSPORTS 环境变量(参见 FASTDDS_BUILTIN_TRANSPORTS)、XML 配置文件(参见 RTPS 元素类型)或通过代码来配置内置传输。

环境变量

export FASTDDS_BUILTIN_TRANSPORTS=LARGE_DATA

XML

<?xml version="1.0" encoding="UTF-8" ?>
<dds xmlns="http://www.eprosima.com"><profiles><!--UDP transport for PDP and SHM/TCPv4 transport for both EDP and application data--><participant profile_name="large_data_builtin_transports" is_default_profile="true"><rtps><builtinTransports>LARGE_DATA</builtinTransports></rtps></participant></profiles>
</dds>

C++

eprosima::fastdds::dds::DomainParticipantQos qos;
qos.setup_transports(eprosima::fastdds::rtps::BuiltinTransports::LARGE_DATA);

注意
请注意,内置传输的 LARGE_DATA 配置还会在 UDP 和 TCP 传输之外创建一个 SHM 传输。只要有可能,就会使用共享内存。如果在 SHM 可行的情况下需要 TCP 通信,则需要手动配置。(参见大数据模式)。

6.3.2.2 服务器-客户端配置

要设置服务器-客户端配置,需要创建一个 TCPv4TransportDescriptor(用于 TCPv4)或 TCPv6TransportDescriptor(用于 TCPv6)的实例,并将其添加到域参与者的用户传输列表中。

根据 TCP 传输描述符设置和定义的网络定位器,域参与者可以充当 TCP 服务器或 TCP 客户端。

TCP 服务器:如果在描述符中提供 listening_ports,域参与者将充当 TCP 服务器,在给定的端口上监听传入的远程连接。下面的示例展示了在 C++ 代码和 XML 文件中的实现过程。

C++

eprosima::fastdds::dds::DomainParticipantQos qos;// 创建新传输的描述符。
auto tcp_transport = std::make_shared<eprosima::fastdds::rtps::TCPv4TransportDescriptor>();
tcp_transport->add_listener_port(5100);// [可选] 线程设置配置
tcp_transport->default_reception_threads(eprosima::fastdds::rtps::ThreadSettings{-1, 0, 0, -1});
tcp_transport->set_thread_config_for_port(12345, eprosima::fastdds::rtps::ThreadSettings{-1, 0, 0, -1});
tcp_transport->keep_alive_thread = eprosima::fastdds::rtps::ThreadSettings{-1, 0, 0, -1};
tcp_transport->accept_thread = eprosima::fastdds::rtps::ThreadSettings{-1, 0, 0, -1};// 将传输层链接到参与者。
qos.transport().user_transports.push_back(tcp_transport);// 避免使用默认传输
qos.transport().use_builtin_transports = false;// [可选] 设置单播定位器
eprosima::fastdds::rtps::Locator_t locator;
locator.kind = LOCATOR_KIND_TCPv4;
eprosima::fastdds::rtps::IPLocator::setIPv4(locator, "192.168.1.10");
eprosima::fastdds::rtps::IPLocator::setPhysicalPort(locator, 5100);
// [可选] 逻辑端口默认值为 0,自动分配。
eprosima::fastdds::rtps::IPLocator::setLogicalPort(locator, 5100);qos.wire_protocol().builtin.metatrafficUnicastLocatorList.push_back(locator);
qos.wire_protocol().default_unicast_locator_list.push_back(locator);

XML

<?xml version="1.0" encoding="UTF-8" ?>
<dds><profiles xmlns="http://www.eprosima.com"><transport_descriptors><transport_descriptor><transport_id>tcp_server_transport</transport_id><type>TCPv4</type><listening_ports><port>5100</port></listening_ports><!-- 可选的线程配置 --><default_reception_threads><scheduling_policy>-1</scheduling_policy><priority>0</priority><affinity>0</affinity><stack_size>-1</stack_size></default_reception_threads><reception_threads><reception_thread port="12345"><scheduling_policy>-1</scheduling_policy><priority>0</priority><affinity>0</affinity><stack_size>-1</stack_size></reception_thread></reception_threads><keep_alive_thread><scheduling_policy>-1</scheduling_policy><priority>0</priority><affinity>0</affinity><stack_size>-1</stack_size></keep_alive_thread><accept_thread><scheduling_policy>-1</scheduling_policy><priority>0</priority><affinity>0</affinity><stack_size>-1</stack_size></accept_thread></transport_descriptor></transport_descriptors><participant profile_name="tcp_server_participant"><rtps><userTransports><transport_id>tcp_server_transport</transport_id></userTransports><useBuiltinTransports>false</useBuiltinTransports><!-- 可选的单播定位器设置 --><defaultUnicastLocatorList><locator><tcpv4><address>192.168.1.10</address><physical_port>5100</physical_port><!-- 可选的逻辑端口设置 --><port>5100</port></tcpv4></locator></defaultUnicastLocatorList><!-- 可选的元数据单播定位器设置 --><builtin><metatrafficUnicastLocatorList><locator><tcpv4><address>192.168.1.10</address><physical_port>5100</physical_port><!-- 可选的逻辑端口设置 --><port>5100</port></tcpv4></locator></metatrafficUnicastLocatorList></builtin></rtps></participant></profiles>
</dds>

TCP 客户端:如果向域参与者提供 initialPeersList,它将充当 TCP 客户端,尝试连接到给定地址和端口上的远程服务器。下面的示例展示了在 C++ 代码和 XML 文件中的实现过程。有关其配置的更多信息,请参见初始对等方。

C++

eprosima::fastdds::dds::DomainParticipantQos qos;// 禁用内置传输层。
qos.transport().use_builtin_transports = false;// 创建新传输的描述符。
// 不配置任何监听端口
auto tcp_transport = std::make_shared<eprosima::fastdds::rtps::TCPv4TransportDescriptor>();
qos.transport().user_transports.push_back(tcp_transport);// [可选] 线程设置配置
tcp_transport->default_reception_threads(eprosima::fastdds::rtps::ThreadSettings{-1, 0, 0, -1});
tcp_transport->set_thread_config_for_port(12345, eprosima::fastdds::rtps::ThreadSettings{-1, 0, 0, -1});
tcp_transport->keep_alive_thread = eprosima::fastdds::rtps::ThreadSettings{-1, 0, 0, -1};
tcp_transport->accept_thread = eprosima::fastdds::rtps::ThreadSettings{-1, 0, 0, -1};// 设置初始对等方。
eprosima::fastdds::rtps::Locator_t initial_peer_locator;
initial_peer_locator.kind = LOCATOR_KIND_TCPv4;
eprosima::fastdds::rtps::IPLocator::setIPv4(initial_peer_locator, "192.168.1.10");
eprosima::fastdds::rtps::IPLocator::setPhysicalPort(initial_peer_locator, 5100);
// 如果在服务器端设置了逻辑端口,则此处也必须设置相同的值。
// 如果在服务器端的单播定位器中未设置,则此处不设置。
eprosima::fastdds::rtps::IPLocator::setLogicalPort(initial_peer_locator, 5100);qos.wire_protocol().builtin.initialPeersList.push_back(initial_peer_locator);

注意
手动设置单播定位器是可选的。如果不设置单播定位器,或者将其逻辑端口设置为 0,则客户端的初始对等方不应设置其逻辑端口(或设置为 0)。否则,初始对等方的逻辑端口必须与服务器的单播逻辑端口匹配。
传输机制示例展示了如何使用和配置 TCP 传输。

6.3.3 基于 TCPv4 的广域网(WAN)或互联网通信

在这里插入图片描述

Fast DDS 在正确配置后,能够通过互联网或其他广域网(WAN)进行连接。要实现此类场景,相关的网络设备(如路由器和防火墙)必须添加允许通信的规则。

例如,假设有如下场景:
一台域参与者充当 TCP 服务器,监听 5100 端口,并通过一个公网 IP 为 80.80.99.45 的路由器连接到广域网。
另一台域参与者充当 TCP 客户端,并在其初始对等方列表中配置了服务器的 IP 地址和端口。

通过使用 set_WAN_address(wan_ip),广域网 IP 地址会被设置到参与者在发现阶段所传递的定位器中。

与局域网情况类似,手动设置单播定位器是可选的。但在这种情况下,设置其 IP 地址时需要考虑一些事项:

  • 在单播定位器中使用 setWAN() 方法设置广域网 IP 地址是无效的,因为它会被 set_WAN_address() 调用覆盖。
  • 为单播定位器分配 IP 地址时,仅使用 setIPv4()setIPv6() 方法,这些是局域网 IP 设置方法。在某些配置中,允许使用这些设置方法配置广域网 IP 地址。

根据服务器是否手动设置了其元数据单播定位器和默认单播定位器,客户端需要相应地调整其初始对等方列表:

  • 如果服务器的单播定位器配置了局域网 IP 地址:
    • 可以使用局域网 IP 设置方法,仅将初始对等方设置为服务器的广域网 IP。
    • 或者,可以使用局域网设置方法配置服务器的局域网 IP,并使用广域网设置方法配置服务器的广域网 IP,从而同时配置这两个 IP 地址。
  • 如果服务器的单播定位器配置了广域网 IP 地址:
    • 必须使用局域网设置方法,仅将初始对等方设置为服务器的广域网 IP。
    • 或者,可以同时使用局域网和广域网设置方法配置广域网 IP 地址。
  • 如果服务器未设置任何单播定位器:
    • 可以使用局域网设置方法,仅将初始对等方配置为服务器的广域网 IP。
    • 或者,可以使用局域网设置方法配置服务器的局域网 IP,并使用广域网设置方法配置服务器的广域网 IP,从而同时配置这两个 IP 地址。

注意
手动设置单播定位器是可选的。如果不设置单播定位器,或者将其逻辑端口设置为 0,则客户端的初始对等方不应设置其逻辑端口(或设置为 0)。否则,初始对等方的逻辑端口必须与服务器的单播逻辑端口匹配。

在服务器端,必须将路由器配置为将所有传入到 5100 端口的流量转发到 TCP 服务器。通常,将 5100 端口的 NAT 路由指向我们的机器就足够了。还应配置任何现有的防火墙。

此外,为了允许通过广域网进行传入连接,TCPv4TransportDescriptor 必须在 wan_addr 数据成员中指定其公网 IP 地址。以下示例展示了如何在 C++ 和 XML 中配置域参与者。

C++

eprosima::fastdds::dds::DomainParticipantQos qos;// 创建新传输的描述符。
auto tcp_transport = std::make_shared<eprosima::fastdds::rtps::TCPv4TransportDescriptor>();
tcp_transport->add_listener_port(5100);
tcp_transport->set_WAN_address("80.80.99.45");// [可选] 线程设置配置
tcp_transport->default_reception_threads(eprosima::fastdds::rtps::ThreadSettings{-1, 0, 0, -1});
tcp_transport->set_thread_config_for_port(12345, eprosima::fastdds::rtps::ThreadSettings{-1, 0, 0, -1});
tcp_transport->keep_alive_thread = eprosima::fastdds::rtps::ThreadSettings{-1, 0, 0, -1};
tcp_transport->accept_thread = eprosima::fastdds::rtps::ThreadSettings{-1, 0, 0, -1};// 将传输层链接到参与者。
qos.transport().user_transports.push_back(tcp_transport);// 避免使用默认传输
qos.transport().use_builtin_transports = false;// [可选] 设置单播定位器(不要使用 setWAN(),set_WAN_address() 会覆盖它)
eprosima::fastdds::rtps::Locator_t locator;
locator.kind = LOCATOR_KIND_TCPv4;
// [推荐] 使用服务器的局域网地址
eprosima::fastdds::rtps::IPLocator::setIPv4(locator, "192.168.1.10");
// [替代方案] 使用服务器的广域网地址。在这种情况下,初始对等方必须仅配置为服务器的广域网地址。
// eprosima::fastdds::rtps::IPLocator::setIPv4(locator, "80.80.99.45");
eprosima::fastdds::rtps::IPLocator::setPhysicalPort(locator, 5100);
// [可选] 逻辑端口默认值为 0,自动分配。
eprosima::fastdds::rtps::IPLocator::setLogicalPort(locator, 5100);
qos.wire_protocol().builtin.metatrafficUnicastLocatorList.push_back(locator);
qos.wire_protocol().default_unicast_locator_list.push_back(locator);

XML

<?xml version="1.0" encoding="UTF-8" ?>
<dds><profiles xmlns="http://www.eprosima.com/XMLSchemas/fastRTPS_Profiles"><transport_descriptors><transport_descriptor><transport_id>tcp_server_wan_transport</transport_id><type>TCPv4</type><listening_ports><port>5100</port></listening_ports><wan_addr>80.80.99.45</wan_addr><!-- 可选的线程配置 --><default_reception_threads><scheduling_policy>-1</scheduling_policy><priority>0</priority><affinity>0</affinity><stack_size>-1</stack_size></default_reception_threads><reception_threads><reception_thread port="12345"><scheduling_policy>-1</scheduling_policy><priority>0</priority><affinity>0</affinity><stack_size>-1</stack_size></reception_thread></reception_threads><keep_alive_thread><scheduling_policy>-1</scheduling_policy><priority>0</priority><affinity>0</affinity><stack_size>-1</stack_size></keep_alive_thread><accept_thread><scheduling_policy>-1</scheduling_policy><priority>0</priority><affinity>0</affinity><stack_size>-1</stack_size></accept_thread></transport_descriptor></transport_descriptors><participant profile_name="tcp_server_wan_participant"><rtps><userTransports><transport_id>tcp_server_wan_transport</transport_id></userTransports><useBuiltinTransports>false</useBuiltinTransports><!-- 可选的单播定位器设置 --><defaultUnicastLocatorList><locator><tcpv4><address>192.168.1.10</address><!-- 或者使用广域网地址 --><!-- <address>80.80.99.45</address> --><physical_port>5100</physical_port><!-- 可选的逻辑端口设置 --><port>5100</port></tcpv4></locator></defaultUnicastLocatorList><!-- 可选的元数据单播定位器设置 --><builtin><metatrafficUnicastLocatorList><locator><tcpv4><address>192.168.1.10</address><!-- 或者使用广域网地址 --><!-- <address>80.80.99.45</address> --><physical_port>5100</physical_port><!-- 可选的逻辑端口设置 --><port>5100</port></tcpv4></locator></metatrafficUnicastLocatorList></builtin></rtps></participant></profiles>
</dds>

在客户端,必须将域参与者配置为使用 TCP 服务器的公网 IP 地址和 listening_ports 作为初始对等方。

C++

eprosima::fastdds::dds::DomainParticipantQos qos;// 禁用内置传输层。
qos.transport().use_builtin_transports = false;// 创建新传输的描述符。
// 不配置任何监听端口
auto tcp_transport = std::make_shared<eprosima::fastdds::rtps::TCPv4TransportDescriptor>();
// [推荐] 如果在其他本地网络中有更多客户端,请使用客户端的广域网地址。
tcp_transport->set_WAN_address("80.80.99.47");
qos.transport().user_transports.push_back(tcp_transport);// [可选] 线程设置配置
tcp_transport->default_reception_threads(eprosima::fastdds::rtps::ThreadSettings{-1, 0, 0, -1});
tcp_transport->set_thread_config_for_port(12345, eprosima::fastdds::rtps::ThreadSettings{-1, 0, 0, -1});
tcp_transport->keep_alive_thread = eprosima::fastdds::rtps::ThreadSettings{-1, 0, 0, -1};
tcp_transport->accept_thread = eprosima::fastdds::rtps::ThreadSettings{-1, 0, 0, -1};// 设置初始对等方。
eprosima::fastdds::rtps::Locator_t initial_peer_locator;
initial_peer_locator.kind = LOCATOR_KIND_TCPv4;
// [推荐] 同时使用广域网和局域网服务器地址
eprosima::fastdds::rtps::IPLocator::setIPv4(initial_peer_locator, "192.168.1.10");
eprosima::fastdds::rtps::IPLocator::setWan(initial_peer_locator, "80.80.99.45");
// [替代方案] 仅使用服务器的广域网地址。如果服务器使用其局域网或广域网地址指定了其单播定位器,则有效。
// eprosima::fastdds::rtps::IPLocator::setIPv4(initial_peer_locator, "80.80.99.45");
eprosima::fastdds::rtps::IPLocator::setPhysicalPort(initial_peer_locator, 5100);
// 如果在服务器端设置了逻辑端口,则此处也必须设置相同的值。
// 如果在服务器端的单播定位器中未设置,则此处不设置。
eprosima::fastdds::rtps::IPLocator::setLogicalPort(initial_peer_locator, 5100);
qos.wire_protocol().builtin.initialPeersList.push_back(initial_peer_locator);

XML

<?xml version="1.0" encoding="UTF-8" ?>
<dds><profiles xmlns="http://www.eprosima.com/XMLSchemas/fastRTPS_Profiles"><transport_descriptors><transport_descriptor><transport_id>tcp_client_wan_transport</transport_id><type>TCPv4</type><!-- 推荐设置客户端的广域网地址 --><wan_addr>80.80.99.47</wan_addr><!-- 可选的线程配置 --><default_reception_threads><scheduling_policy>-1</scheduling_policy><priority>0</priority><affinity>0</affinity><stack_size>-1</stack_size></default_reception_threads><reception_threads><reception_thread port="12345"><scheduling_policy>-1</scheduling_policy><priority>0</priority><affinity>0</affinity><stack_size>-1</stack_size></reception_thread></reception_threads><keep_alive_thread><scheduling_policy>-1</scheduling_policy><priority>0</priority><affinity>0</affinity><stack_size>-1</stack_size></keep_alive_thread><accept_thread><scheduling_policy>-1</scheduling_policy><priority>0</priority><affinity>0</affinity><stack_size>-1</stack_size></accept_thread></transport_descriptor></transport_descriptors><participant profile_name="tcp_client_wan_participant"><rtps><userTransports><transport_id>tcp_client_wan_transport</transport_id></userTransports><useBuiltinTransports>false</useBuiltinTransports><builtin><initialPeersList><locator><tcpv4><!-- 推荐同时使用广域网和局域网服务器地址 --><wan_address>80.80.99.45</wan_address><address>192.168.1.10</address><!-- 或者仅使用服务器的广域网地址 --><!-- <address>80.80.99.45</address> --><physical_port>5100</physical_port><!-- 若服务器端已设置,则此处需设置 --><port>5100</port></tcpv4></locator></initialPeersList></builtin></rtps></participant></profiles>
</dds>

6.3.4 传输机制示例

在 delivery_mechanisms 文件夹中可以找到一个适用于受支持传输机制的“hello world”示例。它展示了通过所需传输机制(可以仅设置为 TCP)进行通信的发布者和订阅者。

http://www.xdnf.cn/news/1480069.html

相关文章:

  • 数据库中间件ShardingSphere v5.2.1
  • (算法 哈希表)【LeetCode 242】有效的字母异位词
  • 关于 React 19 的四种组件通信方法
  • 十三、计算机领域英语
  • TDengine 时间函数 WEEKOFYEAR() 用户手册
  • 【C++框架#3】Etcd 安装使用
  • Blender 3D建模工具学习笔记
  • LeetCode15:三数之和
  • 《MATLAB 批量把振动 CSV(含中文“序号/采样频率”)稳健转成 .mat:自动解析+统一换算+按 H/I/O/F-rpm-fs-load 命名》
  • WIN10+ubuntu22.04.05双系统装机教程
  • 基于STM32F103C8T6的心率与体温监测及报警显示系统设计
  • 如何在 FastAPI 中巧妙覆盖依赖注入并拦截第三方服务调用?
  • vue + ant-design-vue + vuedraggable 实现可视化表单设计器
  • 用 PHP 玩向量数据库:一个从小说网站开始的小尝试
  • 多维度数据统一线性处理的常见方案
  • 鸿蒙libxm2交叉编译
  • (2)桌面云、并行计算、分布式、网格计算
  • LeetCode5最长回文子串
  • 基于Spark的中文文本情感分析系统研究
  • 空间配置器
  • 【STM32HAL-----NRF24L01】
  • leetcode LCR 159 库存管理III
  • Qt网络通信服务端与客户端学习
  • 第5章递归:分治法
  • Qt文字滚动效果学习
  • MySQL 高可用方案之 MHA 架构搭建与实践
  • 常用配置文件
  • 去中心化投票系统开发教程 第三章:智能合约设计与开发
  • [网络入侵AI检测] docs | 任务二分类与多分类
  • 算法题-链表03