【FastRTPS】Publisher-Subscriber接口层、使用及部分配置

来自:https://eprosima-fast-rtps.readthedocs.io/en/latest/pubsub.html

参考《FastRTPS User Manual.odt》第五章

Publisher-Subscriber接口层

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<UDPv6Transport::TransportDescriptor>();
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无法跟上发送带宽时,让丢失的分片重新发送。

QoS

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

 

如何使用Publisher-Subscriber层

第一步:创建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");
<profiles>
    <participant profile_name="participant_xml_profile">
        <rtps>
            <name>my_participant</name>
            <builtin>
                <domainId>80</domainId>
            </builtin>
        </rtps>
    </participant>
</profiles>

配置项:

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");
<profiles>
   <publisher profile_name="publisher_xml_profile">
      <topic>
         <dataType>HelloWorldType</dataType>
         <name>HelloWorldTopic</name>
         <historyQos>
            <kind>KEEP_LAST</kind>
            <depth>5</depth>
         </historyQos>
         <resourceLimitsQos>
            <max_samples>200</max_samples>
         </resourceLimitsQos>
      </topic>
      <qos>
         <reliability>
            <kind>RELIABLE</kind>
         </reliability>
         <durability>
            <kind>TRANSIENT_LOCAL</kind>
         </durability>
      </qos>
      <unicastLocatorList>
         <locator>
            <port>7800</port>
         </locator>
      </unicastLocatorList>
      <multicastLocatorList>
         <locator>
            <address>239.255.0.4</address>
            <port>7900</port>
         </locator>
      </multicastLocatorList>
   </publisher>

   <subscriber profile_name="subscriber_xml_profile">
      ...
   </subscriber>
</profiles>

配置项(蓝色字体是代码中的配置值,蓝色字体后面的是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
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章