6 Fast DDS-传输层
- [6.1 传输 API](#6.1 传输 API)
-
- [6.1.1 TransportDescriptorInterface](#6.1.1 TransportDescriptorInterface)
-
- [6.1.1.1 数据成员](#6.1.1.1 数据成员)
- [6.1.2 TransportInterface](#6.1.2 TransportInterface)
-
- [6.1.2.1 数据成员](#6.1.2.1 数据成员)
- [6.1.3 Locator](#6.1.3 Locator)
-
- [6.1.3.1 数据成员](#6.1.3.1 数据成员)
- [6.1.3.2 使用 IPLocator 配置 IP 定位器](#6.1.3.2 使用 IPLocator 配置 IP 定位器)
- [6.1.4 传输的串联](#6.1.4 传输的串联)
-
- [6.1.4.1 ChainingTransportDescriptor](#6.1.4.1 ChainingTransportDescriptor)
- [6.1.4.2 ChainingTransport](#6.1.4.2 ChainingTransport)
- [6.2 UDP 传输](#6.2 UDP 传输)
-
- [6.2.1 UDPTransportDescriptor](#6.2.1 UDPTransportDescriptor)
-
- [6.2.1.1 UDPv4TransportDescriptor](#6.2.1.1 UDPv4TransportDescriptor)
- [6.2.1.2 UDPv6TransportDescriptor](#6.2.1.2 UDPv6TransportDescriptor)
- [6.2.2 启用 UDP 传输](#6.2.2 启用 UDP 传输)
- [6.3 TCP 传输](#6.3 TCP 传输)
-
- [6.3.1 TCPTransportDescriptor](#6.3.1 TCPTransportDescriptor)
-
- [6.3.1.1 TCPv4TransportDescriptor](#6.3.1.1 TCPv4TransportDescriptor)
- [6.3.1.2 TCPv6TransportDescriptor](#6.3.1.2 TCPv6TransportDescriptor)
- [6.3.2 启用 TCP 传输](#6.3.2 启用 TCP 传输)
-
- [6.3.2.1 内置传输的配置](#6.3.2.1 内置传输的配置)
- [6.3.2.2 服务器-客户端配置](#6.3.2.2 服务器-客户端配置)
- [6.3.3 通过 TCPv4 进行 WAN 或互联网通信](#6.3.3 通过 TCPv4 进行 WAN 或互联网通信)
- [6.3.4 交付机制示例](#6.3.4 交付机制示例)
- [6.4 共享内存传输](#6.4 共享内存传输)
-
- [6.4.1 概念定义](#6.4.1 概念定义)
-
- [6.4.1.1 段 (Segment)](#6.4.1.1 段 (Segment))
- [6.4.1.2 Segment Buffer](#6.4.1.2 Segment Buffer)
- [6.4.1.3 缓冲区描述符 (Buffer Descriptor)](#6.4.1.3 缓冲区描述符 (Buffer Descriptor))
- [6.4.1.4 端口 (Port)](#6.4.1.4 端口 (Port))
- [6.4.1.5 端口健康检查 (Port Health Check)](#6.4.1.5 端口健康检查 (Port Health Check))
- [6.4.2 SharedMemTransportDescriptor](#6.4.2 SharedMemTransportDescriptor)
- [6.4.3 启用共享内存传输](#6.4.3 启用共享内存传输)
- [6.4.4 交付机制示例](#6.4.4 交付机制示例)
- [6.5 数据共享交付](#6.5 数据共享交付)
-
- [6.5.1 概述](#6.5.1 概述)
- [6.5.2 约束条件](#6.5.2 约束条件)
- [6.5.3 数据共享交付配置](#6.5.3 数据共享交付配置)
-
- [6.5.3.1 数据共享交付类型](#6.5.3.1 数据共享交付类型)
- [6.5.3.2 数据共享域标识符](#6.5.3.2 数据共享域标识符)
- [6.5.3.3 数据共享域标识符的最大数量](#6.5.3.3 数据共享域标识符的最大数量)
- [6.5.3.4 共享内存目录](#6.5.3.4 共享内存目录)
- [6.5.4 DataReader 和 DataWriter 历史记录耦合](#6.5.4 DataReader 和 DataWriter 历史记录耦合)
-
- [6.5.4.1 数据确认机制](#6.5.4.1 数据确认机制)
- [6.5.4.2 阻塞样本重用直至确认完成](#6.5.4.2 阻塞样本重用直至确认完成)
- [6.6 进程内交付](#6.6 进程内交付)
-
- [6.6.1 进程内交付的 GUID 前缀注意事项](#6.6.1 进程内交付的 GUID 前缀注意事项)
- [6.7 TCP 上的 TLS](#6.7 TCP 上的 TLS)
-
- [6.7.1 TLS 验证模式](#6.7.1 TLS 验证模式)
- [6.7.2 TLS 选项](#6.7.2 TLS 选项)
- [6.7.3 TLS 握手角色](#6.7.3 TLS 握手角色)
- [6.8 Low Bandwidth Transports(Pro)](#6.8 Low Bandwidth Transports(Pro))
-
- [6.8.1 有效负载压缩传输](#6.8.1 有效负载压缩传输)
-
- [6.8.1.1 PayloadCompressionTransportDescriptor](#6.8.1.1 PayloadCompressionTransportDescriptor)
- [6.8.1.2 启用有效负载压缩传输](#6.8.1.2 启用有效负载压缩传输)
- [6.8.2 头部缩减传输](#6.8.2 头部缩减传输)
-
- [6.8.2.1 HeaderReductionTransportDescriptor](#6.8.2.1 HeaderReductionTransportDescriptor)
- [6.8.2.2 启用头部缩减传输](#6.8.2.2 启用头部缩减传输)
- [6.8.3 完整示例](#6.8.3 完整示例)
- [6.9 监听定位器](#6.9 监听定位器)
-
- [6.9.1 添加监听定位器](#6.9.1 添加监听定位器)
-
- [6.9.1.1 元流量组播定位器](#6.9.1.1 元流量组播定位器)
- [6.9.1.2 元流量单播定位器](#6.9.1.2 元流量单播定位器)
- [6.9.1.3 用户流量组播定位器](#6.9.1.3 用户流量组播定位器)
- [6.9.1.4 用户流量单播定位器](#6.9.1.4 用户流量单播定位器)
- [6.9.2 默认监听定位器](#6.9.2 默认监听定位器)
- [6.9.3 公认端口](#6.9.3 公认端口)
- [6.10 通告的定位器](#6.10 通告的定位器)
-
- [6.10.1 默认通告的定位器](#6.10.1 默认通告的定位器)
- [6.10.2 外部定位器](#6.10.2 外部定位器)
-
- [6.10.2.1 外部性层级](#6.10.2.1 外部性层级)
- [6.10.2.2 匹配算法](#6.10.2.2 匹配算法)
- [6.10.2.3 其他注意事项](#6.10.2.3 其他注意事项)
- [6.11 接口白名单](#6.11 接口白名单)
- [6.12 接口配置](#6.12 接口配置)
-
- [6.12.1 网络掩码过滤](#6.12.1 网络掩码过滤)
- [6.12.2 允许列表 (Allowlist)](#6.12.2 允许列表 (Allowlist))
- [6.12.3 阻止列表 (Blocklist)](#6.12.3 阻止列表 (Blocklist))
- [6.13 禁用所有组播流量](#6.13 禁用所有组播流量)
传输层提供 DDS 实体之间的通信服务,负责在实际的物理传输上发送和接收消息。DDS 层将此服务用于用户数据和发现流量通信。然而,DDS 层本身是与传输无关的,它定义了一个传输 API,并且可以在实现此 API 的任何传输插件上运行。通过这种方式,它不局限于特定的传输,应用程序可以选择最适合其需求的传输,或者创建自己的传输。
eProsima Fast DDS 附带了五种已实现的传输方式:
- UDPv4:基于 IPv4 的 UDP 数据报通信。如果没有给出特定的传输配置,此传输会在新的 DomainParticipant 上默认创建(参见 UDP 传输)。
- UDPv6:基于 IPv6 的 UDP 数据报通信(参见 UDP 传输)。
- TCPv4:基于 IPv4 的 TCP 通信(参见 TCP 传输)。
- TCPv6:基于 IPv6 的 TCP 通信(参见 TCP 传输)。
- SHM:在同一主机上运行的实体之间的共享内存通信。如果没有给出特定的传输配置,此传输会在新的 DomainParticipant 上默认创建(参见共享内存传输)。
虽然它不属于传输模块,但在某些设置下,进程内数据交付和数据共享交付也可用于实体之间的消息发送。下图展示了 Fast DDS 中可用不同传输方式之间的比较。

6.1 传输 API
下图展示了 eProsima Fast DDS 传输 API 中定义的类。它显示了抽象的 API 接口,以及实现一个传输所需的类。

传输 API 示意图
6.1.1 TransportDescriptorInterface
任何实现了 TransportDescriptorInterface 的类都被称为 TransportDescriptor 。它充当特定传输的构建器,意味着它允许配置传输,然后可以使用其 create_transport 工厂成员函数根据此配置构建一个新的传输。
6.1.1.1 数据成员
TransportDescriptorInterface 定义了以下数据成员:
| 成员 | 数据类型 | 描述 |
|---|---|---|
maxMessageSize |
uint32_t |
传输中单个消息的最大大小。 |
maxInitialPeersRange |
uint32_t |
与每个初始远程对等点打开的通道数量。 |
任何 TransportDescriptorInterface 的实现都应添加所需数量的数据成员,以完整配置它所描述的传输。
6.1.2 TransportInterface
Transport 是实现 TransportInterface 的任何类。它是在实际物理传输上执行消息分发的对象。
每个 Transport 类定义了其自身的 kind,这是一个唯一标识符,用于检查定位器(Locator)与传输的兼容性,即确定一个定位器是否指向某个传输。
应用程序不直接创建 Transport 实例。相反,应用程序使用 TransportDescriptor 实例来配置所需的传输,并将此配置好的实例添加到 DomainParticipant 的用户自定义传输列表中。DomainParticipant 会在需要时使用 TransportDescriptor 上的工厂函数来创建 Transport。
DomainParticipantQos qos;
// Create a descriptor for the new transport.
auto udp_transport = std::make_shared<UDPv4TransportDescriptor>();
udp_transport->sendBufferSize = 9216;
udp_transport->receiveBufferSize = 9216;
udp_transport->non_blocking_send = true;
// [OPTIONAL] ThreadSettings configuration
udp_transport->default_reception_threads(eprosima::fastdds::rtps::ThreadSettings{2, 2, 2, 2});
udp_transport->set_thread_config_for_port(12345, eprosima::fastdds::rtps::ThreadSettings{3, 3, 3, 3});
// Link the Transport Layer to the Participant.
qos.transport().user_transports.push_back(udp_transport);
// Avoid using the default transport
qos.transport().use_builtin_transports = false;
6.1.2.1 数据成员
TransportInterface 定义了以下数据成员:
| 成员 | 数据类型 | 描述 |
|---|---|---|
transport_kind_ |
int32_t |
传输类型的唯一标识符。 |
!NOTE
transport_kind_是一个供内部使用的受保护数据成员,无法从公共 API 访问或修改。但是,实现自定义 Transport 的用户需要使用唯一常量值填充它。
目前,Fast DDS 中使用以下标识符:
| 标识符 | 值 | 传输类型 |
|---|---|---|
LOCATOR_KIND_RESERVED |
0 | 无。内部使用的保留值。 |
LOCATOR_KIND_UDPv4 |
1 | 基于 IPv4 的 UDP 传输。 |
LOCATOR_KIND_UDPv6 |
2 | 基于 IPv6 的 UDP 传输。 |
LOCATOR_KIND_TCPv4 |
4 | 基于 IPv4 的 TCP 传输。 |
LOCATOR_KIND_TCPv6 |
8 | 基于 IPv6 的 TCP 传输。 |
LOCATOR_KIND_SHM |
16 | 共享内存传输。 |
6.1.3 Locator
Locator_t 唯一标识了与远程对等点之间针对特定传输的通信通道。例如,在 UDP 传输中,Locator 将包含远程对等点的 IP 地址和端口信息。
Locator 类不是抽象的,也没有为每种传输类型实现特化。相反,传输应将其数据成员映射到它们自己的通道标识概念上。例如,在共享内存传输中,address 包含本地主机的唯一 ID,而 port 表示用于通信缓冲区描述符的共享环形缓冲区。
有关如何配置 DomainParticipant 以监听传入流量的更多信息,请参阅《监听定位器》。
6.1.3.1 数据成员
Locator 定义了以下数据成员:
| 成员 | 数据类型 | 描述 |
|---|---|---|
kind |
int32_t |
传输类型的唯一标识符。 |
port |
uint32_t |
通道端口。 |
address |
octet[16] |
通道地址。 |
Locator IPv4 address
+--------+-----------------------------+-----------------------------+
| Unused | WAN address (62.128.41.210) | LAN address (192.168.0.113) |
+--------+-----------------------------+-----------------------------+
8 bytes (TCP only) 4 bytes 4 bytes
Locator IPv6 address
+--------------------------------------------------------------------+
| Address (2001:0000:130F:0000:0000:09C0:876A:130B) |
+--------------------------------------------------------------------+
16 bytes
检查 TCP IPv4 传输描述符 API 部分中如何操作 WAN 地址。
6.1.3.2 使用 IPLocator 配置 IP 定位器
IPLocator 是一个辅助性的静态类,提供了操作基于 IP 的定位器的方法。在设置新的 UDP 传输或 TCP 传输时,它非常方便,因为它简化了设置 IPv4 和 IPv6 地址或操作端口的过程。
例如,通常用户只需配置物理端口,而无需关心逻辑端口。但是,如果需要,IPLocator 也允许管理它们。
// We will configure a TCP locator with IPLocator
Locator_t locator;
// Get & set the physical port
uint16_t physical_port = IPLocator::getPhysicalPort(locator);
IPLocator::setPhysicalPort(locator, 5555);
// On TCP locators, we can get & set the logical port
uint16_t logical_port = IPLocator::getLogicalPort(locator);
IPLocator::setLogicalPort(locator, 7400);
// Set WAN address
IPLocator::setWan(locator, "80.88.75.55");
Fast DDS 也允许使用名称来指定定位器地址。当地址由名称指定时,Fast DDS 将查询已知主机和可用的 DNS 服务器,以尝试解析出 IP 地址。这个地址随后将用于创建服务器的监听定位器,或在客户端(以及连接到其他服务器的服务器)的情况下,用作远程服务器的地址。
Locator_t locator;
auto response = eprosima::fastdds::rtps::IPLocator::resolveNameDNS("localhost");
// Get the first returned IPv4
if (response.first.size() > 0)
{
IPLocator::setIPv4(locator, response.first.begin()->data());
locator.port = 11811;
}
// Use the locator to create server or client
!WARNING
目前,XML 仅支持通过名称为 UDP 传输加载 IP 地址。
6.1.4 传输的串联
在某些应用场景中,用户需要在传出信息发送到网络之前对其进行预处理,并对接收到的传入信息进行后处理。传输 API 提供了两个接口来实现此类功能:ChainingTransportDescriptor 和 ChainingTransport。

这些扩展允许实现一个依赖于另一个传输(此处称为 low_level_transport_)的新传输。用户可以重写 send() 函数,在调用关联的 low_level_transport_ 之前对输出缓冲区进行预处理。同时,当输入缓冲区到达 low_level_transport_ 时,该传输会调用重写的 receive() 函数,以允许对缓冲区进行预处理。
6.1.4.1 ChainingTransportDescriptor
实现 ChainingTransportDescriptor 允许配置新的 Transport 并设置其所依赖的 low_level_transport_。关联的 low_level_transport_ 可以是任何继承自 TransportInterface 的传输(包括另一个 ChainingTransport)。
ChainingTransportDescriptor 定义了以下数据成员:
| 成员 | 数据类型 | 描述 |
|---|---|---|
low_level_descriptor |
std::shared_ptr<TransportDescriptorInterface> |
底层传输(low_level_transport_)的传输描述符。 |
用户必须在其新的自定义传输定义中指定底层传输 low_level_transport_。
DomainParticipantQos qos;
auto udp_transport = std::make_shared<UDPv4TransportDescriptor>();
// Create a descriptor for the new transport.
// The low level transport will be a UDPv4Transport.
auto custom_transport = std::make_shared<CustomChainingTransportDescriptor>(udp_transport);
// Link the Transport Layer to the Participant.
qos.transport().user_transports.push_back(custom_transport);
// Avoid using the default transport
qos.transport().use_builtin_transports = false;
6.1.4.2 ChainingTransport
该接口强制用户实现 send() 和 receive() 函数。其思路是先对缓冲区进行预处理,然后调用下一级。
class CustomChainingTransport : public eprosima::fastdds::rtps::ChainingTransport
{
public:
CustomChainingTransport(
const CustomChainingTransportDescriptor& descriptor)
: ChainingTransport(descriptor)
, descriptor_(descriptor)
{
}
eprosima::fastdds::rtps::TransportDescriptorInterface* get_configuration()
{
return &descriptor_;
}
bool send(
eprosima::fastdds::rtps::SenderResource* low_sender_resource,
const std::vector<eprosima::fastdds::rtps::NetworkBuffer>& buffers,
uint32_t total_bytes,
eprosima::fastdds::rtps::LocatorsIterator* destination_locators_begin,
eprosima::fastdds::rtps::LocatorsIterator* destination_locators_end,
const std::chrono::steady_clock::time_point& timeout) override
{
//
// Preprocess outcoming buffer.
//
// Call low level transport
return low_sender_resource->send(buffers, total_bytes, destination_locators_begin,
destination_locators_end, timeout, 0);
}
bool send_w_priority(
eprosima::fastdds::rtps::SenderResource* low_sender_resource,
const std::vector<eprosima::fastdds::rtps::NetworkBuffer>& buffers,
uint32_t total_bytes,
eprosima::fastdds::rtps::LocatorsIterator* destination_locators_begin,
eprosima::fastdds::rtps::LocatorsIterator* destination_locators_end,
const std::chrono::steady_clock::time_point& timeout,
int32_t transport_priority) override
{
//
// Preprocess outcoming buffer.
//
// Call low level transport
return low_sender_resource->send(buffers, total_bytes, destination_locators_begin,
destination_locators_end, timeout, transport_priority);
}
void receive(
eprosima::fastdds::rtps::TransportReceiverInterface* next_receiver,
const eprosima::fastdds::rtps::octet* receive_buffer,
uint32_t receive_buffer_size,
const eprosima::fastdds::rtps::Locator_t& local_locator,
const eprosima::fastdds::rtps::Locator_t& remote_locator) override
{
//
// Preprocess incoming buffer.
//
// Call upper level
next_receiver->OnDataReceived(receive_buffer, receive_buffer_size, local_locator, remote_locator);
}
private:
CustomChainingTransportDescriptor descriptor_;
};
6.2 UDP 传输
UDP 是一种无连接的传输协议,其中接收方的 DomainParticipant 必须打开一个 UDP 端口来监听传入的消息,而发送方的 DomainParticipant 则向该端口发送消息。
!WARNING
本文档假定读者具备 UDP/IP 概念的基础知识,因此不会详细解释生存时间(TTL)、套接字缓冲区和端口编号等术语。但是,即使不具备这些知识,也可以在 Fast DDS 上配置基本的 UDP 传输。
6.2.1 UDPTransportDescriptor
eProsima Fast DDS 为 UDPv4 和 UDPv6 实现了 UDP 传输。这两种传输相互独立,并各自拥有独立的 TransportDescriptorInterface。然而,它们的所有 TransportDescriptorInterface 数据成员都是通用的。
下表描述了 UDPv4 和 UDPv6 通用的数据成员。
| 成员 | 数据类型 | 默认值 | 描述 |
|---|---|---|---|
sendBufferSize |
uint32_t |
0 |
套接字发送缓冲区的大小(八位字节)。 |
receiveBufferSize |
uint32_t |
0 |
套接字接收缓冲区的大小(八位字节)。 |
netmask_filter |
NetmaskFilterKind |
AUTO |
参见《网络掩码过滤》。 |
allowlist |
vector<pair<string, NetmaskFilterKind>> |
空向量 | 允许的接口列表及网络掩码过滤器配置。参见《允许列表》。 |
blocklist |
vector<string> |
空向量 | 被阻止的接口列表。参见《阻止列表》。 |
interfaceWhiteList |
vector<string> |
空向量 | 允许的接口列表。参见《接口白名单》。 |
TTL |
uint8_t |
1 |
生存时间,以跳数为单位。 |
m_output_udp_socket |
uint16_t |
0 |
用于传出消息的端口号。 |
non_blocking_send |
bool |
false |
发送操作是否非阻塞(*)。 |
default_reception_threads |
ThreadSettings |
接收线程的默认 ThreadSettings。 |
|
reception_threads |
std::map<uint32_t, ThreadSettings> |
特定端口上接收线程的 ThreadSettings。 |
Note
当
non_blocking_send设置为true时,如果缓冲区已满,发送操作将立即返回,但不会向上层返回错误。这意味着应用程序的行为就像数据报已发送但丢失了一样。此值对于高频的尽力而为写入器特别有用。当设置为
false时,发送操作将阻塞,直到网络缓冲区有空间容纳数据报。这可能会影响高频写入器的性能。
6.2.1.1 UDPv4TransportDescriptor
UDPv4TransportDescriptor 除了 UDPTransportDescriptor 中描述的通用数据成员之外,没有其他额外的数据成员。
Note
UDPv4TransportDescriptor的kind值由LOCATOR_KIND_UDPv4的值给出。
6.2.1.2 UDPv6TransportDescriptor
UDPv6TransportDescriptor 除了 UDPTransportDescriptor 中描述的通用数据成员之外,没有其他额外的数据成员。
Note
UDPv6TransportDescriptor的kind值由LOCATOR_KIND_UDPv6的值给出。
6.2.2 启用 UDP 传输
Fast DDS 默认启用 UDPv4 传输。然而,如果需要,应用程序可以启用其他 UDP 传输。要在 DomainParticipant 中启用新的 UDP 传输,首先创建 UDPv4TransportDescriptor(用于 UDPv4)或 UDPv6TransportDescriptor(用于 UDPv6)的实例,并将其添加到 DomainParticipant 的用户传输列表中。
以下示例在 C++ 代码和 XML 文件中展示了此过程。
DomainParticipantQos qos;
// Create a descriptor for the new transport.
auto udp_transport = std::make_shared<UDPv4TransportDescriptor>();
udp_transport->sendBufferSize = 9216;
udp_transport->receiveBufferSize = 9216;
udp_transport->non_blocking_send = true;
// [OPTIONAL] ThreadSettings configuration
udp_transport->default_reception_threads(eprosima::fastdds::rtps::ThreadSettings{2, 2, 2, 2});
udp_transport->set_thread_config_for_port(12345, eprosima::fastdds::rtps::ThreadSettings{3, 3, 3, 3});
// Link the Transport Layer to the Participant.
qos.transport().user_transports.push_back(udp_transport);
// Avoid using the default transport
qos.transport().use_builtin_transports = false;
6.3 TCP 传输
TCP 是一种面向连接的传输协议,因此 DomainParticipant 必须在发送数据消息之前与远程对等点建立 TCP 连接。因此,通信双方中的一个 DomainParticipant(充当服务器)必须打开一个 TCP 端口来监听传入的连接,而另一方(充当客户端)必须连接到这个端口。
Note
服务器和客户端的概念独立于 Publisher、Subscriber、DataWriter 和 DataReader 这些 DDS 概念。同时,这些概念也独立于 eProsima 发现服务器的服务器和客户端角色(参见《发现服务器设置》)。在建立连接时,任意一方都可以充当 TCP 服务器或 TCP 客户端,并且 DDS 通信将在此连接上正常运行。
Warning
本文档假定读者具备 TCP/IP 概念的基础知识,因此不会详细解释生存时间(TTL)、循环冗余校验(CRC)、传输层安全性(TLS)、套接字缓冲区和端口编号等术语。但是,即使不具备这些知识,也可以在 Fast DDS 上配置基本的 TCP 传输。
6.3.1 TCPTransportDescriptor
eProsima Fast DDS 为 TCPv4 和 TCPv6 实现了 TCP 传输。这两种传输相互独立,并各自拥有独立的 TransportDescriptorInterface。然而,它们共享许多特性,并且 TransportDescriptorInterface 的大部分数据成员都是通用的。
下表描述了 TCPv4 和 TCPv6 通用的数据成员。
| 成员 | 数据类型 | 默认值 | 描述 |
|---|---|---|---|
sendBufferSize |
uint32_t |
0 |
套接字发送缓冲区的大小(八位字节)。 |
receiveBufferSize |
uint32_t |
0 |
套接字接收缓冲区的大小(八位字节)。 |
netmask_filter |
NetmaskFilterKind |
AUTO |
参见《网络掩码过滤》。 |
allowlist |
vector<pair<string, NetmaskFilterKind>> |
空向量 | 允许的接口列表及网络掩码过滤器配置。参见《允许列表》。 |
blocklist |
vector<string> |
空向量 | 被阻止的接口列表。参见《阻止列表》。 |
interfaceWhiteList |
vector<string> |
空向量 | 允许的接口列表。参见《接口白名单》。 |
TTL |
uint8_t |
1 |
生存时间,以跳数为单位。 |
listening_ports |
vector<uint16_t> |
空向量 | 作为服务器监听的端口列表。如果端口设置为 0,将自动分配一个可用端口。 |
keep_alive_frequency_ms |
uint32_t |
0 |
RTCP 存活请求的发送频率(毫秒)。 |
keep_alive_timeout_ms |
uint32_t |
0 |
自发送最后一个存活请求后,认为连接已断开的时间(毫秒)。 |
max_logical_port |
uint16_t |
100 |
在 RTCP 协商期间尝试的最大逻辑端口数。 |
logical_port_range |
uint16_t |
20 |
每次请求在 RTCP 协商期间尝试的最大逻辑端口数。 |
logical_port_increment |
uint16_t |
2 |
在 RTCP 协商期间尝试的逻辑端口之间的增量。 |
enable_tcp_nodelay |
bool |
false |
启用 TCP_NODELAY 套接字选项。 |
non_blocking_send |
bool |
false |
发送操作是否非阻塞(*)。 |
calculate_crc |
bool |
true |
是否计算并发送消息头中的 CRC。 |
check_crc |
bool |
true |
是否检查传入消息头中的 CRC。 |
apply_security |
bool |
false |
是否使用 TLS。参见《TCP 上的 TLS》。 |
tls_config |
TLSConfig |
TLS 的配置。参见《TCP 上的 TLS》。 | |
default_reception_threads |
ThreadSettings |
接收线程的默认 ThreadSettings。 |
|
reception_threads |
std::map<uint32_t, ThreadSettings> |
特定端口上接收线程的 ThreadSettings。 |
|
keep_alive_thread |
ThreadSettings |
用于保持 TCP 连接存活的线程的 ThreadSettings。 |
|
accept_thread |
ThreadSettings |
用于处理传入 TCP 连接请求的线程的 ThreadSettings。 |
|
tcp_negotiation_timeout |
uint32_t |
0 |
等待逻辑端口协商的时间(毫秒)。如果一个逻辑端口正在协商中,在向该端口发送消息之前,会等待协商完成,等待时间不超过此超时值。将此选项设置为非零值会增加发现时间。将其设置为零表示不等待,但可能导致最初的消息丢失。 |
Warning
尽管成员
listening_ports接受多个端口,但实际上只会使用第一个监听端口。其余的端口将被忽略。Note
如果
listening_ports留空,则该参与者将无法接收传入的连接,但可以连接到其他已配置监听端口的参与者。Note
当
non_blocking_send设置为true时,如果发送缓冲区可能已满,发送操作将立即返回,但不会向上层返回错误。这意味着应用程序的行为就像数据包已发送但丢失了一样。当设置为
false时,发送操作将阻塞,直到网络缓冲区有空间容纳数据包。
6.3.1.1 TCPv4TransportDescriptor
下表描述了 TCPv4TransportDescriptor 独有的数据成员。
| 成员 | 数据类型 | 默认值 | 描述 |
|---|---|---|---|
wan_addr |
octet[4] |
[0, 0, 0, 0] |
WAN 的配置。参见《通过 TCPv4 进行 WAN 或互联网通信》。 |
Note
TCPv4TransportDescriptor的kind值由LOCATOR_KIND_TCPv4的值给出。
6.3.1.2 TCPv6TransportDescriptor
TCPv6TransportDescriptor 除了 TCPTransportDescriptor 中描述的通用数据成员之外,没有其他额外的数据成员。
Note
TCPv6TransportDescriptor的kind值由LOCATOR_KIND_TCPv6的值给出。
6.3.2 启用 TCP 传输
在 eProsima Fast DDS 中有多种启用 TCP 传输的方法。根据每个场景的具体情况,某一种方法可能比其他方法更合适。
6.3.2.1 内置传输的配置
第一种方法是修改负责创建 DomainParticipant 传输的内置传输。现有的启用 TCP 传输的配置是 LARGE_DATA。此选项将分别实例化一个 UDPv4、一个 TCPv4 和一个 SHM 传输。UDP 协议将用于参与者发现阶段(参见《发现阶段》)的组播通告,而参与者的活性维持和应用程序数据传输则通过 TCP 或 SHM 进行。此配置启用了自动发现,并且不需要手动设置每个参与者的 IP 和监听端口,从而避免了典型的服务器-客户端配置。
内置传输可以通过 FASTDDS_BUILTIN_TRANSPORTS 环境变量(参见 FASTDDS_BUILTIN_TRANSPORTS)、XML 配置文件(参见 RTPS 元素类型)或通过代码进行配置。
/***************Environment Variable*********************/
export FASTDDS_BUILTIN_TRANSPORTS=LARGE_DATA
/***************C++*********************/
eprosima::fastdds::dds::DomainParticipantQos qos;
qos.setup_transports(eprosima::fastdds::rtps::BuiltinTransports::LARGE_DATA);
Note
请注意,内置传输的
LARGE_DATA配置将在创建 UDP 和 TCP 传输的同时,也会创建一个 SHM 传输。只要可能,就会使用共享内存。如果在 SHM 可行的情况下需要 TCP 通信,则需要进行手动配置。(参见《大数据模式》)。
6.3.2.2 服务器-客户端配置
要设置服务器-客户端配置,您需要创建 TCPv4TransportDescriptor(用于 TCPv4)或 TCPv6TransportDescriptor(用于 TCPv6)的实例,并将其添加到 DomainParticipant 的用户传输列表中。
根据 TCP 传输描述符的设置和定义的网络定位器,DomainParticipant 可以充当 TCP 服务器或 TCP 客户端。
-
TCP 服务器 :如果在描述符中提供了
listening_ports,DomainParticipant 将充当 TCP 服务器,在给定端口上监听传入的远程连接。以下示例在 C++ 代码和 XML 文件中展示了此过程。eprosima::fastdds::dds::DomainParticipantQos qos;
// Create a descriptor for the new transport.
auto tcp_transport = std::make_sharedeprosima::fastdds::rtps::TCPv4TransportDescriptor();
tcp_transport->add_listener_port(5100);// [OPTIONAL] ThreadSettings configuration
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};// Link the Transport Layer to the Participant.
qos.transport().user_transports.push_back(tcp_transport);// Avoid using the default transport
qos.transport().use_builtin_transports = false;// [OPTIONAL] Set unicast locators
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);
// [OPTIONAL] Logical port default value is 0, automatically assigned.
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); -
TCP 客户端 :如果为 DomainParticipant 提供了
initialPeersList,它将充当 TCP 客户端,尝试连接到给定地址和端口上的远程服务器。以下示例在 C++ 代码和 XML 文件中展示了此过程。有关initialPeersList配置的更多信息,请参阅《初始对等点》。eprosima::fastdds::dds::DomainParticipantQos qos;
// Disable the built-in Transport Layer.
qos.transport().use_builtin_transports = false;// Create a descriptor for the new transport.
// Do not configure any listener port
auto tcp_transport = std::make_sharedeprosima::fastdds::rtps::TCPv4TransportDescriptor();
qos.transport().user_transports.push_back(tcp_transport);// [OPTIONAL] ThreadSettings configuration
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};// Set initial peers.
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);
// If the logical port is set in the server side, it must be also set here with the same value.
// If not set in the server side in a unicast locator, do not set it here.
eprosima::fastdds::rtps::IPLocator::setLogicalPort(initial_peer_locator, 5100);qos.wire_protocol().builtin.initialPeersList.push_back(initial_peer_locator);
Note
手动设置单播定位器是可选的。如果不设置它们,或者将其逻辑端口设置为 0,则客户端的初始对等点不应设置其逻辑端口(或将其设置为 0)。否则,初始对等点的逻辑端口必须与服务端的单播逻辑端口匹配。
《交付机制示例》展示了如何使用和配置 TCP 传输。
6.3.3 通过 TCPv4 进行 WAN 或互联网通信
当配置得当时,Fast DDS 能够通过互联网或其他 WAN 网络进行连接。为了实现此类场景,所涉及的网络设备(例如路由器和防火墙)必须添加允许通信的规则。
例如,设想我们有以下图示的场景:

- 一个 DomainParticipant 充当 TCP 服务器,监听端口 5100,并通过公网 IP 为 80.80.99.45 的路由器连接到 WAN。
- 另一个 DomainParticipant 充当 TCP 客户端,并在其初始对等点列表中配置了服务器的 IP 地址和端口。
通过使用 set_WAN_address(wan_ip),会在参与者的定位器上设置 WAN IP 地址,这些定位器在发现阶段会被通信。
与 LAN 情况一样,手动设置单播定位器是可选的。然而,在这种情况下,设置其 IP 地址时需要考虑以下几点:
- 使用
setWAN()方法在单播定位器中设置 WAN IP 地址是无效的,因为它会被set_WAN_address()调用覆盖。 - 为单播定位器分配 IP 地址时,只能使用
setIPv4()或setIPv6()方法,这些是 LAN IP 设置器。存在一些允许使用这些设置器配合 WAN IP 地址的配置。
根据服务器是否手动设置了其元流量单播定位器和默认单播定位器,客户端需要相应调整其初始对等点列表:
-
如果服务器的单播定位器配置了 LAN IP 地址:
- 初始对等点可以仅使用服务器的 WAN IP(通过 LAN IP 设置器)进行设置。
- 或者,也可以同时配置服务器的 LAN 和 WAN IP 地址,LAN IP 使用 LAN 设置器,WAN IP 使用 WAN 设置器。
-
如果服务器的单播定位器配置了 WAN IP 地址:
- 初始对等点必须仅使用服务器的 WAN IP(通过 LAN 设置器)进行设置。
- 或者,也可以使用 LAN 和 WAN 设置器同时配置 WAN IP 地址。
-
如果服务器未设置任何单播定位器:
- 初始对等点可以仅使用服务器的 WAN IP(通过 LAN 设置器)进行配置。
- 或者,也可以同时配置服务器的 LAN 和 WAN IP 地址,LAN IP 使用 LAN 设置器,WAN IP 使用 WAN 设置器。
Note
手动设置单播定位器是可选的。如果不设置它们,或者将其逻辑端口设置为 0,则客户端的初始对等点不应设置其逻辑端口(或将其设置为 0)。否则,初始对等点的逻辑端口必须与服务端的单播逻辑端口匹配。
在服务器端,路由器必须配置为将所有到达端口 5100 的传入流量转发给 TCP 服务器。通常,将端口 5100 的 NAT 路由到我们的机器就足够了。任何现有的防火墙也应进行相应配置。
此外,为了允许通过 WAN 进行传入连接,TCPv4TransportDescriptor 必须在其 wan_addr 数据成员中指明其公网 IP 地址。以下示例展示了如何在 C++ 和 XML 中配置 DomainParticipant。
eprosima::fastdds::dds::DomainParticipantQos qos;
// Create a descriptor for the new transport.
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");
// [OPTIONAL] ThreadSettings configuration
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};
// Link the Transport Layer to the Participant.
qos.transport().user_transports.push_back(tcp_transport);
// Avoid using the default transport
qos.transport().use_builtin_transports = false;
// [OPTIONAL] Set unicast locators (do not use setWAN(), set_WAN_address() overwrites it)
eprosima::fastdds::rtps::Locator_t locator;
locator.kind = LOCATOR_KIND_TCPv4;
// [RECOMMENDED] Use the LAN address of the server
eprosima::fastdds::rtps::IPLocator::setIPv4(locator, "192.168.1.10");
// [ALTERNATIVE] Use server's WAN address. In that case, initial peers must be configured
// only with server's WAN address.
// eprosima::fastdds::rtps::IPLocator::setIPv4(locator, "80.80.99.45");
eprosima::fastdds::rtps::IPLocator::setPhysicalPort(locator, 5100);
// [OPTIONAL] Logical port default value is 0, automatically assigned.
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);
在客户端,DomainParticipant 必须配置 TCP 服务器的公网 IP 地址和 listening_ports 作为初始对等点。
eprosima::fastdds::dds::DomainParticipantQos qos;
// Disable the built-in Transport Layer.
qos.transport().use_builtin_transports = false;
// Create a descriptor for the new transport.
// Do not configure any listener port
auto tcp_transport = std::make_shared<eprosima::fastdds::rtps::TCPv4TransportDescriptor>();
// [RECOMMENDED] Use client's WAN address if there are more clients in other local networks.
tcp_transport->set_WAN_address("80.80.99.47");
qos.transport().user_transports.push_back(tcp_transport);
// [OPTIONAL] ThreadSettings configuration
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};
// Set initial peers.
eprosima::fastdds::rtps::Locator_t initial_peer_locator;
initial_peer_locator.kind = LOCATOR_KIND_TCPv4;
// [RECOMMENDED] Use both WAN and LAN server addresses
eprosima::fastdds::rtps::IPLocator::setIPv4(initial_peer_locator, "192.168.1.10");
eprosima::fastdds::rtps::IPLocator::setWan(initial_peer_locator, "80.80.99.45");
// [ALTERNATIVE] Use server's WAN address only. Valid if server specified its unicast locators
// with its LAN or WAN address.
// eprosima::fastdds::rtps::IPLocator::setIPv4(initial_peer_locator, "80.80.99.45");
eprosima::fastdds::rtps::IPLocator::setPhysicalPort(initial_peer_locator, 5100);
// If the logical port is set in the server side, it must be also set here with the same value.
// If not set in the server side in a unicast locator, do not set it here.
eprosima::fastdds::rtps::IPLocator::setLogicalPort(initial_peer_locator, 5100);
qos.wire_protocol().builtin.initialPeersList.push_back(initial_peer_locator);
6.3.4 交付机制示例
一个适用于所支持交付机制的"Hello World"示例可以在 delivery_mechanisms 文件夹中找到。它展示了一个发布者和一个订阅者通过所需的交付机制(可以设置为仅 TCP)进行通信。
6.4 共享内存传输
共享内存(SHM)传输利用主机操作系统提供的共享内存机制,实现同一处理单元/机器上运行的实体之间的快速通信。
Fast DDS 利用 DomainParticipant 的 GuidPrefix_t 来识别在同一主机上运行的对等点。GuidPrefix_t 的前 4 个字节相同的两个参与者被认为是在同一主机上运行的。提供了 is_on_same_host_as() API 来检查此条件。另请注意《进程内交付的 GUID 前缀注意事项》中包含的注意事项。
SHM 传输比 UDP/TCP 等其他网络传输提供更好的性能,即使这些传输使用环回接口也是如此。这主要是由以下几个原因造成的:
- 支持大消息:网络协议需要分片数据以符合特定协议和网络栈的要求,这会增加通信开销。SHM 传输允许复制完整消息,其唯一的大小限制是机器的内存容量。
- 减少内存拷贝次数:当将同一消息发送到不同的端点时,SHM 传输可以直接与所有目标端点共享同一内存缓冲区。其他协议则需要为每个端点执行一次消息拷贝。
- 更少的操作系统开销:一旦初始设置完成,共享内存传输所需的系统调用比其他协议少得多。因此,使用 SHM 可以获得性能/时间消耗上的收益。
6.4.1 概念定义
本节描述了一些基本概念,以帮助解释共享内存传输如何工作,从而将数据消息传送到适当的 DomainParticipant。其目的不是作为实现的详尽参考,而是对每个概念进行全面的解释,以便用户可以根据自己的需求配置传输。
本节中的许多描述将按照下图中描绘的示例用例进行,其中参与者 1 向参与者 2 发送一条数据消息。请参考该图理解这些定义。

共享内存传输的时序图
6.4.1.1 段 (Segment)
段(Segment)是一块可以从不同进程访问的共享内存。每个已配置共享内存传输的 DomainParticipant 都会创建一个共享内存段。DomainParticipant 将其需要传递给其他 DomainParticipant 的任何数据写入此段,而远程 DomainParticipant 能够使用共享内存机制直接读取这些数据。
Note
如果以更高权限用户(例如,root)启动任何进程,可能会导致通信问题,因为非特权用户运行的进程可能无法写入该内存段。
每个段都有一个 segmentId,这是一个 16 字符的 UUID,用于唯一标识每个共享内存段。这些 segmentId 用于识别和访问每个 DomainParticipant 的段。
6.4.1.2 Segment Buffer
在共享内存段中分配的缓冲区。它充当放置在段中的 DDS 消息的容器。换句话说,DomainParticipant 写入段的每条消息都将放置在不同的缓冲区中。
6.4.1.3 缓冲区描述符 (Buffer Descriptor)
它充当指向特定段中特定段缓冲区的指针。它包含 segmentId 和该段缓冲区相对于段基址的偏移量。当将消息传递给其他 DomainParticipant 时,共享内存传输仅分发缓冲区描述符,从而避免了消息从一个 DomainParticipant 到另一个的拷贝。借助此描述符,接收方 DomainParticipant 可以访问写入缓冲区的消息,因为它唯一地标识了段(通过 segmentId)和段缓冲区(通过其偏移量)。
6.4.1.4 端口 (Port)
代表一个用于通信缓冲区描述符的通道。它被实现为共享内存中的一个环形缓冲区,以便任何 DomainParticipant 都可以在其上读写信息。每个端口都有一个唯一的标识符,一个 32 位的数字,可用于引用该端口。每个已配置共享内存传输的 DomainParticipant 都会创建一个端口来接收缓冲区描述符。此端口的标识符在发现过程中被共享,以便远程对等点知道在与每个 DomainParticipant 通信时应使用哪个端口。
DomainParticipant 会为其接收端口创建一个监听器,以便当新的缓冲区描述符被推送到端口时,它们能够收到通知。
6.4.1.5 端口健康检查 (Port Health Check)
每当 DomainParticipant 打开一个端口(用于读取或写入)时,都会执行一次健康检查以评估其正确性。原因是,如果其中一个使用端口的进程崩溃,该端口可能会变得无法操作。如果附加的监听器在给定的超时时间内没有响应,则该端口被视为已损坏,并将被销毁并重新创建。
6.4.2 SharedMemTransportDescriptor
除了 TransportDescriptorInterface 中定义的数据成员外,共享内存的 TransportDescriptor 还定义了以下数据成员:
| 成员 | 数据类型 | 默认值 | 访问器/修改器 | 描述 |
|---|---|---|---|---|
segment_size_ |
uint32_t |
512*1024 |
segment_size() |
共享内存段的大小(以八位字节为单位)。 |
port_queue_capacity_ |
uint32_t |
512 |
port_queue_capacity() |
监听端口的大小(以消息数为单位)。 |
healthy_check_timeout_ms_ |
uint32_t |
1000 |
healthy_check_timeout_ms() |
端口健康检查的超时时间(以毫秒为单位)。 |
rtps_dump_file_ |
string |
"" |
rtps_dump_file() |
协议转储文件的完整路径。 |
default_reception_threads |
ThreadSettings |
default_reception_threads |
接收线程的默认 ThreadSettings。 |
|
reception_threads |
std::map<uint32_t, ThreadSettings> |
reception_threads |
特定端口上接收线程的 ThreadSettings。 |
|
dump_thread() |
ThreadSettings |
dump_thread() |
SHM 转储线程的 ThreadSettings。 |
如果 rtps_dump_file_ 不为空,则 DomainParticipant 上的所有共享内存流量(发送和接收)都将被跟踪到一个文件中。输出文件格式为 tcpdump 十六进制文本,可以使用诸如 Wireshark 之类的协议分析器应用程序进行处理。具体来说,要使用 Wireshark 打开该文件,请使用"Import from Hex Dump"选项,并选择"Raw IPv4"封装类型。
Note
SharedMemTransportDescriptor的kind值由LOCATOR_KIND_SHM的值给出。Warning
将
segment_size()设置为接近或小于数据大小会带来很高的数据丢失风险,因为写操作会在单次发送操作期间覆盖缓冲区。
6.4.3 启用共享内存传输
Fast DDS 默认启用 SHM 传输。然而,如果需要,应用程序可以启用其他 SHM 传输。要在 DomainParticipant 中启用新的 SHM 传输,首先创建 SharedMemTransportDescriptor 的实例,并将其添加到 DomainParticipant 的用户传输列表中。
以下示例在 C++ 代码和 XML 文件中展示了此过程。
DomainParticipantQos qos;
// Create a descriptor for the new transport.
std::shared_ptr<SharedMemTransportDescriptor> shm_transport =
std::make_shared<SharedMemTransportDescriptor>();
// [OPTIONAL] ThreadSettings configuration
shm_transport->default_reception_threads(eprosima::fastdds::rtps::ThreadSettings{-1, 0, 0, -1});
shm_transport->set_thread_config_for_port(12345, eprosima::fastdds::rtps::ThreadSettings{-1, 0, 0, -1});
shm_transport->dump_thread(eprosima::fastdds::rtps::ThreadSettings{-1, 0, 0, -1});
// Link the Transport Layer to the Participant.
qos.transport().user_transports.push_back(shm_transport);
Note
如果启用了多个传输,即使在同一台机器上运行的两个参与者都启用了 SHM 传输,发现流量也始终使用 UDP/TCP 传输进行。如果一个或多个参与者仅启用了 SHM,而其他参与者同时使用其他传输,这可能会导致发现问题。此外,当同一台机器上的两个参与者都启用了 SHM 传输时,它们之间的用户数据通信将自动仅由 SHM 传输执行。这两个参与者之间不会使用其他已启用的传输。
Hint
要通过共享内存配置发现流量,必须禁用默认的内置传输。这样,通信将完全通过共享内存进行。下面的代码片段示例在 C++ 代码和 XML 文件中展示了此过程。完整示例请参见《交付机制示例》。
DomainParticipantQos qos; // Create a descriptor for the new transport. std::shared_ptr<SharedMemTransportDescriptor> shm_transport = std::make_shared<SharedMemTransportDescriptor>(); // Link the Transport Layer to the Participant. qos.transport().user_transports.push_back(shm_transport); // Explicit configuration of SharedMem transport qos.transport().use_builtin_transports = false;
6.4.4 交付机制示例
一个适用于所支持交付机制的"Hello World"示例可以在 delivery_mechanisms 文件夹中找到。它展示了一个发布者和一个订阅者通过所需的交付机制(可以设置为仅共享内存)进行通信。
6.5 数据共享交付
Fast DDS 允许通过共享内存在 DataReader 和 DataWriter 之间共享 DataWriter 的历史记录,从而加速同一台机器内实体之间的通信。这避免了传输层涉及的任何开销,有效地避免了 DataWriter 和 DataReader 之间的任何数据拷贝。
Note
Fast DDS 利用 DomainParticipant 的
GuidPrefix_t来识别在同一主机上运行的对等点。GuidPrefix_t的前 4 个字节相同的两个参与者被认为是在同一主机上运行的。提供了is_on_same_host_as()API 来检查此条件。另请注意《进程内交付的 GUID 前缀注意事项》中包含的注意事项。
使用数据共享交付并不能避免应用程序与 DataReader 和 DataWriter 之间的数据拷贝。在某些情况下,可以通过使用零拷贝通信来避免这些拷贝。
Note
尽管数据共享交付使用共享内存,但它与共享内存传输的不同之处在于,共享内存是一种完全合规的传输。这意味着,使用共享内存传输时,传输的数据必须从 DataWriter 历史记录复制到传输层,然后再从传输层复制到 DataReader。而使用数据共享则可以避免这些拷贝。
6.5.1 概述
当 DataWriter 被创建时,Fast DDS 会预分配一个包含 max_samples + extra_samples 个样本的池,该池位于一个共享内存映射文件中。当发布新数据时,DataWriter 会从这个池中取出一个样本并将其添加到其历史记录中,并通知 DataReader 池中的哪个样本包含了新数据。
DataReader 将能够访问同一个共享内存映射文件,并且能够访问 DataWriter 发布的数据。
6.5.2 约束条件
仅当满足以下要求时,此功能才可用:
- DataWriter 和 DataReader 可以访问相同的共享内存。
- 主题具有有界的
TopicDataType,即其is_bounded()成员函数返回true。 - 主题不是带键的。
- DataWriter 配置为
PREALLOCATED_MEMORY_MODE或PREALLOCATED_WITH_REALLOC_MEMORY_MODE。 - 未使用安全插件。
此外,DataReader 的 HistoryQos 也存在一个限制。使用数据共享机制时,DataWriter 的历史记录会与 DataReader 共享。这意味着 DataReader 上的有效 HistoryQos 深度最多是 DataWriter 的 HistoryQos 深度。为避免混淆,请将 DataReader 的历史深度设置为小于或等于 DataWriter 的值。
6.5.3 数据共享交付配置
可以在 DataWriter 和 DataReader 上使用 DataSharingQosPolicy 来配置数据共享交付。可以配置四个属性:
- 数据共享交付类型。
- 共享内存目录。
- 数据共享域标识符。
- 数据共享域标识符的最大数量。
6.5.3.1 数据共享交付类型
可以设置为以下三种模式之一:
AUTO:如果 DataWriter 和 DataReader 均满足要求,则它们之间将使用数据共享交付。这是默认值。ON:与AUTO类似,但如果要求未满足,则实体的创建将失败。OFF:此实体上将不使用数据共享交付。
下表显示了两个实体根据其配置何时数据共享兼容(假设实体创建没有失败,并且两个实体都可以访问共享内存):
| Writer\Reader | ON | OFF | AUTO |
|---|---|---|---|
| ON | 仅当它们有共同的域 ID 时 | 否 | 仅当它们有共同的域 ID 时 |
| OFF | 否 | 否 | 否 |
| AUTO | 仅当它们有共同的域 ID 时 | 否 | 仅当 TopicDataType 是有界的并且它们有共同的域 ID 时 |
6.5.3.2 数据共享域标识符
每个实体定义一组标识符,代表该实体所属的域。仅当两个实体至少有一个共同域时,它们之间才能使用数据共享交付。
用户可以通过 DataSharingQosPolicy 定义 DataWriter 或 DataReader 的域。如果用户未提供域标识符,系统将自动创建一个。此自动生成的数据共享域对于实体运行的机器是唯一的。也就是说,在同一台机器上运行、且用户未配置特定域的所有实体,将能够使用数据共享交付(前提是满足其余要求)。
在发现阶段,实体会交换它们的域标识符,并检查它们是否可以使用数据共享进行通信。
Note
尽管数据共享域标识符是一个 64 位整数,但用户定义的标识符被限制为 16 位整数。
6.5.3.3 数据共享域标识符的最大数量
在发现过程中,期望从远程实体接收的域标识符的最大数量。如果远程实体定义(并发送)的域标识符数量超过此数量,发现将失败。
默认情况下,标识符的数量没有限制。可以使用 max_domains() 函数更改默认值。定义一个有限的数字允许在实体创建期间预分配接收标识符列表所需的内存,从而避免后续的动态内存分配。请注意,值为 0 表示无限制。
6.5.3.4 共享内存目录
如果为共享内存文件指定了用户定义的目录,则该目录将用于数据共享交付所使用的内存映射文件。如果未指定,则使用为当前系统配置的默认目录。
在某些场景下,配置用户定义的目录可能很有用:
- 为内存映射文件选择启用了大页(Huge TLB)的文件系统。
- 允许挂载了同一目录的容器之间进行数据共享交付。
6.5.4 DataReader 和 DataWriter 历史记录耦合
在传统的传输层交付中,DataReader 和 DataWriter 保持分离且独立的历史记录,各自拥有样本的独立副本。一旦样本通过传输发送并被 DataReader 接收,DataWriter 就可以自由地从其历史记录中移除该样本,而不会影响 DataReader。
在数据共享交付中,DataReader 直接访问 DataWriter 创建的数据实例。这意味着 DataReader 和 DataWriter 历史记录中的样本都引用共享内存中的同一个对象。因此,DataReader 和 DataWriter 历史记录的行为存在强耦合。
Important
如果 DataWriter 重用同一个样本发布新数据,DataReader 将无法再访问旧的数据样本。
Note
DataWriter 可以从其历史记录中移除样本,并且该样本在 DataReader 上仍然可用,除非池中的同一个样本被重用来发布新的数据。
6.5.4.1 数据确认机制
在使用数据共享交付时,DataReader 的样本确认发生在应用程序首次检索样本时(通过 DataReader::read_next_sample()、DataReader::take_next_sample() 或其任何变体)。一旦应用程序访问了数据,DataWriter 就可以自由地重用该样本以发布新数据。DataReader 会检测样本何时被重用,并自动将其从其历史记录中移除。
这意味着,之后再次尝试从 DataReader 访问同一个样本时,可能根本不会返回任何样本。
6.5.4.2 阻塞样本重用直至确认完成
在 KEEP_LAST_HISTORY_QOS 或 BEST_EFFORT_RELIABILITY_QOS 配置下,DataWriter 可以从其历史记录中移除样本以添加新样本,即使它们未被 DataReader 确认。在发布速率持续快于 DataReader 处理样本速率的情况下,这可能导致每个样本在应用程序有机会处理之前就被重用,从而在应用程序层面阻塞通信。
为了避免这种情况,预分配池中的样本除非已被确认(即已被应用程序至少处理过一次),否则永远不会被重用。如果池中没有可重用的样本,DataWriter 中的写入操作将被阻塞,直到有可用的样本或达到 max_blocking_time。
请注意,DataWriter 的历史记录不受此行为影响,样本将按照标准规则从历史记录中移除。只有池样本的重用受到影响。这意味着 DataWriter 的历史记录可能为空,但写入操作仍然可能因为池中的所有样本都未被确认而被阻塞。
使用 extra_samples 可以减少 DataWriter 在写入操作时被阻塞的机会。这将使池分配比历史记录大小更多的样本,从而使 DataWriter 有更多机会获得空闲样本,同时 DataReader 仍然可以访问已从 DataWriter 历史记录中移除的样本。
6.6 进程内交付
eProsima Fast DDS 允许通过避免传输层涉及的任何开销来加速同一进程内实体之间的通信。相反,发布者直接调用订阅者的接收函数。这不仅避免了传输的拷贝或发送操作,还确保了消息被订阅者接收,从而避免了确认机制。
此功能默认启用,并且可以使用 XML 配置文件进行配置(参见《进程内交付配置文件》)。目前有以下选项可用:
-
INTRAPROCESS_OFF:禁用该功能。 -
INTRAPROCESS_USER_DATA_ONLY:发现元数据继续使用普通传输。 -
INTRAPROCESS_FULL:默认值。用户数据和发现元数据都使用进程内交付。<library_settings>
<intraprocess_delivery>FULL</intraprocess_delivery>
</library_settings>
6.6.1 进程内交付的 GUID 前缀注意事项
Fast DDS 利用 DomainParticipant 的 GuidPrefix_t 来识别在同一进程中运行的对等点。GuidPrefix_t 的前 8 个字节相同的两个参与者被认为是在同一进程中运行的,因此将使用进程内交付。提供了 is_on_same_process_as() API 来检查此条件。当让 Fast DDS 为创建的 DomainParticipant 设置 GUID 前缀时,此机制可以开箱即用。但是,在通过编程方式或使用 XML 手动设置 GuidPrefix_t 时,需要特别注意。
Important
Fast DDS 在分配 GUID 前缀时会考虑几个主机参数,其中包括已启用的网络接口。因此,如果在运行时网络接口发生变化,任何新的 DomainParticipant 都将拥有不同的 GUID 前缀,并被认为是在另一台主机上运行的。
eprosima::fastdds::rtps::GuidPrefix_t guid_prefix;
guid_prefix.value[0] = eprosima::fastdds::rtps::octet(0x77);
guid_prefix.value[1] = eprosima::fastdds::rtps::octet(0x73);
guid_prefix.value[2] = eprosima::fastdds::rtps::octet(0x71);
guid_prefix.value[3] = eprosima::fastdds::rtps::octet(0x85);
guid_prefix.value[4] = eprosima::fastdds::rtps::octet(0x69);
guid_prefix.value[5] = eprosima::fastdds::rtps::octet(0x76);
guid_prefix.value[6] = eprosima::fastdds::rtps::octet(0x95);
guid_prefix.value[7] = eprosima::fastdds::rtps::octet(0x66);
guid_prefix.value[8] = eprosima::fastdds::rtps::octet(0x65);
guid_prefix.value[9] = eprosima::fastdds::rtps::octet(0x82);
guid_prefix.value[10] = eprosima::fastdds::rtps::octet(0x82);
guid_prefix.value[11] = eprosima::fastdds::rtps::octet(0x79);
DomainParticipantQos participant_qos;
participant_qos.wire_protocol().prefix = guid_prefix;
// Use modified QoS in the creation of the DomainParticipant entity
participant_ = factory_->create_participant(domain, participant_qos);
6.7 TCP 上的 TLS
Warning
本文档假定读者具备 TLS 概念的基础知识,因此不会详细解释证书颁发机构(CA)、私钥、RSA 加密系统和 Diffie-Hellman 加密协议等术语。
Fast DDS 允许配置 TCP 传输以使用 TLS(传输层安全性)。要设置 TLS,TCPTransportDescriptor 必须将其 apply_security 数据成员设置为 true,并在 TCPTransportDescriptor 的 tls_config 数据成员中填入所需的配置。以下是 TCP 服务器上 TLS 配置的示例。
DomainParticipantQos qos;
// Create a descriptor for the new transport.
auto tls_transport = std::make_shared<TCPv4TransportDescriptor>();
tls_transport->sendBufferSize = 9216;
tls_transport->receiveBufferSize = 9216;
tls_transport->add_listener_port(5100);
// Create the TLS configuration
using TLSOptions = eprosima::fastdds::rtps::TCPTransportDescriptor::TLSConfig::TLSOptions;
tls_transport->apply_security = true;
tls_transport->tls_config.password = "test";
tls_transport->tls_config.cert_chain_file = "server.pem";
tls_transport->tls_config.private_key_file = "serverkey.pem";
tls_transport->tls_config.tmp_dh_file = "dh2048.pem";
tls_transport->tls_config.add_option(TLSOptions::DEFAULT_WORKAROUNDS);
tls_transport->tls_config.add_option(TLSOptions::SINGLE_DH_USE);
tls_transport->tls_config.add_option(TLSOptions::NO_SSLV2);
// Link the Transport Layer to the Participant.
qos.transport().user_transports.push_back(tls_transport);
以下示例显示了 TCP 客户端上相应的配置。
DomainParticipantQos qos;
// Set initial peers.
Locator_t initial_peer_locator;
initial_peer_locator.kind = LOCATOR_KIND_TCPv4;
IPLocator::setIPv4(initial_peer_locator, "192.168.1.10");
initial_peer_locator.port = 5100;
qos.wire_protocol().builtin.initialPeersList.push_back(initial_peer_locator);
// Create a descriptor for the new transport.
auto tls_transport = std::make_shared<TCPv4TransportDescriptor>();
// Create the TLS configuration
using TLSOptions = eprosima::fastdds::rtps::TCPTransportDescriptor::TLSConfig::TLSOptions;
using TLSVerifyMode = eprosima::fastdds::rtps::TCPTransportDescriptor::TLSConfig::TLSVerifyMode;
tls_transport->apply_security = true;
tls_transport->tls_config.verify_file = "ca.pem";
tls_transport->tls_config.add_verify_mode(TLSVerifyMode::VERIFY_PEER);
tls_transport->tls_config.add_verify_mode(TLSVerifyMode::VERIFY_FAIL_IF_NO_PEER_CERT);
tls_transport->tls_config.add_option(TLSOptions::DEFAULT_WORKAROUNDS);
tls_transport->tls_config.add_option(TLSOptions::SINGLE_DH_USE);
tls_transport->tls_config.add_option(TLSOptions::NO_SSLV2);
tls_transport->tls_config.server_name = "my_server.com";
// Link the Transport Layer to the Participant.
qos.transport().user_transports.push_back(tls_transport);
下表描述了 TLSConfig 上可配置的数据成员。
| 成员 | 数据类型 | 默认值 | 描述 |
|---|---|---|---|
password |
string |
"" |
private_key_file 或 rsa_private_key_file 的密码。 |
private_key_file |
string |
"" |
私钥证书文件的路径。 |
rsa_private_key_file |
string |
"" |
RSA 私钥证书文件的路径。 |
cert_chain_file |
string |
"" |
公钥证书链文件的路径。 |
tmp_dh_file |
string |
"" |
Diffie-Hellman 参数文件的路径。 |
verify_file |
string |
"" |
CA(证书颁发机构)文件的路径。 |
verify_mode |
TLSVerifyMode |
TLSVerifyMode::UNUSED |
建立验证模式掩码。参见《TLS 验证模式》。 |
options |
TLSOptions |
TLSOptions::NONE |
建立 SSL 上下文选项掩码。参见《TLS 选项》。 |
verify_paths |
vector<string> |
空向量 | 系统将查找验证文件的路径。 |
verify_depth |
int32_t |
-1 |
验证中间证书的最大允许深度。 |
default_verify_path |
bool |
false |
在默认路径中查找验证文件。 |
handshake_role |
TLSHandShakeRole |
TLSHandShakeRole::DEFAULT |
传输在握手中将扮演的角色。参见《TLS 握手角色》。 |
server_name |
string |
"" |
使用服务器名称指示(SNI)时所需的服务器名称或主机名。 |
Note
Fast DDS 使用 Boost.Asio 库来处理 TLS 安全连接。这些数据成员用于构建 Asio 库上下文,并且其中大部分成员直接映射到此上下文中,无需进一步操作。您可以在 Boost.Asio 上下文文档中找到关于每个成员影响的更多信息。
6.7.1 TLS 验证模式
验证模式定义了如何验证对等节点。下表描述了可用的验证选项。可以使用 add_verify_mode() 成员函数在同一个 TCPTransportDescriptor 中组合多个验证选项。
| 值 | 描述 |
|---|---|
TLSVerifyMode::VERIFY_NONE |
不执行验证。 |
TLSVerifyMode::VERIFY_PEER |
执行对等点验证。 |
TLSVerifyMode::VERIFY_FAIL_IF_NO_PEER_CERT |
如果对等点没有证书,则验证失败。除非同时设置了 TLSVerifyMode::VERIFY_PEER,否则忽略。 |
TLSVerifyMode::VERIFY_CLIENT_ONCE |
在重新协商时不请求客户端证书。除非同时设置了 TLSVerifyMode::VERIFY_PEER,否则忽略。 |
Note
有关不同验证模式的完整描述,请参阅 OpenSSL 文档。
6.7.2 TLS 选项
这些选项定义要支持哪些 TLS 功能。下表描述了可用的选项。可以使用 add_option() 成员函数在同一个 TransportDescriptor 中组合多个选项。
| 值 | 描述 |
|---|---|
TLSOptions::DEFAULT_WORKAROUNDS |
实现各种错误修复。参见 Boost.Asio 上下文。 |
TLSOptions::NO_COMPRESSION |
禁用压缩。 |
TLSOptions::NO_SSLV2 |
禁用 SSL v2。 |
TLSOptions::NO_SSLV3 |
禁用 SSL v3。 |
TLSOptions::NO_TLSV1 |
禁用 TLS v1。 |
TLSOptions::NO_TLSV1_1 |
禁用 TLS v1.1。 |
TLSOptions::NO_TLSV1_2 |
禁用 TLS v1.2。 |
TLSOptions::NO_TLSV1_3 |
禁用 TLS v1.3。 |
TLSOptions::SINGLE_DH_USE |
使用 Diffie-Hellman 参数时始终创建新密钥。 |
6.7.3 TLS 握手角色
该角色可以取以下值:
| 值 | 描述 |
|---|---|
TLSHandShakeRole::DEFAULT |
如果是连接方,则配置为客户端;如果是接受方,则配置为服务器。 |
TLSHandShakeRole::CLIENT |
配置为客户端。 |
TLSHandShakeRole::SERVER |
配置为服务器。 |
6.8 Low Bandwidth Transports(Pro)
Fast DDS Pro 中的低带宽传输功能通过减小网络数据包的大小,能够在受限网络上实现高效通信。这种优化对于带宽有限的环境(例如卫星链路或战术电台)尤其有益,使 Fast DDS 应用程序能够在此类场景中可靠且高效地运行。
此功能提供了两种专门的传输方式:
- 有效负载压缩传输:在通过网络传输消息有效负载之前对其进行压缩。通过减少发送的数据量,它最大限度地减少了带宽使用,并且可以提高带宽受限链路的吞吐量并减少延迟。
- 头部缩减传输:最小化每个数据包中 RTPS 协议头部的大小。通过减少头部开销,它进一步优化了总数据包大小并确保高效通信,特别是对于小型且频繁的消息。
这两种传输方式都是使用传输串联功能实现的,因此它们可以独立或一起配置,为根据低带宽环境的要求调整 Fast DDS 提供了灵活性。
6.8.1 有效负载压缩传输
这种低带宽传输在发送前对数据执行标准压缩,并在接收后执行相应的解压缩。压缩算法可以选择 Zlib 或 Bzip2,甚至可以由传输同时执行两者并使用产生输出最短的算法。
此传输可以使用 DomainParticipantQos 的 properties() rtps.payload_compression 进行配置。
| 属性名称 | 属性值 |
|---|---|
compression_library |
所有数据包的默认压缩库。取值:ZLIB、BZIP2 或 AUTOMATIC |
compression_level |
所有数据包的默认压缩级别。取值:1 到 9 |
compression_library.small_packets |
小数据包的压缩库。取值:ZLIB、BZIP2 或 AUTOMATIC |
compression_level.small_packets |
小数据包的压缩级别。取值:1 到 9 |
compression_library.medium_packets |
中等数据包的压缩库。取值:ZLIB、BZIP2 或 AUTOMATIC |
compression_level.medium_packets |
中等数据包的压缩级别。取值:1 到 9 |
compression_library.large_packets |
大数据包的压缩库。取值:ZLIB、BZIP2 或 AUTOMATIC |
compression_level.large_packets |
大数据包的压缩级别。取值:1 到 9 |
low_mark |
数据包被视为"小数据包"的最大尺寸。 |
high_mark |
数据包被视为"大数据包"的最小尺寸。 |
6.8.1.1 PayloadCompressionTransportDescriptor
PayloadCompressionTransportDescriptor 除了 ChainingTransportDescriptor 中描述的通用数据成员之外,没有其他额外的数据成员。
6.8.1.2 启用有效负载压缩传输
要在 DomainParticipant 中启用新的 PayloadCompressionTransport,首先创建 PayloadCompressionTransportDescriptor 的实例,并将其添加到 DomainParticipant 的用户传输列表中。
以下示例在 C++ 代码和 XML 文件中展示了此过程。
DomainParticipantQos participant_qos;
auto udp_transport = std::make_shared<UDPv4TransportDescriptor>();
// Create a descriptor for the new transport.
auto compression_transport =
std::make_shared<PayloadCompressionTransportDescriptor>(udp_transport);
// [OPTIONAL] Transport configuration
participant_qos.properties().properties().emplace_back(Property(
"rtps.payload_compression.compression_library",
"AUTOMATIC"));
// Link the Transport Layer to the Participant.
participant_qos.transport().use_builtin_transports = false;
participant_qos.transport().user_transports.push_back(compression_transport);
6.8.2 头部缩减传输
此传输在发送前对数据执行特定压缩,并在接收后执行相应的解压缩。该压缩算法专为 RTPS 协议设计。它会移除某些头部,同时压缩其他头部。
此传输可以使用 DomainParticipantQos 的 properties() rtps.header_reduction 进行配置。
| 属性名称 | 属性值 |
|---|---|
remove_protocol |
从 RTPS 头部中移除协议ID(ProtocolId,用于将消息标识为 RTPS 消息)。取值:true 或 false |
remove_version |
从 RTPS 头部中移除协议版本(ProtocolVersion,标识 RTPS 协议的版本)。取值:true 或 false |
remove_vendor_id |
从 RTPS 头部中移除供应商 ID(VendorId,指示提供 RTPS 协议实现的供应商)。取值:true 或 false |
compress_guid_prefix |
压缩 GUID 前缀(GuidPrefix,RTPS 协议中每个参与者的唯一标识符),从三个 32 位字压缩为更小的大小。取值:三个介于 0 到 32 之间的数字,表示每个 GuidPrefix 字必须缩减的位数。示例:8,8,16 |
submessage.combine_id_and_flags |
将子消息 ID(SubmessageId)和子消息标志(SubmessageFlags)合并为子消息头部中的一个字节。取值:true 或 false |
submessage.remove_extra_flags |
从子消息头部中移除额外标志(ExtraFlags)字段。取值:true 或 false |
submessage.compress_entitiy_ids |
压缩子消息头部中的实体 ID(EntityId)字段,从两个 32 位字压缩为更小的大小。取值:两个介于 0 到 32 之间的数字,表示每个 EntityId 必须缩减的位数。示例:16,16 |
submessage.compress_sequence_number |
压缩子消息头部中的序列号(SequenceNumber)字段,从 64 位字压缩为更小的大小。取值:一个介于 0 到 64 之间的数字,表示序列号必须缩减的位数。示例:32 |
6.8.2.1 HeaderReductionTransportDescriptor
HeaderReductionTransportDescriptor 除了 ChainingTransportDescriptor 中描述的通用数据成员之外,没有其他额外的数据成员。
6.8.2.2 启用头部缩减传输
要在 DomainParticipant 中启用新的 HeaderReductionTransport,首先创建 HeaderReductionTransportDescriptor 的实例,并将其添加到 DomainParticipant 的用户传输列表中。
以下示例在 C++ 代码和 XML 文件中展示了此过程。
DomainParticipantQos participant_qos;
auto udp_transport = std::make_shared<UDPv4TransportDescriptor>();
// Create a descriptor for the new transport.
auto header_reduction_transport = std::make_shared<HeaderReductionTransportDescriptor>(
udp_transport);
// [OPTIONAL] Transport configuration
participant_qos.properties().properties().emplace_back(Property(
"rtps.header_reduction.remove_version", "true"));
participant_qos.properties().properties().emplace_back(Property(
"rtps.header_reduction.remove_vendor_id", "true"));
participant_qos.properties().properties().emplace_back(Property(
"rtps.header_reduction.submessage.combine_id_and_flags",
"true"));
participant_qos.properties().properties().emplace_back(Property(
"rtps.header_reduction.submessage.compress_entitiy_ids",
"16,16"));
// Link the Transport Layer to the Participant.
participant_qos.transport().use_builtin_transports = false;
participant_qos.transport().user_transports.push_back(header_reduction_transport);
6.8.3 完整示例
此示例展示了如何组合所有低带宽传输,以确保在受限网络中获得最佳性能。此过程可以在 C++ 代码或 XML 文件中进行配置。
DomainParticipantQos participant_qos;
auto udp_transport = std::make_shared<UDPv4TransportDescriptor>();
auto compress_transport =
std::make_shared<PayloadCompressionTransportDescriptor>(udp_transport);
participant_qos.properties().properties().emplace_back(Property(
"rtps.payload_compression.compression_library",
"AUTOMATIC"));
auto header_reduction_transport = std::make_shared<HeaderReductionTransportDescriptor>(
compress_transport);
participant_qos.properties().properties().emplace_back(Property(
"rtps.header_reduction.remove_version", "true"));
participant_qos.properties().properties().emplace_back(Property(
"rtps.header_reduction.remove_vendor_id", "true"));
participant_qos.properties().properties().emplace_back(Property(
"rtps.header_reduction.submessage.combine_id_and_flags",
"true"));
participant_qos.properties().properties().emplace_back(Property(
"rtps.header_reduction.submessage.compress_entitiy_ids",
"16,16"));
participant_qos.transport().use_builtin_transports = false;
participant_qos.transport().user_transports.push_back(header_reduction_transport);
6.9 监听定位器
监听定位器用于在 DomainParticipant 上接收传入的通信流量。这些定位器可以根据通信类型和数据性质进行分类。
根据通信类型,我们有:
- 组播定位器:监听组播通信。
- 单播定位器:监听单播通信。
根据数据性质,我们有:
- 元流量定位器:用于接收元流量信息,通常由内置端点用于执行发现。
- 用户定位器:由用户创建的端点使用,用于接收用户主题数据变更。
应用程序可以提供自己的监听定位器,或者使用 eProsima Fast DDS 提供的默认监听定位器。
6.9.1 添加监听定位器
用户可以使用 DomainParticipantQos 向 DomainParticipant 添加自定义的监听定位器。根据定位器添加到的字段不同,它将被视为组播、单播、用户或元流量定位器。
Note
UDP 和 TCP 单播定位器都支持使用空地址。在这种情况下,Fast DDS 会自动获取并使用本地网络地址。
Note
UDP 和 TCP 定位器都支持使用零端口。在这种情况下,Fast DDS 会自动计算并使用该类型流量的公认端口。有关公认端口的详细信息,请参阅《公认端口》。
Warning
TCP 不支持组播场景,因此必须仔细规划网络架构。
6.9.1.1 元流量组播定位器
用户可以在 WireProtocolConfigQos 中的 builtin.metatrafficMulticastLocatorList 内设置自己的元流量组播定位器。
DomainParticipantQos qos;
// This locator will open a socket to listen network messages
// on UDPv4 port 22222 over multicast address 239.255.0.1
eprosima::fastdds::rtps::Locator_t locator;
IPLocator::setIPv4(locator, 239, 255, 0, 1);
locator.port = 22222;
// Add the locator to the DomainParticipantQos
qos.wire_protocol().builtin.metatrafficMulticastLocatorList.push_back(locator);
6.9.1.2 元流量单播定位器
用户可以在 WireProtocolConfigQos 中的 builtin.metatrafficUnicastLocatorList 内设置自己的元流量单播定位器。
DomainParticipantQos qos;
// This locator will open a socket to listen network messages
// on UDPv4 port 22223 over address 192.168.0.1
eprosima::fastdds::rtps::Locator_t locator;
IPLocator::setIPv4(locator, 192, 168, 0, 1);
locator.port = 22223;
// Add the locator to the DomainParticipantQos
qos.wire_protocol().builtin.metatrafficUnicastLocatorList.push_back(locator);
6.9.1.3 用户流量组播定位器
用户可以在 WireProtocolConfigQos 中的 default_multicast_locator_list 内设置自己的用户流量组播定位器。
DomainParticipantQos qos;
// This locator will open a socket to listen network messages
// on UDPv4 port 22224 over multicast address 239.255.0.1
eprosima::fastdds::rtps::Locator_t locator;
IPLocator::setIPv4(locator, 239, 255, 0, 1);
locator.port = 22224;
// Add the locator to the DomainParticipantQos
qos.wire_protocol().default_multicast_locator_list.push_back(locator);
6.9.1.4 用户流量单播定位器
用户可以在 WireProtocolConfigQos 中的 default_unicast_locator_list 内设置自己的用户流量单播定位器。
DomainParticipantQos qos;
// This locator will open a socket to listen network messages
// on UDPv4 port 22225 over address 192.168.0.1
eprosima::fastdds::rtps::Locator_t locator;
IPLocator::setIPv4(locator, 192, 168, 0, 1);
locator.port = 22225;
// Add the locator to the DomainParticipantQos
qos.wire_protocol().default_unicast_locator_list.push_back(locator);
6.9.2 默认监听定位器
如果应用程序未定义任何监听定位器,eProsima Fast DDS 默认会自动启用一组监听的 UDPv4 定位器。这使得在大多数情况下无需进一步配置传输层即可实现开箱即用的通信。
如果应用程序未定义任何元流量定位器(既无单播也无组播),Fast DDS 会启用一个组播定位器(用于发现过程)和一个单播定位器(用于与已发现的 DomainParticipant 进行点对点通信)。
如果应用程序未定义任何用户流量定位器(既无单播也无组播),Fast DDS 会启用一个单播定位器,用于主题数据的点对点通信。
如果应用程序未定义任何 participantId,Fast DDS 将使用 DomainParticipantFactory 给出的值,该工厂会始终尝试为每个 DomainParticipantFactory(每个进程)提供最低可用的值。
例如,可以按照《禁用所有组播流量》中的描述,通过添加一个单一的元流量单播定位器来阻止组播流量。
默认监听定位器始终使用《公认端口》。
6.9.3 公认端口
DDSI-RTPS V2.2 标准(第 9.6.1.1 节)定义了一组计算默认定位器公认端口的规则,以便 DomainParticipant 可以使用这些默认定位器进行通信。当定位器配置的端口号为 0 时,Fast DDS 也会自动选择公认端口。
公认端口使用以下预定义规则计算:
默认监听定位器的端口计算规则
| 流量类型 | 公认端口表达式 |
|---|---|
| 元流量组播 | PB + DG * domainId + offsetd0 |
| 元流量单播 | PB + DG * domainId + offsetd1 + PG * participantId |
| 用户组播 | PB + DG * domainId + offsetd2 |
| 用户单播 | PB + DG * domainId + offsetd3 + PG * participantId |
| 发现服务器(简易模式) | PB + DG * domainId + offsetd4 |
这些规则中使用的值在下表中解释。可以使用 DomainParticipantQos 中 WireProtocolConfigQos 的 port 成员修改默认值。
Note
在部署中,如果在同一主机内创建多个 DomainParticipant,当创建的 DomainParticipant 数量达到
BuiltinAttributes::mutation_tries的值(默认为 100)时,可能会导致它们无法获得可用端口。当发生这种情况时,DomainParticipant 将无法创建监听端口(这会在日志中发出警告),并且将在没有配置单播定位器的情况下创建。有关配置
mutation_tries值的示例,请参阅此示例。
计算公认端口的规则中使用的值
| 符号 | 含义 | 默认值 | QoS 字段 |
|---|---|---|---|
DG |
域 ID 增益 | 250 |
wire_protocol().port.domainIDGain |
PG |
参与者 ID 增益 | 2 |
wire_protocol().port.participantIDGain |
PB |
端口基址 | 7400 |
wire_protocol().port.portBase |
offsetd0 |
附加偏移量 | 0 |
wire_protocol().port.offsetd0 |
offsetd1 |
附加偏移量 | 10 |
wire_protocol().port.offsetd1 |
offsetd2 |
附加偏移量 | 1 |
wire_protocol().port.offsetd2 |
offsetd3 |
附加偏移量 | 11 |
wire_protocol().port.offsetd3 |
offsetd4 |
附加偏移量 | 2 |
wire_protocol().port.offsetd4 |
6.10 通告的定位器
为了进行通信,DDS 实体需要交换它们可以被访问的地址和端口列表。除了默认通告的定位器(对应于应用程序运行所在主机上的接口地址)之外,当相应的路由规则已设置时,用户可以配置具有其他网络地址和端口的附加定位器。
6.10.1 默认通告的定位器
默认通告的定位器列表将根据监听定位器构建,如下所示:
- 如果定位器的地址字段是空地址(例如 UDPv4 的
0.0.0.0),则对于主机的每个网络接口地址,将通告一个具有相同类型和端口的定位器。 - 如果定位器的地址字段不是空地址,则将通告一个具有该地址的单一性定位器。
6.10.2 外部定位器
用户可以为每个单播定位器列表配置一组外部定位器:
WireProtocolConfigQos上的builtin.metatraffic_external_unicast_locatorsWireProtocolConfigQos上的default_external_unicast_locatorsRTPSEndpointQos上的external_unicast_locators
一个外部定位器由标准的定位器字段(kind、address 和 port)以及以下属性组成:
- 一个外部性 (
externality),表示从应用程序运行所在的主机到外部定位器所代表的 LAN 的跳数。 - 一个成本 (
cost),表示相对于同一外部性级别上其他定位器的通信成本。 - 一个掩码 (
mask),表示外部定位器所代表的 LAN 中有效位数。
6.10.2.1 外部性层级
外部定位器的主要目的是支持跨不同层级互联 LAN 的通信。通信将使用可用的最内层 LAN 的定位器进行。
作为一个例子,考虑一个网络拓扑结构,应用程序运行在一台连接到某个办公室 LAN 的主机上,该办公室 LAN 又连接到同一楼层所有办公室的 LAN,而楼层 LAN 又连接到整栋建筑的 LAN。
在默认配置下,通信将仅在办公室 LAN 上的主机之间进行。这被视为外部性级别 0,该级别保留给直接连接到应用程序运行所在主机的网络接口的 LAN。这是默认通告定位器的匹配算法中将使用的外部性级别。楼层 LAN 将被配置为外部性级别 1,而建筑 LAN 将被配置为外部性级别 2。
请注意,为了成功进行通信,很可能需要在不同的网络路由器上添加路由规则。
Important
外部性级别 0 由 Fast DDS 自动填充,应用程序无法配置。
6.10.2.2 匹配算法
当发现一个远程实体时,会处理其通告的定位器列表,以选择能够建立通信的最内层外部性级别上的定位器。首先检查最高的外部性级别。
如果某个级别的发现地址与本地实体通告的地址相同,则意味着它们在该级别上位于同一主机上,算法将进入更内层级别。如果发现地址与本地实体通告的地址不同,则处理将在当前级别停止。
当确定了将用于建立通信的外部性级别后,算法将:
- 移除与任何其他外部性级别上的地址匹配的定位器。
- 保留与所选外部性级别匹配的定位器。
- 对于地址与本地实体通告的任何定位器都不匹配的定位器:
- 如果
ignore_non_matching_locators为false(默认行为),则保留它们。 - 如果
ignore_non_matching_locators为true,则移除它们。
- 如果
6.10.2.3 其他注意事项
由于使用外部定位器会增加通告的定位器数量,因此需要根据您的应用程序调整定位器发现的分配限制。
在同一主机上运行,但在其 builtin.metatraffic_external_unicast_locators 中使用不同地址的参与者,将丢弃共享内存传输的定位器。数据共享通信不受此限制的影响。
6.11 接口白名单
使用 Fast DDS,可以限制 TCP 传输和 UDP 传输使用的网络接口。这是通过将接口添加到 TCPTransportDescriptor 或 UDPTransportDescriptor 的 interfaceWhiteList 字段来实现的。因此,其 TransportDescriptorInterface 定义了 interfaceWhiteList 的 DomainParticipant 所使用的通信接口被限制为该列表中定义的接口地址,从而避免了使用系统中其余可用的网络接口。interfaceWhiteList 中的接口可以通过 IP 地址或接口名称来指定。例如:
-
使用 IP 地址填充的接口白名单:
DomainParticipantQos qos;
// Create a descriptor for the new transport.
auto tcp_transport = std::make_shared(); // Add loopback to the whitelist by IP address
tcp_transport->interfaceWhiteList.emplace_back("127.0.0.1");// Link the Transport Layer to the Participant.
qos.transport().user_transports.push_back(tcp_transport);// Avoid using the builtin transports
qos.transport().use_builtin_transports = false; -
使用接口名称填充的接口白名单:
DomainParticipantQos qos;
// Create a descriptor for the new transport.
auto tcp_transport = std::make_shared(); // Add loopback to the whitelist by interface name
tcp_transport->interfaceWhiteList.emplace_back("lo");// Link the Transport Layer to the Participant.
qos.transport().user_transports.push_back(tcp_transport);// Avoid using the builtin transports
qos.transport().use_builtin_transports = false;
Important
如果传输描述符的白名单中的值与主机上的接口都不匹配,那么白名单中的所有接口都会被过滤掉,因此将无法通过该传输建立通信。
Warning
接口白名单功能适用于网络接口。因此,它仅在 TCP 传输和 UDP 传输上可用。
6.12 接口配置
默认情况下,Fast DDS 会使用系统中找到的所有活动网络接口进行通信(注意:适用于 UDP 传输和 TCP 传输)。但是,用户可以指定库使用的一组特定网络接口,和/或以特定方式配置其中一些接口。
6.12.1 网络掩码过滤
Fast DDS 的标准行为是尝试向任何注册了兼容传输(基于类型)的远程定位器发送数据。这可能会导致非最佳的资源利用,因为在特定的网络架构下,消息可能会从一个接口发送到一个不可达的目的地。在这种情况下,用户可以决定启用网络掩码过滤功能,该功能通过仅允许网络接口向同一子网内的远程定位器发送数据来防止此行为。
此配置选项可以在参与者、传输和接口级别设置,其可能的值为:
| 值 | 描述 |
|---|---|
ON |
启用网络掩码过滤。 |
OFF |
禁用网络掩码过滤。 |
AUTO |
使用容器的网络掩码过滤器配置。 |
AUTO 网络掩码过滤器配置意味着其有效值将由它的"容器"给出,对于允许列表条目而言,容器是包含它的传输描述符;对于传输描述符而言,容器是注册它的参与者。
然而,并非所有配置都有效;例如,如果注册此传输的参与者的网络掩码过滤器配置为 ON,则该传输的配置不能为 OFF。同样,如果定义此允许列表的传输描述符的配置为 OFF,则允许列表条目的网络掩码过滤器配置不能为 ON。
Note
由于实现细节,当在参与者或传输级别启用网络掩码过滤功能而没有定义允许列表或阻止列表时,需要在参与者和端点中将
ignore_non_matching_locators设置为true(参见《匹配算法》)。
当网络掩码过滤与外部定位器结合使用时,需要考虑额外的因素。特别是,当为参与者或端点定义了一组本地外部定位器(外部性大于 0)时,无法在允许列表的所有条目中启用网络掩码过滤。原因是,匹配的远程外部定位器随后(很可能)会被有效忽略,因为根据其网络掩码,没有网络接口能够到达它。
网络掩码过滤可以通过 C++ API 或 XML 配置在参与者级别启用:
// Configure netmask filtering at participant level
qos.transport().netmask_filter = NetmaskFilterKind::AUTO;
qos.wire_protocol().ignore_non_matching_locators = true; // Required if not defining an allowlist or blocklist
对于套接字(UDP/TCP)传输描述符:
// Create a descriptor for the new transport.
auto udp_transport = std::make_shared<UDPv4TransportDescriptor>();
// Configure netmask filtering at transport level
udp_transport->netmask_filter = NetmaskFilterKind::AUTO;
qos.wire_protocol().ignore_non_matching_locators = true; // Required if not defining an allowlist or blocklist
请参阅《允许列表》,了解如何为特定网络设备配置网络掩码过滤。
6.12.2 允许列表 (Allowlist)
使用 Fast DDS,可以限制 TCP 传输和 UDP 传输使用的网络接口。这是通过将接口添加到 TCPTransportDescriptor 或 UDPTransportDescriptor 的 allowlist 字段来实现的。因此,其 TransportDescriptorInterface 定义了 allowlist 的 DomainParticipant 所使用的通信接口被限制为该列表中定义的接口,从而避免了使用系统中其余可用的网络接口。allowlist 中的接口可以通过 IP 地址或接口名称来指定。此外,添加到 allowlist 的每个条目都可以指定一个网络掩码过滤器配置值(默认为 AUTO)。
例如:
DomainParticipantQos qos;
// Create a descriptor for the new transport.
auto udp_transport = std::make_shared<UDPv4TransportDescriptor>();
// Add allowed interface by device name
udp_transport->interface_allowlist.emplace_back("eth0", NetmaskFilterKind::OFF);
// Add allowed interface by IP address (using default netmask filter AUTO)
udp_transport->interface_allowlist.emplace_back("127.0.0.1");
// Add allowed interface with explicit AllowedNetworkInterface construction
AllowedNetworkInterface another_allowed_interface("docker0", NetmaskFilterKind::OFF);
udp_transport->interface_allowlist.emplace_back(another_allowed_interface);
// Link the Transport Layer to the Participant.
qos.transport().user_transports.push_back(udp_transport);
// Avoid using the builtin transports
qos.transport().use_builtin_transports = false;
Important
如果传输描述符的
allowlist中的值与主机上的接口都不匹配,那么allowlist中的所有接口都会被过滤掉,因此将无法通过该传输建立通信。
6.12.3 阻止列表 (Blocklist)
除了定义允许的接口列表之外,还可以定义应阻止的接口列表。这通过 TCPTransportDescriptor 或 UDPTransportDescriptor 中的 blocklist 字段来实现。
Note
此列表优先于
allowlist,因此,如果一个网络接口同时存在于两个列表中,它将被阻止。
blocklist 中的接口可以通过 IP 地址或接口名称来指定。
例如:
DomainParticipantQos qos;
// Create a descriptor for the new transport.
auto udp_transport = std::make_shared<UDPv4TransportDescriptor>();
// Add blocked interface by device name
udp_transport->interface_blocklist.emplace_back("docker0");
// Add blocked interface by IP address
udp_transport->interface_blocklist.emplace_back("127.0.0.1");
// Add blocked interface with explicit BlockedNetworkInterface construction
BlockedNetworkInterface another_blocked_interface("eth0");
udp_transport->interface_blocklist.emplace_back(another_blocked_interface);
// Link the Transport Layer to the Participant.
qos.transport().user_transports.push_back(udp_transport);
// Avoid using the builtin transports
qos.transport().use_builtin_transports = false;
6.13 禁用所有组播流量
如果所有对等点都是预先已知的,并且已经在初始对等点列表中配置,那么可以完全禁用所有组播流量。
通过定义自定义的元流量单播定位器,本地 DomainParticipant 会为列表中指定的每个地址-端口对创建一个单播元流量接收资源,从而避免创建默认的元流量组播和单播定位器。这将阻止 DomainParticipant 监听来自组播源的任何发现数据。
应考虑 metatrafficUnicastLocatorList 中端口的分配,避免分配不可用或与发布者参与者初始对等点列表中列出的地址-端口不匹配的端口。
以下是配置一个元流量单播定位器以禁用所有组播流量的示例。
DomainParticipantQos qos;
// Metatraffic Multicast Locator List will be empty.
// Metatraffic Unicast Locator List will contain one locator, with null address and null port.
// Then Fast DDS will use all network interfaces to receive network messages using a well-known port.
Locator_t default_unicast_locator;
qos.wire_protocol().builtin.metatrafficUnicastLocatorList.push_back(default_unicast_locator);
// Initial peer will be UDPv4 address 192.168.0.1. The port will be a well-known port.
// Initial discovery network messages will be sent to this UDPv4 address.
Locator_t initial_peer;
IPLocator::setIPv4(initial_peer, 192, 168, 0, 1);
qos.wire_protocol().builtin.initialPeersList.push_back(initial_peer);