车载消息中间件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的结构:
我们看到一个RTPS消息主要由消息头(header),HeaderExtention和子消息(submessage)三部分组成。
这里面HeaderExtention,字面意思就是header的扩展,其实是个submessage。这个HeaderExtention是个可选的配置。
2.winshark抓的包
我们通过winshark抓个包看一下
这是一个完整的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
每个子消息有每个子消息的结构。
SubmessageKind | submessage的类型 |
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的组成部分:
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