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

189 阅读11分钟

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

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

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

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

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

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

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

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

message简介

RTPS消息是RTPS的Writer和Reader之间交换的消息。writer向reader发送RTPS消息,reader解析RTPS消息。

1.message 的结构图

下图是message的结构:

image-20231123172845384.png

我们看到一个RTPS消息主要由消息头(header),HeaderExtention和子消息(submessage)三部分组成。

这里面HeaderExtention,字面意思就是header的扩展,其实是个submessage。这个HeaderExtention是个可选的配置。

2.winshark抓的包

我们通过winshark抓个包看一下

image-20240116205139689.png

这是一个完整的RTPS消息:

最上面一个消息名称:Real-Time Publish-Subscribe Wire Protocol:缩写为RTPS

红圈处是消息头的位置:下面会详细说

再往下是消息体:由Submessage 组成

3.消息头

header是在rtps消息的开头

上面winshark截图中红色部分是rtps的消息头

Protocol version:dds的版本号,dds发布了有多个版本,需要进行版本区分。

vendorid 是一样的都是01.15。 表示的是供应商号。fastdds 和open dds等其他dds的vendorid是不一样的。

header的组成:

ProtocolId标识这是RTPS消息,PROTOCOL_RTPS
ProtocolVersion标识RTPS协议版本
VendorId标识实现RTPS协议的供应商
GuidPrefix定义所有GUID的默认前缀

如果有HeaderExtention的话,就在header的下面(HeaderExtention 是可选的)。

3.submessage

winshark抓的包的截图红圈下面就是子消息(submessage),上面截图中submessage有两个 :INFO_TS和DATA

每个子消息有每个子消息的结构。

SubmessageKindsubmessage的类型
SubmessageFlag共8个可能的标志位,第一位表示大小端,False为大端;其他与Submessage类型有关。
SubmessageLength这个submessage的长度

3.1submessage分类

submessage分为两类:Entity子消息 和 解释器子消息

区别就是在于解释器子消息是会修改接收者的状态,用于处理后续的Entity 子消息。而Entity子消息并不会修改接收者的状态。

Entity子消息大概有这些:Data、DataFrag、Heartbeat、HeartbeatFrag、Gap、AckNack、NackFrag

解释器子消息大概有这些:HeaderExtension、InfoSource、InfoDestination、InfoReply、InfoTimestamp、Pad

这张图显示了,所有submessage以及各个submessage的组成部分: image-20240117105957829.png

3.2submessage 各个子消息的简单介绍

AckNack

用于将Reader的状态返回Writer:通知SequenceNumber。 内容:

    • EndiannessFlag:大小端
    • FinalFlag:Response是否强制,标识Reader是否需要Writer的Heartbeat
    • readerId、writerId:标识发送者、接收者
    • readerSNState:在readerSNState.base之前的SequenceNumber已被收到;在此SequenceNumberSet中的未被收到,其他未知
    • AckNack的计数器,用于识别响应重发。

Data

用于通知Reader在Writer端的某个数据对象的Change,包括数据值的改变和生命周期的改变。

内容:

    • EndiannessFlag:大小端
    • InlineQosFlag:标识Qos参数列表的存在性
    • DataFlag:标识dataPayload字段有数据
    • KeyFlag:标识dataPayload字段有数据的key
    • NonStandardPayloadFlag:标识dataPayload字段非标准格式
    • readerId、writerId:标识发送者、接收者
    • writerSN:writer造成的change的序号,由writer维护
    • inlineQos:当Flag为1时存在,包括QoS信息
    • serializedPayload:DataFlag或KeyFlag为1时存在,包含数据对象的key或value

DataFrag

Writer发送给Reader,将Data分段为多个DataFrag,由Reader进行重排。 在Data的基础上分成多个DataFrag的好处:保证每一个子消息的内容和结构变化最小,使得实现解析网络报文协议更容易;避免QoS参数中增加分段内容,引起性能降低并加大在线调试的难度(因为需要确认是否分段)。

内容:

    • Data所有字段
    • fragmentStartingNum:标识开始段的序号
    • fragmentsInSubmessage:此子消息中包含了多少个Frag
    • dataSize:分段前的数据总大小
    • fragmentSize:单独的fragment的大小,不超过64K

Gap

Writer发送给Reader,标识HistoryCache中的一些Change不再可用,也不会再发给Reader。 同时,Gap消息也会通知Reader

Heartbeat

Writer发送给Reader,通知Writer有的changes的SequenceNumber。

目的:

    • 通知Reader当前Writer的HistoryCache中的情况,便于Reader发送AckNack请求
    • 了解Reader当前已收到数据的情况

内容:

    • EndiannessFlag:大小端
    • FinalFlag:是否需要Reader响应
    • LivelinessFlag:DDS Writer是否强调其存活状态
    • GroupInfoFlag:是否包含Writer所属的Writer Group的额外信息
    • readerId,writerId
    • firstSN,lastSN:标识Writer已经写的change的最小/最大序号
    • count:计数、递增
    • GroupInfo(当GroupInfoFlag为1时存在):
      • currentGSN:Group写的change的序号
      • firstGSN、lastGSN:对应Group的firstSN、lastSN
      • writerSet、secureWriterSet:标识当此消息发出时,Group中的writer/secureWriter

说明:

    • 当Writer未发送消息时,firstSN=1,lastSN=0
    • 当Writer在cache中仅有一条消息时:firstSN=lastSN
    • 当Wrtier已写10个sample,同时最后5个在cache中时:firstSN=6,lastSN=10
    • 当Writer在发送心跳前已写10个sample,同时cache中无数据时:firstSN=11,lastSN=10

HeartbeatFrag

Heartbeat信息被分片时,发送HeartbeatFrag消息通知哪个Frag消息是可用的,使能可靠通讯; 当所有分段信息都可用时,会发送一个Heartbeat消息。(注意此分片消息并不是Heartbeat的分片)

内容:

    • EndiannessFlag:大小端
    • readerId、writerId
    • writerSN:当前Change的SN
    • lastFragmentNum:当前最后一个可用的FragSN

InfoDestination

Writer发送给Reader,用于修改GuidPrefix。

内容:

    • EndiannessFlag:大小端
    • GuidPrefix:要修改成的guidPrefix

InfoReply

Reader发送给Writer,包含了接收方需要把对同一个Message中后续的Submessage的Reply发送到的位置。

内容:

    • EndiannessFlag:大小端
    • MulticastFlag:是否包括多播地址
    • unicastLocatorList:Writer使用其中的单播地址,用于后续的回复
    • multicastLocatorList:可选,多播地址,用途同上

InfoSource

修改跟在其后的Submessage的logical source

内容:

    • EndiannessFlag:大小端
    • protocolVersion:后续Submessage需要使用的协议版本号
    • vendorId, guidPrefix:意义如描述,用途同上

NackFrag

当数据分片时,Reader可能通知Writer有某些Fragment number没有收到。

内容:

    • EndiannessFlag:大小端
    • readerId、writerId:意义同上文
    • writerSN:丢包的SN号
    • fragmentNummberState:一个NumberSet,其中包含的fragment number是reader端没收到的(但不包含的并不代表收到了)
    • count:计数器

Pad

内存对齐

4.message 的相关代码

4.1生成message

我们看一下源码中是如何生成message的,我们以一个简单的心跳message 举例:

主要是在RTPSMessageGroup中完成消息的拼装

RTPSMessageGroup 有两个变量

CDRMessage_t* full_msg_ 一整个消息

CDRMessage_t* submessage_msg_ 子消息

full_msg_ 就是发送的一整个消息,一整个消息里面包含了子消息。

在初始化的时候full_msg_ 会把header初始化后先加到full_msg_中去。

 bool RTPSMessageGroup::add_heartbeat(
         const SequenceNumber_t& firstSN,
         const SequenceNumber_t& lastSN,
         const Count_t count,
         bool isFinal,
         bool livelinessFlag)
 {
     assert(nullptr != sender_);
 
   // check一下需不需要把之前的message发送掉
     check_and_maybe_flush();
 ​
     const EntityId_t& readerId = get_entity_id(sender_->remote_guids());
 
   //配置submessage_msg_,因为是heartbeat,所以配置的submessage 类型就是heartbeat
     if (!RTPSMessageCreator::addSubmessageHeartbeat(submessage_msg_, readerId, endpoint_->getGuid().entityId,
             firstSN, lastSN, count, isFinal, livelinessFlag))
    {
         EPROSIMA_LOG_ERROR(RTPS_WRITER, "Cannot add HEARTBEAT submsg to the CDRMessage. Buffer too small");
         return false;
    }
 ​
 ​
 //将submessage 插入到fullmessage中去
     return insert_submessage(false);
 }
 ​

上面函数就是先生成了一个heartbeat 的submessage,然后将submessage 加入到 full_msg_中去。

 bool RTPSMessageGroup::insert_submessage(
         const GuidPrefix_t& destination_guid_prefix,
         bool is_big_submessage)
 {
     // 将submessage_msg_插入到full_msg_中去
     if (!append_message(full_msg_, submessage_msg_))
    {
         // Retry
         flush_and_reset();
       //增加消息接收者的guid_prefix到full_msg_消息体里面去
         add_info_dst_in_buffer(full_msg_, destination_guid_prefix);
 ​
         if (!append_message(full_msg_, submessage_msg_))
        {
             EPROSIMA_LOG_ERROR(RTPS_WRITER, "Cannot add RTPS submesage to the CDRMessage. Buffer too small");
             return false;
        }
    }
 ​
     // Messages with a submessage bigger than 64KB cannot have more submessages and should be flushed
     if (is_big_submessage)
    {
         flush();
    }
 ​
     return true;
 }

上面函数就是将submessage 插入到full_msg_中去

 static bool append_message(
         CDRMessage_t* full_msg,
         CDRMessage_t* submsg)
 {
 ------
     return CDRMessage::appendMsg(full_msg, submsg);
 ------
 }
 ​
 inline bool CDRMessage::appendMsg(
         CDRMessage_t* first,
         CDRMessage_t* second)
 {
     return(CDRMessage::addData(first, second->buffer, second->length));
 }
 ​
 inline bool CDRMessage::addData(
         CDRMessage_t* msg,
         const octet* data,
         const uint32_t length)
 {
     if (msg->pos + length > msg->max_size)
    {
         return false;
    }
 ​
     memcpy(&msg->buffer[msg->pos], data, length);
     msg->pos += length;
     msg->length += length;
     return true;
 }

这个就是把数据内容copy到buffer中去。组成一个CDRMessage。最终就是拼接成一个byte数组。

这样一个fullmessage就组装完成了,剩下的就是将fullmessage发送出去

4.2解析message

下面这个函数是接收到消息之后,对消息处理的函数

 void MessageReceiver::processCDRMsg(
         const Locator_t& source_locator,
         const Locator_t& reception_locator,
         CDRMessage_t* msg)
 {
     if (msg->length < RTPSMESSAGE_HEADER_SIZE)
    {
         EPROSIMA_LOG_WARNING(RTPS_MSG_IN, IDSTRING "Received message too short, ignoring");
         return;
    }
 ​
 #ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
     GuidPrefix_t participantGuidPrefix;
 #else
     GuidPrefix_t participantGuidPrefix = participant_->getGuid().guidPrefix;
 #endif // ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
 ​
 ······
     bool ignore_submessages = false;
 ​
    {
         std::lock_guard<eprosima::shared_mutex> guard(mtx_);
 //参数重置
         reset();
 ​
         dest_guid_prefix_ = participantGuidPrefix;
 ​
         msg->pos = 0; //Start reading at 0
 ​
         //Once everything is set, the reading begins:
         //检测消息头
         if (!checkRTPSHeader(msg))
        {
             return;
        }
 ······
 ​
         if (!ignore_submessages)
        {
             //统计数据
             notify_network_statistics(source_locator, reception_locator, msg);
        }
 ​
 ······
    }
 ​
     // Loop until there are no more submessages
     // Each submessage processing method choses the lock kind required
     bool valid;
     SubmessageHeader_t submsgh; //Current submessage header
 ​
     bool ignore_current_submessage;
 ​
     // 没有越界
     while (msg->pos < msg->length)// end of the message
    {
         CDRMessage_t* submessage = msg;
 ​
 ······
         //First 4 bytes must contain: ID | flags | octets to next header
         // 读一下submessage 的head,check一下格式
         if (!readSubmessageHeader(submessage, &submsgh))
        {
             return;
        }
 ​
         valid = true;
         uint32_t next_msg_pos = submessage->pos;
         next_msg_pos += (submsgh.submessageLength + 3u) & ~3u;
 ​
         // We ignore submessage if the source participant is to be ignored, unless the submessage king is INFO_SRC
         // which triggers a reevaluation of the flag.
         ignore_current_submessage = ignore_submessages &&
                 submsgh.submessageId != INFO_SRC;
 ​
         if (!ignore_current_submessage)
        {
 ​
             switch (submsgh.submessageId)
            {
                 case DATA:
                {
                     if (dest_guid_prefix_ != participantGuidPrefix)
                    {
                         EPROSIMA_LOG_INFO(RTPS_MSG_IN, IDSTRING "Data Submsg ignored, DST is another RTPSParticipant");
                    }
                     else
                    {
                         EPROSIMA_LOG_INFO(RTPS_MSG_IN, IDSTRING "Data Submsg received, processing.");
                         EntityId_t writerId = c_EntityId_Unknown;
                         valid = proc_Submsg_Data(submessage, &submsgh, writerId);
 #if !defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION)
                         if (valid && writerId == c_EntityId_SPDPWriter)
                        {
                             ignore_submessages = participant_->is_participant_ignored(source_guid_prefix_);
                        }
 #endif  // if !defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION)
 ​
                    }
                     break;
                }
                 case DATA_FRAG:
                     if (dest_guid_prefix_ != participantGuidPrefix)
                    {
                         EPROSIMA_LOG_INFO(RTPS_MSG_IN,
                                 IDSTRING "DataFrag Submsg ignored, DST is another RTPSParticipant");
                    }
                     else
                    {
                         EPROSIMA_LOG_INFO(RTPS_MSG_IN, IDSTRING "DataFrag Submsg received, processing.");
                         valid = proc_Submsg_DataFrag(submessage, &submsgh);
                    }
                     break;
                 case GAP:
                {
                     if (dest_guid_prefix_ != participantGuidPrefix)
                    {
                         EPROSIMA_LOG_INFO(RTPS_MSG_IN,
                                 IDSTRING "Gap Submsg ignored, DST is another RTPSParticipant...");
                    }
                     else
                    {
                         EPROSIMA_LOG_INFO(RTPS_MSG_IN, IDSTRING "Gap Submsg received, processing...");
                         valid = proc_Submsg_Gap(submessage, &submsgh);
                    }
                     break;
                }
                 case ACKNACK:
                {
                     if (dest_guid_prefix_ != participantGuidPrefix)
                    {
                         EPROSIMA_LOG_INFO(RTPS_MSG_IN,
                                 IDSTRING "Acknack Submsg ignored, DST is another RTPSParticipant...");
                    }
                     else
                    {
                         EPROSIMA_LOG_INFO(RTPS_MSG_IN, IDSTRING "Acknack Submsg received, processing...");
                         valid = proc_Submsg_Acknack(submessage, &submsgh);
                    }
                     break;
                }
                 case NACK_FRAG:
                {
                     if (dest_guid_prefix_ != participantGuidPrefix)
                    {
                         EPROSIMA_LOG_INFO(RTPS_MSG_IN,
                                 IDSTRING "NackFrag Submsg ignored, DST is another RTPSParticipant...");
                    }
                     else
                    {
                         EPROSIMA_LOG_INFO(RTPS_MSG_IN, IDSTRING "NackFrag Submsg received, processing...");
                         valid = proc_Submsg_NackFrag(submessage, &submsgh);
                    }
                     break;
                }
                 case HEARTBEAT:
                {
                     if (dest_guid_prefix_ != participantGuidPrefix)
                    {
                         EPROSIMA_LOG_INFO(RTPS_MSG_IN, IDSTRING "HB Submsg ignored, DST is another RTPSParticipant...");
                    }
                     else
                    {
                         EPROSIMA_LOG_INFO(RTPS_MSG_IN, IDSTRING "Heartbeat Submsg received, processing...");
                         valid = proc_Submsg_Heartbeat(submessage, &submsgh);
                    }
                     break;
                }
                 case HEARTBEAT_FRAG:
                {
                     if (dest_guid_prefix_ != participantGuidPrefix)
                    {
                         EPROSIMA_LOG_INFO(RTPS_MSG_IN,
                                 IDSTRING "HBFrag Submsg ignored, DST is another RTPSParticipant...");
                    }
                     else
                    {
                         EPROSIMA_LOG_INFO(RTPS_MSG_IN, IDSTRING "HeartbeatFrag Submsg received, processing...");
                         valid = proc_Submsg_HeartbeatFrag(submessage, &submsgh);
                    }
                     break;
                }
                 case PAD:
                     EPROSIMA_LOG_WARNING(RTPS_MSG_IN, IDSTRING "PAD messages not yet implemented, ignoring");
                     break;
                 case INFO_DST:
                     EPROSIMA_LOG_INFO(RTPS_MSG_IN, IDSTRING "InfoDST message received, processing...");
                     valid = proc_Submsg_InfoDST(submessage, &submsgh);
                     break;
                 case INFO_SRC:
                     EPROSIMA_LOG_INFO(RTPS_MSG_IN, IDSTRING "InfoSRC message received, processing...");
                     valid = proc_Submsg_InfoSRC(submessage, &submsgh);
 #if !defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION)
                     ignore_submessages = participant_->is_participant_ignored(source_guid_prefix_);
 #endif  // if !defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION)
                     break;
                 case INFO_TS:
                {
                     EPROSIMA_LOG_INFO(RTPS_MSG_IN, IDSTRING "InfoTS Submsg received, processing...");
                     valid = proc_Submsg_InfoTS(submessage, &submsgh);
                     break;
                }
                 case INFO_REPLY:
                     break;
                 case INFO_REPLY_IP4:
                     break;
                 default:
                     break;
            }
        }
         if (!valid || submsgh.is_last)
        {
             break;
        }
 ​
         submessage->pos = next_msg_pos;
    }
 ​
 #if !defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION)
     participant_->assert_remote_participant_liveliness(source_guid_prefix_);
 #endif // if !defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION)
 }

//这个其实就是逐一解析,CDRMessage

1.先解析header

2.之后解析submessage。针对不同的submessage,调用不同的函数来解析

这篇文章我们介绍了Message的组成部分,以及message如何拼装成byte数组。如何解析接收到的message消息。

现在我们基本把fastdds的所有的组成部分都介绍过了,下面我们进入到fastdds中比较核心的内容,发送接收消息,以及协议部分的内容。 下一篇我们介绍一下如何发送fastdds的第一条PDP消息。

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

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

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

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

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

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

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

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