來自:https://eprosima-fast-rtps.readthedocs.io/en/latest/advanced.html
參考《FastRTPS User Manual.odt》第六章第6.5節 版本1.5
網絡配置
FastRTPS支持多種傳輸層接口(插件架構),也可以開發符合FastRTPS的第三方傳輸層,因此高級用戶可以根據項目需要來自己設計。當前版本實現了UDPv4(默認)、UDPv6、TCPv4(1.5版本中沒有)、TCPv6(1.5版本中沒有),並在未來版本規劃了共享內存等傳輸方式。
當用戶沒有定義 Input / Output通道時,FastRTPS會內置網絡配置,使用IPv4協議,通過所有可用接口監聽發送,發送端口爲10040。
rtps.userTransports 自定義傳輸(比如發送、接收緩衝區大小)。
rtps.useBuiltinTransports false禁用內置傳輸
自定義UDPv4傳輸:
//Create a udpv4 descriptor for the new transport.
auto custom_transport = std::make_shared<UDPv4TransportDescriptor>();
custom_transport->sendBufferSize = 9216;
custom_transport->receiveBufferSize = 9216;
//Disable the built-in Transport Layer.
participant_attr.rtps.useBuiltinTransports = false;
//Link the Transport Layer to the Participant.
participant_attr.rtps.userTransports.push_back(custom_transport);
自定義TCPv4傳輸(1.5版本不支持)
//Create a descriptor for the new transport.
auto tcp_transport = std::make_shared<TCPv4TransportDescriptor>();
tcp_transport->add_listener_port(5100);
//Set initial peers. remote listening port
Locator_t initial_peer_locator;
initial_peer_locator.kind = LOCATOR_KIND_TCPv4;
IPLocator::setIPv4(initial_peer_locator, "192.168.1.55");
initial_peer_locator.port = 5100;
participant_attr.rtps.builtin.initialPeersList.push_back(initial_peer_locator);
//Disable the built-in Transport Layer.
participant_attr.rtps.useBuiltinTransports = false;
//Link the Transport Layer to the Participant.
participant_attr.rtps.userTransports.push_back(tcp_transport);
本地偵聽端口5100,連接到遠程地址192.168.1.55:5100
IPLocator(1.5版本不支持, 略)
Locator_t locator;
// Get & Set Physical Port
uint16_t physical_port = IPLocator::getPhysicalPort(locator);
IPLocator::setPhysicalPort(locator, 5555);
// Get & Set Logical Port
uint16_t logical_port = IPLocator::getLogicalPort(locator);
IPLocator::setLogicalPort(locator, 7400);
// Set WAN Address
IPLocator::setWan(locator, "80.88.75.55");
通道適應性:
創建Participant的時候,管理 Input / Output通道的系資源會被保留下來。由於各種原因,可能無法打開管理指定通道的系統資源。所以FastRTPS使用了通道適應機制來確保在創建Participant的時候有一個最小的通道可用集,當指定的通道不可用時就返回特徵一致的通道。創建了新通道時,ParticipantAttribute也會被更新。也就是說,FastRTPS會始終維護有效的內置通道。
用戶定義的Writer / Reader,並沒有適應機制,也就意味着配置不正確時會導致申請資源失敗。也就是說,用戶需要確保設置的通道是有效的。
偵聽定位器:Locator_t
元組播定位器(metatraffic multicast):用於接收元組播數據,被用於內置端點,比如發現內置端點。
元單播定位器(metatraffic unicast):用於接收元單播數據,被用於內置端點,比如發現內置端點。
用戶組播定位器(user multicast):用於接收用戶組播數據,被用於用戶端點。
用戶單播定位器(user unicast):用於接收用戶單播數據,被用於用戶端點。
rtps.builtin.metatrafficMulticastLocatorList 自定義元組播定位器
rtps.builtin.metatrafficUnicastLocatorList 自定義元單播定位器
rtps.defaultMulticastLocatorList 自定義用戶組播定位器
rtps.defaultUnicastLocatorList 自定義用戶單播定位器
示例1:
eprosima::fastrtps::ParticipantAttributes part_attr;
// This locator will open a socket to listen network messages on UDPv4 port 22222 over multicast address 239.255.0.1
eprosima::fastrtps::rtps::Locator_t locator.set_IP4_address(239, 255, 0 , 1);
locator.port = 22222;
part_attr.rtps.builtin.metatrafficMulticastLocatorList.push_back(locator);
接收239.255.0.1地址的多播消息,偵聽端口22222;
示例2:
eprosima::fastrtps::ParticipantAttributes part_attr;
// This locator will open a socket to listen network messages on UDPv4 port 22223 over network interface 192.168.0.1
eprosima::fastrtps::rtps::Locator_t locator.set_IP4_address(192, 168, 0 , 1);
locator.port = 22223;
part_attr.rtps.builtin.metatrafficUniicastLocatorList.push_back(locator);
接收發送到本機192.168.0.1上的單播消息,偵聽端口22223;
locator.IP4_address=null時,FastRTPS會主動獲取本地網絡地址。
locator.port=0時,FastRTPS會給UDPv4通信分配一個通用端口 ,分配計算規則:
Traffic type | Well-known port expression |
---|---|
Metatraffic multicast | PB + DG * domainId + offsetd0 |
Metatraffic unicast | PB + DG * domainId + offsetd1 + PG * participantId |
User multicast | PB + DG * domainId + offsetd2 |
User unicast | PB + DG * domainId + offsetd3 + PG * participantId |
DG--域ID增量(domainid gain),屬性:rtps.port.domainIDGain,默認值:250
PG--參與者ID增量(participantID gain),屬性:rtps.port.participantIDGain,默認值:2
PB--端口基數(port base number),屬性:rtps.port.portBase,默認值:7400
offset--偏移,屬性rtps.port.offsetd,默認值:offsetd0 = 0,offsetd1 = 10, offsetd2 = 1, offsetd3 = 11
發送定位器
用於創建發送消息的網絡端點。(例中,通過192.168.0.1:34000向網絡發送消息)
eprosima::fastrtps::ParticipantAttributes part_attr;
// This locator will create a socket to send network message on UDPv4 port 34000 over network interface 192.168.0.1
Locator_t locator.set_IP4_address(192.168.0.1);
locator.port = 34000;
part_attr.rtps.defaultOutLocatorList.push_back(locator);
locator.IP4_address=null時,FastRTPS會主動獲取本地網絡地址。
locator.port=0時,FastRTPS會給UDPv4通信分配一個通用端口 ,分配計算規則:
初始網絡發現定位器(Initial peers)
用於創建網絡發現的網絡端點。(例中,通過192.168.0.2:7600發送網絡發現消息,這個消息是週期性發送的)
默認情況下,FastRTPS也將這些定位器用作元組播定位器(Metatraffic Multicast Locators)
eprosima::fastrtps::ParticipantAttributes part_attr;
// This locator configures as initial peer the UDPv4 address 192.168.0.2:7600.
// Initial discovery network messages will send to this UDPv4 address.
Locator_t locator.set_IP4_address(192.168.0.2);
locator.port = 7600;
part_attr.rtps.builtin.initialPeersList.push_back(locator);
白名單
有時候用戶需要禁用某些網絡接口,防止通過這些接口進行連接或者發送數據。這時候可以設置 interfaceWhiteList屬性來設置要用的網絡接口。
UDPv4TransportDescriptor descriptor;
descriptor.interfaceWhiteList.emplace_back("127.0.0.1");
禁用多播
只設置單播定位器和初始網絡發現定位器。
eprosima::fastrtps::ParticipantAttributes part_attr;
// Metatraffic Multicast Locator List will be empty.
// Metatraffic Unicast Locator List will contain one locator, with null address and null port.
// Then eProsima Fast RTPS will use all network interfaces to receive network messages using a well-known port.
Locator_t default_unicast_locator;
participant_attr_.rtps.builtin.metatrafficUnicastLocatorList.push_back(default_unicast_locator);
// Initial peer will be UDPv4 addresss 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;
initial_peer.set_IP4_address(192, 168, 0, 1);
participant_attr_.rtps.builtin.initialPeersList.push_back(initial_peer);
流控制
參考 Publisher-Subscriber層、Writer-Reader層 中的流控制策略部分。
發送大數據
參考 Publisher-Subscriber層、Writer-Reader層 中的數據包拆分部分。
如何提高發送大數據的性能?
假設,發送一個9.9MB大小的文件,帶寬是100MB/s,緩衝區大小65kb(也就是說文件數據得被分成1100多個包)。
1. 設置爲異步(pubAttr.qos.m_publishMode / writerAttr.mode)
2. 可靠模式(pubAttr.qos.m_reliability.kind / writerAttr.endpoint.reliabilityKind):不允許數據分片丟失,如果是音頻或視頻文件,就可以用高效模式,因爲丟失一兩幀並不會有什麼影響。
3. 降低心跳週期(pubAttr.times.heartbeatPeriod / writerAttr.times.heartbeatPeriod):大量的數據分片會降低傳輸速度,降低心跳週期會增加網絡中消息的數量,但是同時,當數據分片丟失的時候,會加速系統響應。
4. 增加流控制(pubAttr.terminalThroughputController / writerAttr.throughputController):防止發送數據佔用所有的帶寬,比如限制在5MB/s
5. 增加UDP緩衝區大小:linux系統默認最大socket緩衝區,命令如下:
# temporary modify socket buffer size
sysctl -w net.ipv4.udp_mem="102400 873800 16777216"
sysctl -w net.core.netdev_max_backlog="30000"
sysctl -w net.core.rmem_max="16777216"
sysctl -w net.core.wmem_max="16777216"
# permanent modify socket buffer size
echo 'net.core.wmem_max=12582912' >> /etc/sysctl.conf
echo 'net.core.rmem_max=12582912' >> /etc/sysctl.conf
# get max socket buffer size
sudo sysctl -a | grep net.core.wmem_max
sudo sysctl -a | grep net.core.rmem_max
設置FastRTPS中的默認緩衝區大小:
participant_attr.rtps.sendSocketBufferSize = 1048576;
participant_attr.rtps.listenSocketBufferSize = 4194304;
發現
自動發現分爲兩個階段:Participant發現(PDP)、EndPoint發現(EDP)
PDP(Participant Discovery Phase):週期性的發送自己的信息,先發現Participant,再匹配EndPoint。
注意:發送網絡配置:Initial peers
EDP(EndPoint Discovery Phase):向遠程Participant 發送實體信息,並處理遠程Participant的實體信息,並檢查哪些實體可以匹配。這個階段也可以用不發送任何消息的靜態版本(直接從XML加載)實現,靜態發現適合於網絡帶寬有限且Publisher和Subscriber已知的情況。
默認啓用發現,禁用發現代碼:
participant_attr.rtps.builtin.use_SIMPLE_RTPSParticipantDiscoveryProtocol = false;
使用靜態發現:
ParticipantAttributes participant_attr;
participant_attr.rtps.builtin.use_SIMPLE_EndpointDiscoveryProtocol = false;
participant_attr.rtps.builtin.use_STATIC_EndpointDiscoveryProtocol = true;
participant_attr.rtps.builtin.setStaticEndpointXMLFilename("ParticipantWithASubscriber.xml");
ParticipantWithASubscriber.xml:例子中,會匹配遠程Participant中的一個Reader,這個Reader會接收本程序中的Writer發出的數據。
<staticdiscovery>
<participant>
<name>HelloWorldSubscriber</name>
<reader>
<userId>3</userId>
<entityId>4</userId>
<topicName>HelloWorldTopic</topicName>
<topicDataType>HelloWorld</topicDataType>
</reader>
</participant>
</staticdiscovery>
靜態發現XML中的 reader / writer 參數:
參數 | 類型 |
userId | numeric |
entityId | numeric |
expectsInlineQos | true / false (只有配置reader時有效) |
topicName | text |
topicDataType | text |
topicKind | NO_KEY / WITH_KEY |
reliabilityQos | BEST_EFFORT_RELIABILITY_QOS / RELIABLE_RELIABILITY_QOS |
unicastLocator.address | text |
unicastLocator.port | numeric |
multicastLocator.address | text |
multicastLocator.port | numeric |
durabilityQos |
VOLATILE_DURABILITY_QOS / TRANSIENT_LOCAL_DURABILITY_QOS / TRANSIENT_DURABILITY_QOS |
ownershipQos.kind | SHARED_OWNERSHIP_QOS / EXCLUSIVE_OWNERSHIP_QOS |
partitionQos | text |
livelinessQos.kind | AUTOMATIC_LIVELINESS_QOS / MANUAL_BY_PARTICIPANT_LIVELINESS_QOS / MANUAL_BY_TOPIC_LIVELINESS_QOS |
livelinessQos.leaseDuration_ms | numeric |
訂閱發現消息
Participant在發現的過程中會發送一些元數據。用戶可以定義ParticipantListener來處理髮現消息。內置協議處理完消息後就會調用ParticipantListener中的回調函數。
class CustomParticipantListener : public eprosima::fastrtps::ParticipantListener
{
/* Custom Listener onSubscriberDiscovery */
void onSubscriberDiscovery(
eprosima::fastrtps::Participant * participant,
eprosima::fastrtps::rtps::ReaderDiscoveryInfo && info) override
{
(void)participant;
switch(info.status) {
case eprosima::fastrtps::rtps::ReaderDiscoveryInfo::DISCOVERED_READER:
/* Process the case when a new subscriber was found in the domain */
cout << "New subscriber for topic '" << info.info.topicName() << "' of type '" << info.info.typeName() << "' discovered";
break;
case eprosima::fastrtps::rtps::ReaderDiscoveryInfo::CHANGED_QOS_READER:
/* Process the case when a subscriber changed its QOS */
break;
case eprosima::fastrtps::rtps::ReaderDiscoveryInfo::REMOVED_READER:
/* Process the case when a subscriber was removed from the domain */
cout << "Subscriber for topic '" << info.info.topicName() << "' of type '" << info.info.typeName() << "' left the domain.";
break;
}
}
/* Custom Listener onPublisherDiscovery */
void onPublisherDiscovery(
eprosima::fastrtps::Participant * participant,
eprosima::fastrtps::rtps::WriterDiscoveryInfo && info) override
{
(void)participant;
switch(info.status) {
case eprosima::fastrtps::rtps::WriterDiscoveryInfo ::DISCOVERED_WRITER:
/* Process the case when a new publisher was found in the domain */
cout << "New publisher for topic '" << info.info.topicName() << "' of type '" << info.info.typeName() << "' discovered";
break;
case eprosima::fastrtps::rtps::WriterDiscoveryInfo ::CHANGED_QOS_WRITER:
/* Process the case when a publisher changed its QOS */
break;
case eprosima::fastrtps::rtps::WriterDiscoveryInfo ::REMOVED_WRITER:
/* Process the case when a publisher was removed from the domain */
cout << "publisher for topic '" << info.info.topicName() << "' of type '" << info.info.typeName() << "' left the domain.";
break;
}
}
};
// Create Custom user ParticipantListener (should inherit from eprosima::fastrtps::ParticipantListener.
CustomParticipantListener *listener = new CustomParticipantListener();
// Pass the listener on participant creation.
Participant* participant = Domain::createParticipant(participant_attr, listener);
調優
1)利用多播。如果一個Topic有多個Subscriber,那麼選用多播是比較合適的,這樣,每個數據只發送一個網絡包,相較於單播,提高了CPU和網絡的使用率。
2)增大Socket緩衝區大小:在高速率、大數據的場景中,由於Socket緩衝區不足,可能會丟棄部分數據包。在可靠模式下,FastRTPS會試圖恢復這些丟失的數據包,這是有重傳代價的,而在高效模式下,這些丟失的數據包就徹底的丟失了。增加緩衝區大小從兩個地方增加:參考“如何提高發送大數據的性能?”第5步。
3)可靠模式優化:參考“如何提高發送大數據的性能?”第3步。
4)非嚴格可靠(topic.historyQos.kind):KEEP_ALL會讓所有的Subscriber必須接收所有的實例數據,在大量實例數據丟失的情況下,這會嚴重降低性能。如果用戶不需要這種嚴格性,可設置爲KEEP_LAST。
5)降低發送頻率:高頻率的發送數據會造成數據丟失。參考“如何提高發送大數據的性能?”第4步。