来自: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);
第一步:创建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(无限制)
注意:一般情况下需要根据实际发送速率等来设置,如果允许无限制增长,会导致系统不可控,进而影响系统的稳定性。