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++
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 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++
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 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. 傳輸機制示例