【FastRTPS】Writer-Reader层、使用及部分配置

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

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

该层是RTPS协议的原始实现,相对于Publisher-Subscriber层,它能对内部协议的实现提供更多的控制功能,比较适合于高级用户使用。

类介绍

RTPSDomain

创建和销毁RTPSParticipant、RTPSWriter、RTPSReader的管理类。

RTPSParticipant

对RTPSWriter 和RTPSReader进行分组到一个组织单元中,并使用内置RTPS发现和活跃度协议进行管理。

创建RTPSParticipant的例子

RTPSParticipantAttributes PParam;
Pparam.setName("participant");
Pparam.builtin.domainId = 80;
Pparam.use_IP6_to_send = false;
RTPSParticipant* p = RTPSDomain::createRTPSParticipant(PParam);
if(p!=nullptr){
//Participant correctly created
}
//To remove:
RTPSDomain::removeParticipant(p);

用户需要手动的将RTPSWriter和RTPSReader注册到内置协议,这是因为创建端点时能尽量减少配置(比使用内置协议注册所需的配置还要少)例子如下:

RTPSParticipant* part;
RTPSWriter* writer;
TopicAttributes tA("topicName","topicType",NO_KEY);
WriterQos wqos; //Change QOS as you want
part->registerWriter(writer,tA,wqos);
...
part->updateWriter(writer,wqos);

RTPSWriter

用于向网络中其他匹配的RTPSReader发送数据。在这层中,RTPSReader和RTPSWriter都是与History关联的,用户可以与History交互。

Input / Output通道:

创建RTPSWriter可以为其指定Input / Output通道,这些通道可以是RTPSParticipant的默认通道的子集,也可以是用户定义的。

为防止用不同的Input / Output通道创建RTPSWriter,FastRTPS会自动管理这些额外通道的创建和分配。

注意1: 同一个系统中的一个通道在同一时间只能有一个RPTSParticipant控制使用。

注意2:RTPSWriter创建完成后,WriterAttribute中的配置项会被更新为实际的Input / Output通道,这些通道要么保存在RTPSParticipant,并链接到新的RTPSWriter,要么专门为这个RTPSWriter保留。

流控制策略:

本层中的吞吐量控制是内置的,默认为无限吞吐量。(Publisher-Subscriber层也有流控配置接口)

注意:如果吞吐量大小 < Socket大小,那么消息就不会发送了。

注意2:在可靠模式下发送大数据时,可以使用流控制策略,能够避免网络中消息突发,并提高性能。

WriterAttributes WParams;
WParams.throughputController.size = 300000; //300kb
WParams.throughputController.timeMS = 1000; //1000ms

数据包拆分:

WriterAttributes Wparam;
Wparam.mode = ASYNCHRONOUS_WRITER;    // Allows fragmentation

注意:在高效模式下,如果发送端不断快速的发送大数据,发送缓冲区会很快的被填满,这时如果接收端消费消息太慢,就会造成消息数据丢失。在可靠模式下,大量的数据分片(数据包大于缓冲区大小时拆分出来的)会降低接收消息的频率,这可以通过增大Socket发送缓存来解决,也可以降低心跳周期。

匹配Reader:通过内置协议发现匹配,或者用户手动添加。可通过 RemoteReaderAttributes 获取到匹配信息。

(用户文档中说,不同模式(BestEffort / Reliable)的Reader/Writer不能匹配,这与网站资料不一致,需阅读源码后再确定)

手动添加匹配Reader例子:

RemoteReaderAttributes ratt;
Locator_t loc;
loc.set_IP4_address(127,0,0,1);
loc.port = 22222;
ratt.endpoint.unicastLocatorList.push_back(loc)
ratt.guid = c_Guid_Unknown; //For Realiable Writers, you actually need the GUID_t
writer->matched_writer_add(ratt);

RTPSReader

侦听网络上RTPSWriter发送的数据,用户可以直接与RTPSReader、ReaderHistory交互。

多个回调函数:

用户可以将回调函数附加到EDP RTPSReader,当内置过程执行完成后会调用回调函数。这样用户就可以处理RTPSWriter/RTPSReader发现消息了。

CustomReaderListener *my_readerListenerSub = new(CustomReaderListener);
CustomReaderListener *my_readerListenerPub = new(CustomReaderListener);
Std::pair EDPReaders = my_participant->getEDPReaders();
EDPReaders.first()->setListener(my_readerListenerSub);
EDPReaders.second()->setListener(my_readerListenerPub);

如何使用Writer-Reader层

第一步:创建Participant对象

RTPSParticipantAttributes Pparam;
Pparam.setName("participant");
RTPSParticipant* p = RTPSDomain::createRTPSParticipant(PParam);

第二步:创建Writer对象

HistoryAttributes hatt;
WriterHistory * history = new WriterHistory(hatt);
WriterAttributes watt;
RTPSWriter* writer = RTPSDomain::createRTPSWriter(rtpsParticipant, watt, history);

在RTPS标准中,Writer和Reader都是与History元素相关联的。在Publisher-Subscriber层,他的创建和管理是隐藏的,在这一层,你可以完全控制它。

第三步:创建Reader对象

class MyReaderListener:public ReaderListener;
MyReaderListener listen;
HistoryAttributes hatt;
ReaderHistory * history = new ReaderHistory(hatt);
ReaderAttributes ratt;
RTPSReader* reader = RTPSDomain::createRTPSReader(rtpsParticipant, watt, history, &listen);

ReaderListener用来实现回调函数。

第四步:发送数据

//Request a change from the history
CacheChange_t* ch = writer->new_change([]() -> uint32_t { return 255;}, ALIVE);
//Write serialized data into the change
ch->serializedPayload.length = sprintf((char*) ch->serializedPayload.data, "My example string %d", 2)+1;
//Insert change back into the history. The Writer takes care of the rest.
history->add_change(ch);

在RTPS标准中,Writer和Reader将话题数据保存在相关的History中,话题的每一个数据都表示成(CacheChange_t),这些数据由History管理。

用户与History交互过程:请求CacheChange_t、使用、释放。

话题数据类型需要实现deserialize和serialize方法。

返回最大有效负载字节数的回调函数。

第五步:接收数据

1. ReaderListener方式:

class MyReaderListener: public ReaderListener
{
    public:

    MyReaderListener(){}
    ~MyReaderListener(){}
    void onNewCacheChangeAdded(RTPSReader* reader,const CacheChange_t* const change)
    {
        // The incoming message is enclosed within the `change` in the function parameters
        printf("%s\n",change->serializedPayload.data);
        //Once done, remove the change
        reader->getHistory()->remove_change((CacheChange_t*)change);
    }
}

2. 直接读取History方式:

//Blocking method
reader->waitForUnreadMessage();
CacheChange_t* change;
//Take the first unread change present in the History
if(reader->nextUnreadCache(&change))
{
    /* use data */
}
//Once done, remove the change
history->remove_change(change);

配置

WriterAttribute / ReaderAttribute 配置

endpoint.reliabilityKind 参考Publisher-Subscriber的配置项 qos.m_reliability.kind

BEST_EFFORT: StatefulWriter / StatefulReader

RELIABLE: StatelessWriter / StatelessReader

endpoint.durabilityKind 参考Publisher-Subscriber的配置项 qos.m_durability.kind

注意:TRANSIENT_LOCAL和TRANSIENT会将History中所有未释放的CacheChange_t都发送出来。

endpoint.topicKind 参考Publisher-Subscriber的配置项 topic.topicKind

WITH_KEY:RTPSWriter使用该选项时,用户应先设置CacheChange_t的关键字,再把它加入到WriterHistory。不同的关键字可以看做同一个主题中的不同数据端点,这样RTPSReader就可以根据关键字区分数据源或者数据类别。在FastRTPS内置PDP发现、内置EDP发现使用的都是WITH_KEY选项。

endpoint.InputLocatorLists / endpoint.OutputLocatorList

包含unicastLocatorList 和 multicastLocatorList,FastRTPS会这里面的每一个Locator创建一个接收线程。

  inputLocatorList outputLocatorList
StatefulWriter 接收确认 发送数据
StatelessWriter × 发送数据
StatefulReader 接收数据 发送确认
StatelessReader 接收数据 ×

 

times

用来定义StatefulWriter的触发一些事件的时间间隔。如心跳间隔。

WriterHistory / ReaderHistory 配置

注意:RTPSReader可能会从多个RTPSWriter接收数据,这些RTPSWriter的发送频率不同,所以在设置ReaderHistory参数的时候需要保守一些。

historyAttr.payloadMaxSize:CacheChange_t有效负载的最大大小。最好能容纳尽可能大的数据块。默认值500 bytes

historyAttr.initialReservedCaches:初始缓存大小,默认500

historyAttr.maximumReservedCaches:最大缓存大小,默认0(无限制)

注意:一般情况下需要根据实际发送速率等来设置,如果允许无限制增长,会导致系统不可控,进而影响系统的稳定性。

你可能感兴趣的:(C++,FastRTPS)