FastDDS 源码解析(十四)接收PDP消息(上)

380 阅读9分钟

车载消息中间件FastDDS 源码解析(一)FastDDS 介绍和使用

车载消息中间件FastDDS 源码解析(二)RtpsParticipant的创建(上)

车载消息中间件FastDDS 源码解析(三)RtpsParticipant的创建(中)

车载消息中间件FastDDS 源码解析(四)RtpsParticipant的创建(下)

车载消息中间件FastDDS 源码解析(五)BuiltinProtocols(上)

车载消息中间件FastDDS 源码解析(六)BuiltinProtocols(中)EDP

车载消息中间件FastDDS 源码解析(七)BuiltinProtocols(下)WLP&TypeLookupManager

车载消息中间件FastDDS 源码解析(八)TimedEvent

车载消息中间件FastDDS 源码解析(九)Message

车载消息中间件FastDDS 源码解析(十)发送第一条PDP消息(上)

FastDDS 源码解析(十一)发送第一条PDP消息(中)

FastDDS 源码解析(十二)发送第一条PDP消息(下)---异步发送

FastDDS 源码解析(十三)发送第一条PDP消息---跨进程发送

1.pdp消息的winshark抓包

1.1winshark抓包总览

image-20240123111646650.png

用tcpdump抓包后,使用winshark 可以看到抓到的包,这些都是典型的PDP消息,DATA(p)表示这是个PDP消息

1.2详细内容

具体的内容如下可见:

image-20240123111842332.png 我们逐一分析一下这条pdp消息

1.2.1消息头

image-20240615174729145.png

这个Protocol是表示协议版本号,这里的版本号是2.2

vendorId表示的是fastdds:我们知道dds 有很多开源和闭源的实现,这个就是表示是fastdds

guidPrefix 表示发送这条消息的这个participant的guidPrefix

1.2.2 submessage

image-20240123113243306.png

这两个submessage就是表示这个rtps message 中包含了2个submessage消息

INFO_TS 和DATA 子消息

INFO_TS表示时间戳子消息

1.2.3DATA子消息的解析

DATA子消息表示DATA子消息,这个可能是pdp消息,也可能是dds使用方发送的其他消息。

image-20240123114359778.png

Endianness 表示这个消息体是大端还是小端编码

octetsToNextHeader表示这个子消息的大小

发送方接收方

image-20240123141243373.png readerEntityId 表示接收方是 Builtin_Participant_Reader(PDP的StatelessReader)

writerEntityId 表示发送方是 Buitlin_Participant_Writer(PDP的StatelessWriter)

writerSeqNumber:消息的编号 这里是1

具体内容

serializedData是这个Data submessage 包含的

image-20240123163403448.png

PID_PROTOCOL_VERSION 协议版本号,和消息头部分类似

PID_VENDOR_ID 表示的是fastdds:和消息头部分类似

PID_PARTICIPANT_GUID 这条pdp消息对应的Participant 的guid,和消息头部分类似

多播单播地址

image-20240123170005796.png

PID_METATRAFFIC_UNICAST_LOCATOR 这个item中包含了 多播地址 端口号 等信息

这边有3个item,表示有3个本地的

PID_DEFAULT_UNICAST_LOCATOR 这个item中包含了 单播地址 端口号 等信息

这边有3个item,表示有3个单播地址

超时时间

image-20240123172006859.png

PID_PRATICIPANT_LEASE_DURATION 表示 participant的参数:这边设置为20s,超过时间,则认为这个Participant已经不存在了

PID_BUILTIN_ENDPOINT_SET

这个我们可以看一下抓到的pdp消息中有个字段 PID_BUILTIN_ENDPOINT_SET,这个字段表示了这个Participant配置的EndPoint

image-20240122182634220.png

image-20231026211522763.png

我们再看一下DDs 的这张经典的图,是可以找到一一对应的关系的

Participant Detector 表示这个远端的Participant有一个接收PDP 消息的StatelessReader(图中对应SPDPReader)

Participant Announcer 表示这个远端的Participant有一个发送PDP消息的StatelessWriter(图中对应SDPWriter)

Participant Message DataReader 表示这个远端的Participant 有个WLP的Reader(图中对应RTPSMsgReader)

Participant Message DataWriter 表示这个远端的Participant 有个WLP 的Writer(图中对应RTPSMsgWriter)

Subscription Detector 表示这个远端的Participant 有一个EDP 的sub 的StatefulReader(图中对应SEDPSubReader)

Subscription Announcer 表示这个远端的Participant 有一个EDP 的StatefulWriter(图中对应SEDPSubWriter)

Publication Detector 表示这个远端的Participant 有一个EDP 的pub的StatefulReader(图中对应SEDPPubReader)

Publication Announcer 表示这个远端的Participant 有一个EDP 的pub的StatefulWriter(图中对应SEDPPubWriter)

根据某些配置,这些端点都是可配置的。

消息类型

image-20240123173824421.png 表示这是个RTPSParticipant的消息。

1.3类图

我们再看一下一个RTPSParticipant类的组成部分(参考车载消息中间件FastDDS 源码解析(四)RtpsParticipant的创建(下)

classDiagram
      RTPSParticipantImpl *-- BuiltinProtocols
      RTPSParticipantImpl *-- RTPSParticipantAttributes
      RTPSParticipantAttributes *-- BuiltinAttributes
      BuiltinProtocols *-- PDP
      PDP *-- RTPSWriter
      PDP *-- RTPSReader
      BuiltinProtocols *-- WLP
      PDP *-- EDP
      EDP <-- EDPSimple
      EDPSimple *--StatefulWriter
      EDPSimple *--StatefulReader
      BuiltinProtocols *-- TypeLookupManager
      class RTPSParticipantImpl {
          +BuiltinProtocols mp_builtinProtocols
          +RTPSParticipantAttributes m_att
      }
      class BuiltinProtocols {
      		+PDP mp_PDP
      		+WLP mp_WLP
      		+TypeLookupManager tlm_
      }
      class PDP {
      		+EDP mp_EDP
      }
      class RTPSWriter {
      }
      class RTPSReader {
      }
      class EDPSimple {
      		std::pair<StatefulWriter*,WriterHistory*> publications_writer_
      		std::pair<StatefulWriter*,WriterHistory*> subscriptions_writer_
      		std::pair<StatefulReader*,ReaderHistory*> publications_reader_
      		std::pair<StatefulReader*,ReaderHistory*> subscriptions_reader_
      }
      class RTPSParticipantAttributes {
      		+LocatorList_t defaultUnicastLocatorList
      		+LocatorList_t defaultMuliticastLocatorList
      		+BuiltinAttributes builtin
      }
      class BuiltinAttributes {
      		+LocatorList_t metatrafficUnicastLocatorList
      		+LocatorList_t metatrafficMuliticastLocatorList
      }

对比pdp消息,我们可以看到,基本上PDP消息,把RTPSParticipant中的所有对象信息都完整的发送了出去,本地Participant通过收到的pdp消息,进行一一匹配,pdp与pdp 匹配,EDP与EDP匹配,wlp与wlp匹配,TypeLookupManager与TypeLookupManager匹配。

2.接收消息

消息分为几类,pdp消息,edp消息,wlp消息,用户消息。本质上都是消息。

从发送途径来说分为跨网络消息,跨进程消息。

我们这边以接收网络pdp消息举例,跨进程消息其实类似。

我们这里也只以pdp消息举例,其他消息类似,只是在处理的时候有些区别,在接收阶段都是一样的。

PDPWriter是pdp发送消息的对象 和PDPReader是pdp接收消息的对象。

image-20231029192226592.png

如图所示:每个PDP都有一个writer 和一个reader,本地的pdp writer 与远端的pdp reader 对接,本地的pdp reader 与远端的pdpwriter 对接。

前面我们介绍了pdp如何发送消息的,现在我们介绍一下pdp如何接收消息的。

我们再看一下第四篇的这个类图

2.1类图

classDiagram
      
      RTPSParticipantImpl *-- ReceiverControlBlock
     	ReceiverControlBlock *-- ReceiverResource
      ReceiverControlBlock *-- MessageReceiver
      ReceiverResource *-- MessageReceiver
      UDPTransportInterface <|-- UDPV4Transport
      UDPV4Transport *-- UDPChannelResource
      UDPChannelResource *-- ReceiverResource
      class RTPSParticipantImpl {
      		+std::list<ReceiverControlBlock> m_receiverResourcelist
      }
      class ReceiverControlBlock {
      		+std::shared_ptr<ReceiverResource> Receiver
      		+MessageReceiver* mp_receiver
      }
      class ReceiverResource {
      		+MessageReceiver* receiver
      }
      class MessageReceiver{
      		+std::unordered_map<EntityId_t, std::vector<RTPSReader*>> associated_readers_
      }
      class UDPV4Transport{
      		+std::map<uint16_t, std::vector<UDPChannelResource*>> mInputSockets
      }
      class UDPChannelResource{
      		+TransportReceiverInterface* message_receiver_
      		+eProsimaUDPSocket socket_
      }

1.UDPV4Transport 有一个接收消息的socket队列

2.socket和MessageReceiver是一一对应的

socket收到消息就交给MessageReceiver处理

3.RTPSParicipantImp管理这些MessageReceiver

4.MessageReceiver 有一个RTPSReader的队列,MessageReceiver收到消息后从这些RTPSReader中找一个出来处理消息

2.2时序图

sequenceDiagram
		participant UDPChannelResource
		participant ReceiverResource
		participant MessageReceiver		
		participant StatelessReader			
		UDPChannelResource ->> UDPChannelResource: 1.perform_listen_operation()
		UDPChannelResource ->> UDPChannelResource: 2.Receive()
		UDPChannelResource ->> ReceiverResource: 3.OnDataReceived()
		ReceiverResource ->> MessageReceiver: 4.processCDRMsg()
		MessageReceiver ->> MessageReceiver: 5.proc_Submsg_Data()
		MessageReceiver ->> MessageReceiver: 6.process_data_message_without_security()
		MessageReceiver ->> StatelessReader: 7.processDataMsg()
	

1.UDPChannelResource 跑一个线程 干了

a.接收消息 见 2

b.ReceiverResource 的OnDataReceived,处理接收的消息

2.UDPChannelResource::Receive 主要干了2件事情

接收udp 消息,同时获取发送端的ip地址 和 port端口号

3.ReceiverResource::OnDataReceived 主要是 调用MessageReceiver::processCDRMsg 处理消息

4.MessageReceiver::processCDRMsg 主要是解析消息,针对不同的消息类型进行不同的处理。

如果是data消息的话,调用proc_Submsg_Data,这个Data消息,是PDP消息

如果是其他消息的话就调用相应的函数处理。

5.proc_Submsg_Data 解析各个字节的消息,将信息放入CacheChange_t,解析完成后主要是调用的process_data_message_function_

6.process_data_message_function_函数如果没有安全模块的情况下就是process_data_message_without_security函数

在这里找到对应reader,来处理 CacheChange_t,调用的函数processDataMsg ,在pdp 阶段,调用的是statelessreader 的processDataMsg

7.processDataMsg 就是处理接受的pdp消息

2.2perform_listen_operation

在介绍RTPSParticipantImple的构造函数

创建UDPChannelResource的部分,会创建一个thread,不断轮训获取消息,这个轮训函数就是这个perform_listen_operation,对应时序图步骤1

 void UDPChannelResource::perform_listen_operation(
         Locator input_locator)
 {
     Locator remote_locator;
 ​
     while (alive())
     {
         // Blocking receive.
         auto& msg = message_buffer();
         //接收消息
         if (!Receive(msg.buffer, msg.max_size, msg.length, remote_locator))
         {
             continue;
         }
 ​
         // Processes the data through the CDR Message interface.
         if (message_receiver() != nullptr)
         {
             message_receiver()->OnDataReceived(msg.buffer, msg.length, input_locator, remote_locator);
         }
         else if (alive())
         {
             EPROSIMA_LOG_WARNING(RTPS_MSG_IN, "Received Message, but no receiver attached");
         }
     }
 ​
     message_receiver(nullptr);
 }

轮训函数,不断接收消息,处理消息,

1.Receive接收消息

2.message_receiver()->OnDataReceived 处理消息

步骤2:

UDPChannelResource::Receive 就是获取消息,同时获取远端的ip地址和端口号

 bool UDPChannelResource::Receive(
         octet* receive_buffer,
         uint32_t receive_buffer_capacity,
         uint32_t& receive_buffer_size,
         Locator& remote_locator)
 {
   ------
         asio::ip::udp::endpoint senderEndpoint;
         //接收udp message,同时获取发送端的ip地址 和 port端口号
         size_t bytes = socket()->receive_from(asio::buffer(receive_buffer, receive_buffer_capacity), senderEndpoint);
         receive_buffer_size = static_cast<uint32_t>(bytes);
         if (receive_buffer_size > 0)
         {
   ------            
             //将发送端的ip地址 和 port端口号 赋值给remote_locator          
             transport_->endpoint_to_locator(senderEndpoint, remote_locator);
         }
         return (receive_buffer_size > 0);
     }
   ------ 
 }

主要干了2件事

1.socket()->receive_from 接收socket 消息

接收udp message,同时获取发送端的ip地址 和 port端口号

2.transport_->endpoint_to_locator

将发送端的ip地址 和 port端口号 赋值给remote_locator

步骤3:

 void ReceiverResource::OnDataReceived(
         const octet* data,
         const uint32_t size,
         const Locator_t& localLocator,
         const Locator_t& remoteLocator)
 {
     (void)localLocator;
 ​
     std::lock_guard<std::mutex> _(mtx);
 ​
     MessageReceiver* rcv = receiver;
 ​
     if (rcv != nullptr && active_callbacks_ >= 0)
     {
         ++active_callbacks_;
 ​
         CDRMessage_t msg(0);
         msg.wraps = true;
         msg.buffer = const_cast<octet*>(data);
         msg.length = size;
         msg.max_size = size;
         msg.reserved_size = size;
 ​
         // TODO: Should we unlock in case UnregisterReceiver is called from callback ?
         rcv->processCDRMsg(remoteLocator, localLocator, &msg);
 ​
         // allow disabling
         if (--active_callbacks_ == 0)
         {
             cv_.notify_one();
         }
     }
 }

最终调用MessageReceiver的processCDRMsg函数,处理接收到的消息, 这里面有两个参数remoteLocator, localLocator 一个是远端的locator,另一个是本地的一个locator

MessageReceiver的processCDRMsg 会将message 进行解析,针对submessage分别进行处理。可以参考 车载消息中间件FastDDS 源码解析(九)Message

这里调用到了proc_Submsg_Data

步骤5:

 bool MessageReceiver::proc_Submsg_Data(
         CDRMessage_t* msg,
         SubmessageHeader_t* smh,
         EntityId_t& writerID) const
 {
     
     
 ------    
     //reader and writer ID
     RTPSReader* first_reader = nullptr;
     EntityId_t readerID;
     valid &= CDRMessage::readEntityId(msg, &readerID);
 ​
     //WE KNOW THE READER THAT THE MESSAGE IS DIRECTED TO SO WE LOOK FOR IT:
     if (!willAReaderAcceptMsgDirectedTo(readerID, first_reader))
     {
         return false;
     }
 ​
     //FOUND THE READER.
     //We ask the reader for a cachechange to store the information.
     CacheChange_t ch;
     ch.kind = ALIVE;
     ch.writerGUID.guidPrefix = source_guid_prefix_;
     valid &= CDRMessage::readEntityId(msg, &ch.writerGUID.entityId);
 ​
     writerID = ch.writerGUID.entityId;
 ​
     //Get sequence number
     valid &= CDRMessage::readSequenceNumber(msg, &ch.sequenceNumber);
 ​
 ------
        
 ​
     // Set sourcetimestamp
     if (have_timestamp_)
     {
         ch.sourceTimestamp = timestamp_;
     }
 ​
 ------
     //Look for the correct reader to add the change
   
     //主要是这个函数
     process_data_message_function_(readerID, ch);
 ​
     IPayloadPool* payload_pool = ch.payload_owner();
     if (payload_pool)
     {
         payload_pool->release_payload(ch);
     }
 ​
 ------    
     return true;
 }

主要干了2件事:

1.读取了一些参数,我们在这儿举例一些关键参数

a.SequenceNumber 这个消息的唯一标识

b.时间戳,writerid(发送writer的唯一标识),readerid(读取reader的唯一标识)

2.调用了process_data_message_function_ 这个函数

如果不使用安全模块,最终调用到process_data_message_without_security 函数

步骤6:

 void MessageReceiver::process_data_message_without_security(
         const EntityId_t& reader_id,
         CacheChange_t& change)
 {
     auto process_message = [&change](RTPSReader* reader)
             {
                 reader->processDataMsg(&change);
             };
 ​
     findAllReaders(reader_id, process_message);
 }

调用了MessageReceiver 对应的reader来处理数据

在pdp阶段,一般都是StatelessReader来处理消息

车载消息中间件FastDDS 源码解析(一)FastDDS 介绍和使用

车载消息中间件FastDDS 源码解析(二)RtpsParticipant的创建(上)

车载消息中间件FastDDS 源码解析(三)RtpsParticipant的创建(中)

车载消息中间件FastDDS 源码解析(四)RtpsParticipant的创建(下)

车载消息中间件FastDDS 源码解析(五)BuiltinProtocols(上)

车载消息中间件FastDDS 源码解析(六)BuiltinProtocols(中)EDP

车载消息中间件FastDDS 源码解析(七)BuiltinProtocols(下)WLP&TypeLookupManager

车载消息中间件FastDDS 源码解析(八)TimedEvent

车载消息中间件FastDDS 源码解析(九)Message

车载消息中间件FastDDS 源码解析(十)发送第一条PDP消息(上)

FastDDS 源码解析(十一)发送第一条PDP消息(中)

FastDDS 源码解析(十二)发送第一条PDP消息(下)---异步发送

FastDDS 源码解析(十三)发送第一条PDP消息---跨进程发送