来自: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();
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();
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向网络发送消息)
注:在FastRTPS1.9中为remoteLocatorList
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通信分配一个通用端口 ,分配计算规则:
用于创建网络发现的网络端点。(例中,向192.168.0.2:7600发送自己的PDP消息,这个消息是周期性发送的,默认250s)
创建Participant时没有指定intial peers时,FastRTPS会将元多播定位器(Metatraffic Multicast Locators)作为网络发现的定位器,Participant会向这些地址发送自己的信息。
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发出的数据。
HelloWorldSubscriber
3
4
HelloWorldTopic
HelloWorld
静态发现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步。