分析EDP交互码流(No.75~No.109)前,先说明下图图3-1码流中各个子消息的功能。
名称 |
功能 |
INFO_TS |
指示该条RTPS消息被发送时的时间戳。 |
INFO_DST |
指示在该条RTPS消息中,INFO_DST后面的子消息的处理者RTPS reader实体的GuidPrefix为INFO_DST.GuidPrefix。 |
DATA(p) |
数据消息,包含参与者的属性信息,即PDP消息。 |
DATA(w) |
数据消息,包含userWriter的属性信息。当发布端userWriter创建成功后,该消息会被发送给订阅端。 |
DATA(r) |
数据消息,包含userReader的属性信息。当订阅端userReader创建成功后,该消息会被发送给发布端。 |
DATA |
数据消息,包含用户发送的实际消息内容。 |
HEARTBEAT |
该消息有两个功能: 1.它通知Reader在Writer的历史缓存中可用的序列号,这样Reader就可以请求(使用一个AckNack消息)任何已经错过的内容。 2.它要求Reader发送对已进入Reader历史缓存的CacheChange更改的确认,以便Writer了解Reader的状态。 所有Heartbeat消息都有第一个功能。也就是说,Reader总是会发现writer的HistoryCache的状态,并可能请求它所遗漏的内容。通常,如果它丢失了一个CacheChange,RTPS Reader会发送一个AckNack消息。 Writer使用FinalFlag请求Reader对它收到的序列号发送确认信息。如果Heartbeat设置了FinalFlag,那么Reader就不需要回AckNack消息了。但是,如果没有设置FinalFlag,那么Reader必须发送一个AckNack消息,指示它已经接收到哪些缓存更改,即使AckNack指出它已经在Writer的历史缓存中获得了所有的CacheChange更改。 |
ACKNACK |
该子消息用于将Reader的状态通知Writer。该子消息允许Reader告知Writer所收到的序列号,以及它仍然缺少的序列号。这个子消息可以用来做正常应答以及异常应答。 ACKNACK消息中元素readerSNState,所有在readerSNState.base之前的序列号是已经被Reader确认收到的。出现在集合里面的序列号是Reader没有收到的序列号。 |
图3-2 中的所有的Writer和Reader端点,又分为有状态端点和无状态端点。PDP的2个端点是无状态端点。EDP和WLP的6个端点都是有状态端点,UserWriter和UserReader端点由于在本示例当中Qos设置的是可靠传输,所以他们也是有状态端点。
有状态Writer和Reader建立关联的消息交互如图3-3所示。
图中缩写意义如下:
缩写 |
含义 |
HB |
HEARTBEAT消息。 |
ACK |
ACKNACK消息。 |
INIT_ACK |
初始化ACKNACK消息。 ACKNACK.readerSNState的bitmapBase和numBits都为0,如图3-4所示。 该消息在StatefulReader匹配一个StatefulWriter的时候被发送。当StatefulWriter收到该消息时,响应HEARTBEAT消息。 |
图3-3 中5条消息的详细描述如下:
所以理论上一对StatefulWriter和StatefulReader建立关联会产生5条RTPS消息,其中HEARTBEAT两条,ACKNACK三条。另外,StatefulWriter和StatefulReader建立关联,发送消息的顺序不会严格和图3-3中的一致,和实际测试环境相关。
注意,为什么说是理论上,因为有可能少于5条。图3-3中蓝色的ACKNACK消息,有可能Reader不会发送,原因是Reader接收Writer的HEARTBEAT消息并响应ACKNACK消息的前提是,Reader已经匹配了该Writer,如果在接收到Writer的HEARTBEAT消息时,Reader还没有完成和该Writer的匹配,那么HEARTBEAT消息会被Reader丢弃,同时Reader也不会响应ACKNACK消息。这种情况是存在的,幸运的是本篇博客中截取码流的这次测试,所有有状态端点的交互都没有出现这种情况。笔者测试过多次,实际上这种情况出现的概率很高。
StatefulReader接收到StatefulWriter的HEARTBEAT消息时,判断是否匹配的代码段如下:
EDP和WLP一共6对有状态端点,建立连接理论上会产生30条RTPS消息。HEARTBEAT一共12条,ACKNACK一共18条。
图3-1中,从编号75到编号107的RTPS码流对应的正好是EDP和WLP的6对有状态端点建立连接的码流。一共30条RTPS消息。具体消息数分类如下:
类型 |
数量 |
总计 |
HEARTBEAT |
6对* 每对发送2条 |
12 |
ACK |
6对* 每对发送2条 |
12(有可能少于12,原因前面已经描述过) |
ACK(初始化ACK) |
6对* 每对发送1条 |
6 |
具体EDP和WLP端点消息交互可以参考图3-4的编号75到107。
图3-5消息交互和图3-1的wireshark码流是对应的。
EDP和WLP的端点都是通过对端发来的PDP消息,获取对端端点的属性信息。然后进行消息交互,建立关联。
EDP和WLP的端点都完成关联了,然后轮到UserWriter和UserReader建立关联。
UserWriter在被创建时,它的属性信息被封装成数据,存放在发布端EDP的SEDPPubWriter的历史缓存中。UserWriter被创建时调用堆栈如下:
UserReader在被创建时,它的属性信息也被封装成数据,存放在订阅端EDP的SEDPSubWriter的历史缓存中。UserReader被创建时调用堆栈如下:
由于SEDPPubWriter和SEDPSubWriter的历史缓存中存在数据,所以在他们同对端Reader端点建立关联的时候,也就是如图3-3建立连接的时候,发布端SEDPPubWriter,通过HEARTBEAT消息告知订阅端SEDPPubReader,自己的历史缓存中有序列号为1的数据(即UserWriter的属性信息)。HEARTBEAT消息如下图:
订阅端SEDPPubReader收到HEARTBEAT消息,响应ACKNACK消息,因为没有收到序列号为1的数据,所以在消息内容中标记了该条数据没有被收到。ACKNACK消息如下图:
发布端SEDPPubWriter收到ACKNACK消息,启动响应ACKNACK消息的定时器,定时器超时后,发送序列号为1的DATA消息(UserWriter的属性信息)给订阅端SEDPPubReader。对应wireshark码流中编号为108的RTPS消息。DATA消息内容如下:
至此,订阅端获取到了发布端的UserWriter的属性信息。
当订阅端SEDPSubWriter和发布端SEDPSubReader如图3-3建立关联时,订阅端SEDPSubWriter发送HEARTBEAT消息告知发布端SEDPSubReader,自己的历史缓存中有序列号为1的数据(即UserReader的属性信息),HEARTBEAT消息如下图:
发布端SEDPSubReader收到HEARTBEAT消息,响应ACKNACK消息,因为没有收到序列号为1的数据,所以在消息内容中标记了该条数据没有被收到。ACKNACK消息如下图:
订阅端SEDPSubWriter收到ACKNACK消息,启动响应ACKNACK消息的定时器,定时器超时后,发送序列号为1的DATA消息(UserReader的属性信息)给发布端SEDPSubReader。对应wireshark码流中编号为109的RTPS消息。DATA消息内容如下:
至此,发布端获取到了订阅端的UserReader的属性信息,订阅端也获取到了发布端的UserWriter的属性信息。
发布端获取到订阅端的UserReader的属性信息,会检查和本地UserWriter是否匹配,比如TopicName,TypeName,TopicKind,QoS是否匹配,如果匹配,UserWriter开始按照图3-3中Writer的流程发消息,开始建立关联。此时发布端可以开始发送用户数据。
订阅端获取到发布端的UserWriter的属性信息,会检查和本地UserReader是否匹配,比如TopicName,TypeName,TopicKind,QoS是否匹配,如果匹配,UserReader开始按照图3-3中Reader的流程发消息,开始建立关联。此时订阅端可以收到发布端发送的用户数据。