6.4 共享内存传输
共享内存(SHM)传输依靠主机操作系统提供的共享内存机制,实现了在同一处理单元/机器上运行的实体之间的快速通信。
注意
Fast DDS 利用域参与者(DomainParticipant)的
GuidPrefix_t
来识别在同一主机上运行的对等方。GuidPrefix_t
的前 4 个字节相同的两个参与者被视为在同一主机上运行。提供了is_on_same_host_as()
API 来检查此条件。此外,还请考虑进程内传输的 GUID 前缀注意事项中包含的警告。
共享内存传输比 UDP / TCP 等其他网络传输提供更好的性能,即使这些传输使用环回接口也是如此。这主要是由于以下原因:
- 大型消息支持:网络协议需要对数据进行分片,以符合特定协议和网络栈的要求,从而增加了通信开销。共享内存传输允许复制完整消息,其唯一的大小限制是机器的内存容量。
- 减少内存复制次数:当向不同的端点发送相同的消息时,共享内存传输可以直接与所有目标端点共享相同的内存缓冲区。其他协议需要为每个端点执行一次消息复制。
- 更少的操作系统开销:初始设置完成后,共享内存传输所需的系统调用比其他协议少得多。因此,使用共享内存可以提高性能/减少时间消耗。
6.4.1 概念定义
本节描述基本概念,以帮助解释共享内存传输如何将数据消息传递到适当的域参与者。其目的不是成为实现的详尽参考,而是对每个概念进行全面解释,以便用户可以根据自己的需求配置传输。
本节中的许多描述将按照下图所示的示例用例进行,其中参与者 1 向参与者 2 发送数据消息。请在理解定义时参考该图。
共享内存传输的序列图
6.4.1.1 段(Segment)
段是可以从不同进程访问的一块共享内存。每个配置了共享内存传输的域参与者都会创建一个共享内存段。域参与者将需要传递给其他域参与者的任何数据写入此段,远程域参与者能够使用共享内存机制直接读取这些数据。
注意
使用具有更高权限的用户(例如 root)启动任何进程都可能导致通信问题,因为非特权用户运行的进程可能无法写入内存段。
每个段都有一个 segmentId,这是一个 16 字符的 UUID,用于唯一标识每个共享内存段。这些 segmentId 用于识别和访问每个域参与者的段。
6.4.1.2 段缓冲区(Segment Buffer)
在共享内存段中分配的缓冲区。它充当放置在段中的 DDS 消息的容器。换句话说,域参与者在段上写入的每条消息都将放在不同的缓冲区中。
6.4.1.3 缓冲区描述符(Buffer Descriptor)
它充当指向特定段中特定段缓冲区的指针。它包含 segmentId 和段缓冲区相对于段基址的偏移量。在与其他域参与者通信消息时,共享内存传输仅分发缓冲区描述符,避免了消息从一个域参与者复制到另一个域参与者。通过此描述符,接收域参与者可以访问写入缓冲区中的消息,因为它唯一地标识了段(通过 segmentId)和段缓冲区(通过其偏移量)。
6.4.1.4 端口(Port)
表示用于通信缓冲区描述符的通道。它在共享内存中实现为环形缓冲区,因此任何域参与者都可以潜在地在其上读取或写入信息。每个端口都有一个唯一的标识符,一个 32 位数字,可用于引用该端口。每个配置了共享内存传输的域参与者都会创建一个端口来接收缓冲区描述符。此端口的标识符在发现期间共享,以便远程对等方知道要与每个域参与者通信时使用哪个端口。
域参与者为其接收端口创建一个侦听器,以便在新的缓冲区描述符被推送到该端口时得到通知。
6.4.1.5 端口健康检查(Port Health Check)
每次域参与者打开一个端口(用于读取或写入)时,都会执行健康检查以评估其正确性。原因是如果涉及的某个进程在使用端口时崩溃,该端口可能会变得无法使用。如果附加的侦听器在给定的超时时间内没有响应,则该端口被视为已损坏,并将其销毁后重新创建。
6.4.2 SharedMemTransportDescriptor
除了在 TransportDescriptorInterface 中定义的数据成员外,共享内存的传输描述符还定义了以下成员:
成员 | 数据类型 | 默认值 | 访问器 / 修改器 | 描述 |
---|---|---|---|---|
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() |
共享内存转储线程的 ThreadSettings。 |
如果 rtps_dump_file_
不为空,则域参与者上的所有共享内存流量(发送和接收的)都会被跟踪到一个文件中。输出文件格式是 tcpdump 十六进制文本,可以使用 Wireshark 等协议分析器应用程序进行处理。具体来说,要使用 Wireshark 打开该文件,请使用"从十六进制转储导入"选项,并使用"原始 IPv4"封装类型。
注意
SharedMemTransportDescriptor
的kind
值由LOCATOR_KIND_SHM
给出。
警告将
segment_size()
设置为接近或小于数据大小会带来很高的数据丢失风险,因为在单次发送操作期间,写入操作会覆盖缓冲区。
6.4.3 启用共享内存传输
Fast DDS 默认启用共享内存传输。不过,应用程序可以根据需要启用其他共享内存传输。要在域参与者中启用新的共享内存传输,首先创建一个 SharedMemTransportDescriptor 的实例,并将其添加到域参与者的用户传输列表中。
下面的示例展示了在 C++ 代码和 XML 文件中的实现过程。
C++
cpp
DomainParticipantQos qos;
// 创建新传输的描述符。
std::shared_ptr<SharedMemTransportDescriptor> shm_transport =
std::make_shared<SharedMemTransportDescriptor>();
// [可选] 线程设置配置
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});
// 将传输层链接到参与者。
qos.transport().user_transports.push_back(shm_transport);
XML
xml
<?xml version="1.0" encoding="UTF-8" ?>
<dds>
<profiles xmlns="http://www.eprosima.com">
<transport_descriptors>
<!-- 创建新传输的描述符 -->
<transport_descriptor>
<transport_id>shm_transport</transport_id>
<type>SHM</type>
<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>
<dump_thread> <!-- 可选 -->
<scheduling_policy>-1</scheduling_policy>
<priority>0</priority>
<affinity>0</affinity>
<stack_size>-1</stack_size>
</dump_thread>
</transport_descriptor>
</transport_descriptors>
<participant profile_name="SHMParticipant">
<rtps>
<!-- 将传输层链接到参与者 -->
<userTransports>
<transport_id>shm_transport</transport_id>
</userTransports>
</rtps>
</participant>
</profiles>
</dds>
注意
如果启用了多种传输,发现流量始终使用 UDP/TCP 传输,即使在同一台机器上运行的两个参与者都启用了共享内存传输也是如此。如果一个或多个参与者仅启用了共享内存,而其他参与者同时使用其他传输,则可能导致发现问题。此外,当同一台机器上的两个参与者启用了共享内存传输时,它们之间的用户数据通信会自动仅通过共享内存传输执行。这些两个参与者之间不会使用其余已启用的传输。
提示要通过共享内存配置发现流量,必须禁用默认的内置传输。这样,通信就完全使用共享内存执行。下面的代码片段示例展示了在 C++ 代码和 XML 文件中的实现过程。完整示例请参见传输机制示例。
C++
cpp
DomainParticipantQos qos;
// 创建新传输的描述符。
std::shared_ptr<SharedMemTransportDescriptor> shm_transport =
std::make_shared<SharedMemTransportDescriptor>();
// 将传输层链接到参与者。
qos.transport().user_transports.push_back(shm_transport);
// 显式配置共享内存传输
qos.transport().use_builtin_transports = false;
XML
xml
<?xml version="1.0" encoding="UTF-8" ?>
<profiles xmlns="http://www.eprosima.com">
<transport_descriptors>
<!-- 创建新传输的描述符 -->
<transport_descriptor>
<transport_id>shm_transport_only</transport_id>
<type>SHM</type>
</transport_descriptor>
</transport_descriptors>
<participant profile_name="DisableBuiltinTransportsParticipant">
<rtps>
<!-- 将传输层链接到参与者 -->
<userTransports>
<transport_id>shm_transport_only</transport_id>
</userTransports>
<useBuiltinTransports>false</useBuiltinTransports>
</rtps>
</participant>
</profiles>
6.4.4 传输机制示例
在 delivery_mechanisms 文件夹中可以找到一个适用于受支持传输机制的"hello world"示例。它展示了通过所需传输机制(可以仅设置为共享内存)进行通信的发布者和订阅者。
本页内容
6.4.1. 概念定义
6.4.1.1. 段
6.4.1.2. 段缓冲区
6.4.1.3. 缓冲区描述符
6.4.1.4. 端口
6.4.2. SharedMemTransportDescriptor
6.4.3. 启用共享内存传输
6.4.4. 传输机制示例