来自:https://eprosima-fast-rtps.readthedocs.io/en/latest/pubsub.html
参考《FastRTPS User Manual.odt》第五章
eProsima Fast RTPS 提供了高层的Publisher-Subscriber层,该层是RTPS协议上的简单抽象。通过这层,你可以直接编写程序代码而不用管低层RTPS配置,因为这是由库自己管理的。(底层RTPS有很多配置选项,可以改善此层的应用效果)
Domain
创建Participant、Subscriber、Publisher的管理类,以及注册在网络上使用的数据类型和话题。
自定义话题类型例子:https://blog.csdn.net/JL_Gao/article/details/85685086
Participant
管理Publisher和Subscriber的分组类,保存了Publisher、Subscriber、TopicDataType等数据。
ParticipantAttributes
创建Participant配置选项,有一些参数是必须指定的:DomainId 和 Discovery
DomainId:用于计算PDP发现端口,非常适用于同一个网络中有多个应用程序的情况,这样就能将网络中的程序分离。对于同一个计算机上同样的DomainId的Participant来说,发送接收PDP消息使用的多播端口是一样的,单播端口与ParticipantID有关,所以每个Participant用到的单播端口是不一样的。对于网络中不同的计算机来说,只要Participant的DomainId一样,就可以通过同一个多播地址来发现彼此,DomainId不同的Participant,他们发送接收PDP消息的多播地址端口不一样,也就不会彼此发现了。
Discovery:如果使用静态发现(SEDP),需要提供相应的XML端点配置文件。
创建Participant的例子:
ParticipantAttributes PParam;
Pparam.rtps.setName("participant");
Pparam.rtps.builtin.domainId = 80;
Pparam.rtps.listenSocketBufferSize = 100000;
Participant* p = Domain::createParticipant(PParam);
if(p!=nullptr){
//Participant correctly created
}
//To remove:
Domain::removeParticipant(p);
从Participant修改RTPS底层网络配置:
ParticipantAttributes Pparams;
auto myTransport = std::make_shared();
myTransport->receiveBufferSize = 65536;
myTransport->granularMode = false;
Pparams.rtps.useBuiltinTransport = false;
Pparams.rtps.userTransports.push_back(myTransport);
Publisher
用于向网络中的Subscriber发送数据。用户通过Publisher发送数据,也可以通过PublisherListener处理一些事件。
自定义Publisher例子:https://blog.csdn.net/JL_Gao/article/details/85694276
Domain创建Publisher的验证过程:
1. 如果Participant使用的SEDP静态端点发现协议,则必须定义userDefinedId,且>0
2. 使用的数据类型必须提前注册
3. 如果topic类型是WITH_KEY,注册的数据类型必须实现getKey方法。
数据包拆分:
发送缓冲区大小的参数是从Participant继承的,默认为65KB。当RTPS消息很大不能一次发送时,该消息将会被分割。分割后的消息会通过多个数据包发送,此时需将 qos.m_publishMode 设置为异步。
在高效模式下,数据包的大小正确就意味着数据包被正确接收了。
// Allows fragmentation.
publisher_attr.qos.m_publishMode.kind = ASYNCHRONOUS_PUBLISH_MODE;
流控制策略:
用户可配置流控制策略,用来限制在特定条件下发送的数据量(可用不同的流控制器类型实现)
启用内置吞吐量控制器:
// This controller allows 300kb per second.
ThroughputControllerDescriptor slowPublisherThroughputController{300000, 1000};
PublisherAttributes WparamSlow;
WparamSlow.terminalThroughputController = slowPublisherThroughputController;
mp_slow_publisher = Domain::createPublisher(mp_participant,WparamSlow,(PublisherListener*)&m_listener);
PublisherListener
实现的方法应避免耗时循环和阻塞语句,以防引起事件或侦听线程阻塞。
Subscriber
自定义Subscriber例子:https://blog.csdn.net/JL_Gao/article/details/85694276
数据包拆分支持:能够接收单个重量级消息的多个数据包
1. 利用Writer的流控制,使Reader能够跟上发送的带宽
2. 利用可靠QoS,当Reader无法跟上发送带宽时,让丢失的分片重新发送。
FastRTPS对标准的QoS提供了部分本地支持,此外,不能通过API访问的QoS类型可以在用户端实现。
SampleInfo_t:从History中读取或获取数据时提供的辅助结构,用在takeNextData 和 readNextData
sampleKind:ALIVE(活动的)、DISPOSED(已处理的)、UNREGISTERED(未注册的)
ownershipStrength:接收到数据时,Writer的所有权强度
sourceTimestamp:数据发送的时间戳,可用于实现基于时间的过滤
sample_identity:Writer的GUID、数据的序号
MatchingInfo:两个端点间的匹配信息,用在onPublicationMatched 和 onSubscriptionMatched
remoteEndpointGuid:匹配的Writer或Reader的GUID
status:MATCHED_MATCHING、REMOVED_MATCHING
基于时间的过滤和基于内容的过滤Qos例子:https://blog.csdn.net/JL_Gao/article/details/85700559
所有权Qos例子:https://blog.csdn.net/JL_Gao/article/details/85700559
期限和所有权强度QoS例子:参考源码中example
第一步:创建Participant对象,相当于我们程序中用到的Publisher与Subscriber的容器。
ParticipantAttributes participant_attr; //Configuration structure
Participant *participant = Domain::createParticipant(participant_attr);
Domain -- 创建Participant、Subscriber、Publisher的管理类,以及注册在网络上使用的数据类型和话题。
Participant -- 管理Publisher和Subscriber的分组类,保存了Publisher、Subscriber、TopicDataType等数据。
ParticipantAttributes -- Participant配置,上面代码中采用的默认配置。默认配置提供了一些基本选项包括通信用到的端口。
第二步:注册Topic
HelloWorldPubSubType m_type; //Auto-generated type from FastRTPSGen
Domain::registerType(participant, &m_type);
HelloWorldPubSubType -- 由fastrtpsgen生成的类
第三步:创建Publisher对象
PublisherAttributes publisher_attr; //Configuration structure
PubListener m_listener; //Class that implements callbacks from the publisher
Publisher *publisher = Domain::createPublisher(participant, publisher_attr, (PublisherListener *)&m_listener);
PubListener -- 实现回调函数
Publisher 有一组可选的回调函数,它们会中事件发生时触发,例如当Subscriber开始侦听同一个Topic。可通过继承PublisherListener实现对不同事件的处理。
class PubListener : public PublisherListener
{
public PubListener(){};
~PubListener(){};
void onPublicationmatched(Publisher* pub, MatchingInfo& info)
{
//Callback implementation. This is called each time the Publisher finds a Subscriber on the network that listens to the same topic.
}
} m_listener;
第四步:发布数据
HelloWorld m_Hello; //Auto-generated container class for topic data from FastRTPSGen
m_Hello.msg("Hello there!"); // Add contents to the message
publisher->write((void *)&m_Hello); //Publish
第五步:创建Subscriber对象
SubscriberAttributes subscriber_attr; //Configuration structure
SubListener m_listener; //Class that implements callbacks from the Subscriber
Subscriber *subscriber = Domain::createSubscriber(participant,subscriber_attr,(SubsciberListener*)&m_listener);
SubListener类 -- 实现回调函数(实现同PubListener)
通过参数sendSocketBufferSize和listenSocketBufferSize可配置底层UDP套接字的发送和接收缓冲区大小。因为当要发送的数据类型大于底层发送缓冲区时,FastRTPS会将数据拆分成多个数据碎片,并在接收端重新组合构建。
Participant 配置
代码方式:
ParticipantAttributes participant_attr;
participant_attr.setName("my_participant");
participant_attr.rtps.builtin.domainId = 80;
Participant *participant = Domain::createParticipant(participant_attr);
XML文件方式:
Participant *participant = Domain::createParticipant("participant_xml_profile");
my_participant
80
配置项:
setName:设置Participant Name,这是RTPS协议元数据中一部分。
rtps.builtin.domainId:域ID,一般用于区分不同应用。
Publisher和Subscriber 配置
代码方式:
PublisherAttributes publisher_attr;
publisher_attr.topic.topicDataType = "HelloWorldType";
publisher_attr.topic.topicName = "HelloWorldTopic";
publisher_attr.qos.m_reliability.kind = RELIABLE_RELIABILITY_QOS;
publisher_attr.topic.historyQos.kind =KEEP_LAST_HISTORY_QOS;
publisher_attr.topic.historyQos.depth = 5
publisher_attr.qos.m_durability.kind =TRANSIENT_LOCAL_DURABILITY_QOS;
publisher_attr.topic.resourceLimitsQos.max_samples = 200;
Locator_t unicast_locator;
unicast_locator.port = 7800;
publisher_attr.unicastLocatorList.push_back(unicast_locator);
Locator_t multicast_locator;
multicast_locator.set_IP4_address("239.255.0.4");
multicast_locator.port = 7900;
publisher_attr.multicastLocatorList.push_back(multicast_locator);
Publisher *publisher = Domain::createPublisher(participant, publisher_attr);
SubscriberAttributes subscriber_attr;
...
Subscriber *subscriber = Domain::createSubscriber(participant, subscriber_attr);
XML方式:
Publisher *publisher = Domain::createPublisher(participant, "publisher_xml_profile");
Subscriber *subscriber = Domain::createSubscriber(participant, "subscriber_xml_profile");
HelloWorldType
HelloWorldTopic
KEEP_LAST
5
200
RELIABLE
TRANSIENT_LOCAL
7800
239.255.0.4
7900
...
配置项(蓝色字体是代码中的配置值,蓝色字体后面的是XML文件中的配置值):
topic.topicDataType:Topic数据类型,
topic.topicName:Topic名称,
topic.topicKind:WITH_KEY、NO_KEY(默认)
topic.resourceLimitsQos.max_samples:History的最大存储大小
topic.resourceLimitsQos.max_instance:(Subscriber) 最多接收多少个关键字的数据
topic.resourceLimitsQos.max_samples_per_instance:(Subscriber)每个关键字的能接收的最大大小
注意:max_samples 必须大于max_samples_per_instance
topic.historyQos:RTPS底层History元素配置参数访问点
topic.historyQos.kind:缓存策略,KEEP_ALL、KEEP_LAST(默认)
KEEP_ALL_HISTORY_QOS(KEEP_ALL)-- 保存所有的Change数据
KEEP_LAST_HISTORY_QOS(KEEP_LAST) -- 当数据条数大于depth时,保存最新的Change数据,并覆盖旧数据
qos.m_publishMode:ASYNCHRONOUS_PUBLISH_MODE
qos.m_ownership.kind:EXCLUSIVE_OWNERSHIP_QOS(只能有一个Writer能更新实例,该Writer拥有最大所有权)
qos.m_reliability.kind:可靠性,默认BEST_EFFORT
BEST_EFFORT_RELIABILITY_QOS (BEST_EFFORT)-- 不需要接收者回复确认,较快,但可能有数据丢失。
RELIABLE_RELIABILITY_QOS (RELIABLE)-- 需要接收者回复确认,较慢,能防止数据丢失。
!!!注意配置的兼容性!!!
Publisher \ Subscriber |
Best Effort | Reliable |
---|---|---|
Best Effort | ✓ | ✕ |
Reliable | ✓ | ✓ |
qos.m_durability.kind:持久性,定义了在subscriber加入前,对topic上数据的存储行为。
publisher默认TRANSIENT_LOCAL,subscriber默认VOLATILE
VOLATILE_DURABILITY_QOS(VOLATILE)-- 忽略subscriber matched之前的数据
TRANSIENT_LOCAL_DURABILITY_QOS(TRANSIENT_LOCAL)-- 有新的subscriber时,会将过去的数据添加到History
TRANSIENT_DURABILITY_QOS(不了解)-- 有新的subscriber时,会将持久存储中的数据添加到History
times.heartbeatPeriod:心跳周期,用seconds和fraction字段设置秒和毫秒,默认3s。主要用在底层StatefulWriter上面,用来周期性的检查对方有没有收到数据。减少心跳周期能提高不稳定网络情况下的性能。
times.nackResponseDelay:返回ACKNACK消息前的延迟,主要用在底层StatefulWriter上面,用来向对方发送没有收到的数据。
unicastLocator:单播定位器,定义接收数据的网络端点(参考网络配置)。Publisher和Subscriber会从Participant中继承单播定位器,也可以通过该配置项配置不同的定位器。配置后会覆盖Participant的默认配置。
multicastLocator:多播定位器,默认情况下Publisher和Subscriber都不会使用多播定位器,但是当有很多publisher/subscriber ,它们之间通过单播是每次都复制一份再发送,这样就降低了publisher端的效率,所以此时采用多播能够有效降低网络使用率。配置后会覆盖Participant的默认配置。
eProsima Fast RTPS 中的locator(定位器)就是网络端点。
Locator定义:
class RTPS_DllAPI Locator_t
{
public:
int32_t kind; // 协议,LOCATOR_KIND_UDPv4/LOCATOR_KIND_UDPv6
uint32_t port;
octet address[16]; // IP address
}