一. 概述
该文的目的是为了整体大致的梳理RTPS协议层从创建Reader到Reader收到数据的大致流程以及涉及到的相关类的关系。
同时,在日常使用RTPS Reader时,总是对部分概念存在疑问,在这里也想提出这些问题并且从代码中找到答案
二. 疑问
2.1 GAP报文的用途是什么?
2.2 创建Reader的时候是否创建了通信通路?
2.3 如何指定用户数据的传输协议?
2.4 传输层收到数据后怎么处理?怎么将数据回调给Reader?
2.5 BestEffort和Reliable的区别?
2.6 StatelessReader和StatefulReader的区别?
2.7 通信的端口是怎么指定的?
问题的答案放在后面的章节
三. RTPS Reader相关类
在阅读代码的时候发现,RTPS协议对于具体的网络传输的功能和RTPS自己本身的Reader的功能是分的比较开的,Reader只完成了协议上要求其完成的功能(RTPS协议 8.4.10.2/ 8.4.10.3),而涉及到具体通信的功能,则依赖了Transport相关的类。
大致的类关系图如下:
.
个人认为在RTPS Reader要完成数据读取功能,依赖的相关类可以分为下面两部分:
RTPS协议层:
RTPSParticipantImpl :
RTPSParticipantImpl是Writer和Reader这两类Endpoint的容器(内部存储了其创建的所有Writer和Reader),并且负责自身PDP和EDP报文的发送处理(通过BuiltinProtol中的PDPSimple和EDPSimple来完成)。
RTPSParticipantImpl内建的Transport默认支持并且UDPV4类型的Transport,在RTPSParticipantImpl.cpp的174行可以看到向NetworkFactor注册了UDPv4传输协议。
RPTSParticipantImpl对上层DDS层提供了创建Writer和Reader的接口,并且作为Writer和Reader的容器,提供了部分公共的Entity属性(例如GUID_Prefix) 此外,每个RTPSParticipantImpl需要自己负责自己RTPSParticipant以及内部Writer/Reader定期发送PDP和EDP报文让其他的RTPSParticipant能够发现自己,这部分功能是依赖RTPSParticipantImpl自己内部创建的BuiltinProtocol对象来完成的(BuiltinProtocol对象内部有PDPSimple和EDPSimple对象)。
RTPSReader:
RTPSReader是RTPS协议中的Reader父类,分为Stateful和Stateless两种具体的Reader(根据QoS或者Attribute中的Reliability,如果等于BestEffort,创建Stateless,否则创建Stateful)。
RTPSReader主要根据协议定义了Reader的主要接口,例如:matched_writer_remove,matched_writer_is_matched等,其中最重要的一个接口是processDataMsg,processDataFragMsg,processHeartbeatMsg等process*接口,这个是StatelessReader和StatefulReader都要实现的,用来处理收到的各种RTPS协议中的SubMessage。可以从这些接口的实现看出Stateless和Stateful的Reader在具体功能实现上的差异,例如,从对心跳包的处理就可以看出,StatelessReader是忽视心跳包的,这种情况下,StatelessReader当然也就不知道对端Writer当前发送到哪一包message了,也就是说不维护对端Writer的状态,和RTPS协议上的说明对的上。
此外,从RTPSReader的接口就可以大致看出,其只是处理数据的类,不是负责通信的类。也就是说,到达的对端Writer发送的数据会被送到RTPSReader处理,但是其本身不负责收数据这个动作。
MessageReceiver:
MessageReceiver是传输层和协议层之间的通道,所有到达的CDRMessage都会先给到MessageReceiver(CDRMessage从ReceiverResource来),然后有MessageReceiver来判断应该将消息转发给那些RTPSReader进行下一步处理。
MessageReceiver中最重要的接口函数是process_data_message_without_security/process_data_message_with_security,将消息分发给合适的Reader就是在这个接口中实现的:
ReceiverControlBlock:
从类关系图中可以看到,ReceiverControlBlocker类内部保存了了MessageReceiver和ResourceResource,其中MessageReceiver是用于分发RTPS子消息给各个RTPSReader的,而ResourceResource是封装了下层具体的网络数据通信。
在创建ReceiverResource的时候,对于每个Locator(地址定位:包含IP和端口),NetworkFactory会轮询已经注册了的传输层协议(TCP,UDP,用户自定义的),如果支持该Locator,则为该Locator创建ReceiverResource(具体的动作在NetworkFacctory::BuildReceiverResources完成)用于完成对应协议上的Socket创建和数据的收取动作。
对于每个ReceiverResource,RTPSParticipantImpl会创建一个MessagReceiver用于接收该ReceiverResource通路上收到的CDRMessage(RTPParticipantImpl.cpp第1720行)
传输层
NetworkFactory:
从名字看,大致就知道这个类是用于管理网络通信的,不管是TCP还是UDP还是其他用户自己定义的传输协议,都需要注册给NetworkFactory。
当RTPS协议层想在某个Locator上发送/接收消息的时候,同时通过NetworkFactory创建ReceiverResource/SenderResource来完成具体的数据收发动作(内部是依赖注册的不同协议的TransportInterface来完成的,例如TCPV4Transport, UDPV4Transport)。
ReceiverResource:
创建ReceiverResource是需要将传输层协议传进去,这个是为了创建具体的通信信道(例如UDPChannelResource,TCPChannelResource等)。通过UDPV4Transport对外提供的openInputChannel创建对应的ChannelResource,其内部包含了具体的创建socket以及数据收取的功能。
ReceiverResource内部根据不同的传输协议和具体的Locator会创建不同的ChannelResource来完成具体的RTPS消息的收取,收到的数据会传给MessageReceiver(通过ReceiverResource::RegisterReceiver注册进来, RTPSParticipantImpl.cpp 第1724行)进行处理(分解出其中不同的RTPS子消息)
UDPv4Transport:
其主要功能都在两个接口中完成了:OpenOutputChannel以及OpenInputChannel,这两个接口就是用于创建通信通路ChannelResource的。
Host上可能不止只有一张网卡,而是有多张网卡,在创建ChannelResource的时候,是需要在每张网卡的IP上都创建的:
但是可以使用网卡白名单指定DDS的通信发生在哪些网卡上,在白名单上的网卡,UDPv4Transport才会为其创建ChannelResource
UDPChannelResource:
UDPChannelResource内部封装了具体用于协议通信的Socket,每个UDPChannelResource都会创建一个单独的线程,在该线程上对socket进行数据收取
四. 流程
创建Reader
数据回调
五. 问题的调查结论
2.1 GAP报文的用途是什么?
A:为了告知Readr,GAP报文中这些sequence对应的message无法再被获取了(例如由于有效时间设置或者历史队列长度限制,这些mesage被删除了) // RTPS协议8.3.8.4
2.2 创建Reader的时候是否创建了通信通路?
A:创建Reader的时候创建了通信通路,具体流程是 RTPSParticipantImpl::create_reader -> RTPSParticipantImpl::createAndAssociateReceiverswithEndpoint -> RTPSParticipantImpl::createReceiverResource
2.3 如何指定用户数据的传输协议?
A:在RTPSParticipantImpl的构造函数中,可以看到通过传入的ParticipantAttribute中指定传输协议:
2.4 传输层收到数据后怎么处理?怎么将数据回调给Reader?
A:数据先从ChannelResource(包含socket)给到ReceiverResource,然后到达MessageReceiver,MessageReceiver解析消息头和内部各个子消息,并且分发给合适的Reader进行处理。
2.5 BestEffort和Reliable的区别?
A: 对于Reader来说,Bestffort类型的Reader只做接收数据的动作,而不做发送数据的动作(最主要是ACKNACK) 对于Reliable类型的Reader来说
-
必须对Writer发送的HeartBeat报文(没有Final Flag)做回应(发送ACKNACK) // RTPS协议8.4.2.3.1
-
收到HeartBeat报文后,如果Reader发现报文中的sequence大于Reader最后接收的message的sequence,必须做回应(发送ACKNACK)请求Writer进行重发 // RTPS协议8.4.2.3.2
-
一旦Reader对于HeartBeart报文发送过ACKNACK报文进行回复,则必须对后面每次收到的HeartBeat报文进行回复 // RTPS协议8.4.2.3.3
-
Reader只能发送ACKNACK消息给Writer
2.6 StatelessReader和StatefulReader的区别?
A:1. StatelessReader只有Besteffort类型的,没有Reliable类型的(RTPS协议文档中8.4.11.2中有说明)
-
Stateless和Stateful主要的区别在与是否维护对端的状态信息,对于Reader来说,Stateful会维护匹配的对端Writer的状态,而Stateless则不会(参照RTPS协议文档中8.21的类图说明以及WriterProxy)
-
正是由于Stateful维护了对端Writer的状态,才知道Writer上一次发送的最后的message的序号,因此才可以对收到的HeartBeat消息进行正确的恢复,而Stateless做不到。
2.7 通信的端口是怎么指定的?
A: 每次创建ReceiverResource的时候,会找一个合适的端口用于创建socket,具体的实现逻辑在RTPSParticipantImpl::createAndAssociateReceiverswithEndpoint中,大致就是根据DomainID和ParticipantID可以算出一个DDS可用端口的区间,然后每次创建Resource的时候,会从这个区间中找到没有使用的端口来创建对应的ChannelResource。