FastDDS 源码解析(十九)EDP阶段处理heartbeat消息,发送acknack消息

853 阅读15分钟

第一篇 一个例子

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

第二篇fastdds的组成

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

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

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

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

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

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

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

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

第三篇组网建立连接

pdp建连

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

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

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

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

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

FastDDS 源码解析(十五)接收PDP消息(下)

FastDDS 源码解析(十六)处理PDP消息——PDP匹配

EDP建连

FastDDS 源码解析(十七)处理PDP消息——EDP匹配

FastDDS 源码解析(十八)EDP阶段发送心跳heartbeat

FastDDS 源码解析(十九)EDP阶段处理heartbeat消息,发送acknack消息

接收heartbeat可以参考FastDDS 源码解析(十四)接收PDP消息(上),比较类似,也是有一个线程不断接收socket消息,收到消息之后交给MessageReceiver

这边不再赘述接收消息部分的代码了

我们从处理heartbeat开始

1.处理heartbeat

1.1时序图

sequenceDiagram
		participant UDPChannelResource
		participant ReceiverResource
		participant MessageReceiver		
		participant StatefulReader
    participant WriterProxy
		UDPChannelResource ->> UDPChannelResource: 1.perform_listen_operation()
		UDPChannelResource ->> UDPChannelResource: 2.Receive()
		UDPChannelResource ->> ReceiverResource: 3.OnDataReceived()
		ReceiverResource ->> MessageReceiver: 4.processCDRMsg()
		MessageReceiver ->> MessageReceiver: 5.proc_Submsg_Heartbeat()
		MessageReceiver ->> StatefulReader: 6.processHeartbeatMsg()
		StatefulReader ->> WriterProxy: 7.process_heartbeat()
		WriterProxy->> WriterProxy: 8.lost_changes_update()
		WriterProxy->> WriterProxy: 9.missing_changes_update()
		WriterProxy->> WriterProxy: 10.heartbeat_response_->restart_timer

1.UDPChannelResource 跑一个线程 干了

a.接收消息 见 2

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

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

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

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

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

如果是heartbeat消息的话,调用proc_Submsg_Heartbeat ,这个heartbeat消息,是EDP消息

5.proc_Submsg_Heartbeat 解析各个字节的消息,解析完成后主要是调用StatefulReader的processHeartbeatMsg

6.processHeartbeatMsg

在这里找到对应WriterProxy,调用WriterProxy的函数process_heartbeat来处理消息

7.WriterProxy的函数process_heartbeat 主要干了3件事

a.调用lost_changes_update 见8

b.调用missing_changes_update 见9

c.heartbeat_response_->restart_timer 见10

步骤8:调用lost_changes_update,参数是first_seq,这个参数是heartbeat发送过来的,表示writer的history中最小的seq,低于这个seq已经不能发送给这个Reader了,这时候我们就知道这个StatefulReader永远失去的msg的数量 。

步骤9:调用missing_changes_update,参数是last_seq, 这个参数是heartbeat发送过来的,表示writer的history中最大的seq,这个函数能够知道这个StatefulReader还有多少msg能够收到,但是却没有收到 。

步骤10:heartbeat_response_->restart_timer,这样发送一个acknack消息

1.2源码解析

步骤1-4 参考FastDDS 源码解析(十四)接收PDP消息(上) 2.2部分

步骤5:

 bool MessageReceiver::proc_Submsg_Heartbeat(
         CDRMessage_t* msg,
         SubmessageHeader_t* smh) const
 {
     eprosima::shared_lock<eprosima::shared_mutex> guard(mtx_);
 ​
     bool endiannessFlag = (smh->flags & BIT(0)) != 0;
     bool finalFlag = (smh->flags & BIT(1)) != 0;
     bool livelinessFlag = (smh->flags & BIT(2)) != 0;
     //Assign message endianness
     if (endiannessFlag)
     {
         msg->msg_endian = LITTLEEND;
     }
     else
     {
         msg->msg_endian = BIGEND;
     }
 ​
     GUID_t readerGUID;
     GUID_t writerGUID;
     readerGUID.guidPrefix = dest_guid_prefix_;
     CDRMessage::readEntityId(msg, &readerGUID.entityId);
     writerGUID.guidPrefix = source_guid_prefix_;
     CDRMessage::readEntityId(msg, &writerGUID.entityId);
     SequenceNumber_t firstSN;
     SequenceNumber_t lastSN;
     CDRMessage::readSequenceNumber(msg, &firstSN);
     CDRMessage::readSequenceNumber(msg, &lastSN);
 ​
     SequenceNumber_t zeroSN;
     ------
     uint32_t HBCount;
     //读取HBCount,这个数值表示是第几个心跳
     if (!CDRMessage::readUInt32(msg, &HBCount))
     {
         EPROSIMA_LOG_WARNING(RTPS_MSG_IN, IDSTRING "Unable to read heartbeat count from heartbeat message");
         return false;
     }
 ​
     //Look for the correct reader and writers:
     findAllReaders(readerGUID.entityId,
             [&writerGUID, &HBCount, &firstSN, &lastSN, finalFlag, livelinessFlag](RTPSReader* reader)
             {
                 reader->processHeartbeatMsg(writerGUID, HBCount, firstSN, lastSN, finalFlag, livelinessFlag);
             });
 ​
     return true;
 }

从消息里面读取数据

HBCount 表示第几个心跳

firstSN 表示history中第一个消息的id

lastSN 表示history中最后一个消息的id

finalFlag 和livelinessFlag 是两个关键参数,这个后续会讲到,关系到acknack的发送

步骤6

 bool StatefulReader::processHeartbeatMsg(
         const GUID_t& writerGUID,
         uint32_t hbCount,
         const SequenceNumber_t& firstSN,
         const SequenceNumber_t& lastSN,
         bool finalFlag,
         bool livelinessFlag)
 {
     
     ------
     if (acceptMsgFrom(writerGUID, &writer) && writer)
     {
         bool assert_liveliness = false;
         int32_t current_sample_lost = 0;
         if (writer->process_heartbeat(
                     hbCount, firstSN, lastSN, finalFlag, livelinessFlag, disable_positive_acks_, assert_liveliness,
                     current_sample_lost))
         {
             //删除小于firstSN的message
             mp_history->remove_fragmented_changes_until(firstSN, writerGUID);
 ​
             if (0 < current_sample_lost)
             {
                 if (getListener() != nullptr)
                 {
                     getListener()->on_sample_lost((RTPSReader*)this, current_sample_lost);
                 }
             }
 ​
             // Maybe now we have to notify user from new CacheChanges.
             NotifyChanges(writer);
 ​
             // Try to assert liveliness if requested by proxy's logic
             if (assert_liveliness)
             {
                 if (liveliness_lease_duration_ < c_TimeInfinite)
                 {
                     if (liveliness_kind_ == MANUAL_BY_TOPIC_LIVELINESS_QOS ||
                             writer->liveliness_kind() == MANUAL_BY_TOPIC_LIVELINESS_QOS)
                     {
                         auto wlp = this->mp_RTPSParticipant->wlp();
                         if ( wlp != nullptr)
                         {
                             lock.unlock(); // Avoid deadlock with LivelinessManager.
                             wlp->sub_liveliness_manager_->assert_liveliness(
                                 writerGUID,
                                 liveliness_kind_,
                                 liveliness_lease_duration_);
                         }
                         else
                         {
                             EPROSIMA_LOG_ERROR(RTPS_LIVELINESS, "Finite liveliness lease duration but WLP not enabled");
                         }
                     }
                 }
             }
         }
 ​
         return true;
     }
 ​
     return false;
 }

主要干了这几件事:

1.调用了WriterProxy的process_heartbeat函数

2.调用NotifyChanges,这个什么意思哪,就是告知history的使用者,我们这边有些message 不保存了,你来取走

3.调用wlp->sub_liveliness_manager_->assert_liveliness,意思是现在收到writer的一个消息,writer是存活的

步骤7:

 bool WriterProxy::process_heartbeat(
         uint32_t count,
         const SequenceNumber_t& first_seq,
         const SequenceNumber_t& last_seq,
         bool final_flag,
         bool liveliness_flag,
         bool disable_positive,
         bool& assert_liveliness,
         int32_t& current_sample_lost)
 {
 #ifdef SHOULD_DEBUG_LINUX
     assert(get_mutex_owner() == get_thread_id());
 #endif // SHOULD_DEBUG_LINUX
 ​
     assert_liveliness = false;
     if (state_ != StateCode::STOPPED && last_heartbeat_count_ < count)
     {
         // If it is the first heartbeat message, we can try to cancel initial ack.
         // TODO: This timer cancelling should be checked if needed with the liveliness implementation.
         // To keep PARTICIPANT_DROPPED event we should add an explicit participant_liveliness QoS.
         // This is now commented to avoid issues #457 and #155
         // initial_acknack_->cancel_timer();
         
         //last_heartbeat_count_ 收到的最新心跳的数量
         last_heartbeat_count_ = count;
         // 看了一下,缺了几个message
         current_sample_lost = lost_changes_update(first_seq);
         // 更新一下writer messageid的上限
         missing_changes_update(last_seq);
         //final flag 如果心跳中有final flag,reader 不一定需要发送acknack消息,如果没有final flag reader一定要发送ackanck消息
         heartbeat_final_flag_.store(final_flag);
 ​
         //Analyze whether a acknack message is needed:
         if (!is_on_same_process_)
         {
             if (!final_flag)
             {
                 if (!disable_positive || are_there_missing_changes())
                 {
                     //如果没有设置finalflag,如果有缺失的消息,或者允许positive acknack,启动heartbeat_response_ 的TimedEvent,来发送acknack消息
                     heartbeat_response_->restart_timer();
                 }
             }
             else if (final_flag && !liveliness_flag)
             {
                 //如果设置了finalflag,同时不是liveliness的包,有缺失的message 的时候才会发送acknack消息
                 if (are_there_missing_changes())
                 {
                     heartbeat_response_->restart_timer();
                 }
             }
             else
             {
                 assert_liveliness = liveliness_flag;
             }
         }
         else
         {
             assert_liveliness = liveliness_flag;
         }
 ​
         if (!received_at_least_one_heartbeat_)
         {
             current_sample_lost = 0;
             received_at_least_one_heartbeat_ = true;
         }
 ​
         return true;
     }
 ​
     return false;
 }

我们看一下这块逻辑

1.调用lost_changes_update,参数是first_seq,这个参数是heartbeat发送过来的,表示writer的history中最小的seq,低于这个seq已经不能发送给这个Reader了,这时候我们就知道永远失去的msg的数量

2.调用missing_changes_update,参数是last_seq, 这个参数是heartbeat发送过来的,表示writer的history中最大的seq,这个函数能够知道我们还有多少msg能够收到,但是却没有收到

3.如果是跨设备通信

final_flag && liveliness_flag 表示只是收到一个liveliness的消息,不会发送acknack

final_flag && !liveliness_flag 如果有缺失的消息,需要启动heartbeat_response_,发送acknack消息

!final_flag 并且! disable_positive,如果有缺失的消息,需要启动heartbeat_response_,发送acknack消息

步骤8:

 int32_t WriterProxy::lost_changes_update(
         const SequenceNumber_t& seq_num)
 {
 #ifdef SHOULD_DEBUG_LINUX
     assert(get_mutex_owner() == get_thread_id());
 #endif // SHOULD_DEBUG_LINUX
 ​
     EPROSIMA_LOG_INFO(RTPS_READER, guid().entityId << ": up to seq_num: " << seq_num);
     int32_t current_sample_lost = 0;
 ​
     // Check was not removed from container.
     // 如果writer端最小的seq比changes_from_writer_low_mark_+1大
     if (seq_num > (changes_from_writer_low_mark_ + 1))
     {
         // Remove all received changes with a sequence lower than seq_num
         ChangeIterator it = std::lower_bound(changes_received_.begin(), changes_received_.end(), seq_num);
         if (!changes_received_.empty())
         {
             //这个temp表示的是 changes_received_中第一个message到changes_from_writer_low_mark_的长度
             uint64_t tmp = (*changes_received_.begin()).to64long() - (changes_from_writer_low_mark_.to64long() + 1);
             //
             auto distance = std::distance(changes_received_.begin(), it);
             tmp += seq_num.to64long() - (*changes_received_.begin()).to64long() - distance;
             current_sample_lost = tmp > static_cast<uint64_t>(std::numeric_limits<int32_t>::max()) ?
                     std::numeric_limits<int32_t>::max() : static_cast<int32_t>(tmp);
         }
         else
         {
             uint64_t tmp = seq_num.to64long() - (changes_from_writer_low_mark_.to64long() + 1);
             current_sample_lost = tmp > static_cast<uint64_t>(std::numeric_limits<int32_t>::max()) ?
                     std::numeric_limits<int32_t>::max() : static_cast<int32_t>(tmp);
         }
         changes_received_.erase(changes_received_.begin(), it);
 ​
         // Update low mark
         changes_from_writer_low_mark_ = seq_num - 1;
         if (changes_from_writer_low_mark_ > max_sequence_number_)
         {
             max_sequence_number_ = changes_from_writer_low_mark_;
         }
 ​
         // Next could need to be removed.
         cleanup();
     }
 ​
     return current_sample_lost;
 }

changes_from_writer_low_mark_ 这个参数的意思是reader端的history中连续的消息的最大的seq

seq_num > (changes_from_writer_low_mark_ + 1) 这个seq_num 就是heartbeat发过来的first_seq,这个first_seq表示的是发送heartbeat的writer中现在有 [first_seq,last_seq]这个范围的message能够被取到。

当seq_num > (changes_from_writer_low_mark_ + 1) 的时候表示的是在reader端接收的数据中永远丢失了一些message。

current_sample_lost 就算出丢失了多少message。

将无用的缓存删除,同时将changes_from_writer_low_mark_ ,max_sequence_number_更新

步骤9:

 void WriterProxy::missing_changes_update(
         const SequenceNumber_t& seq_num)
 {
 #ifdef SHOULD_DEBUG_LINUX
     assert(get_mutex_owner() == get_thread_id());
 #endif // SHOULD_DEBUG_LINUX
 ​
     EPROSIMA_LOG_INFO(RTPS_READER, guid().entityId << ": changes up to seq_num: " << seq_num << " missing.");
 ​
     // Check was not removed from container.
     if (seq_num > changes_from_writer_low_mark_)
     {
         if (seq_num > max_sequence_number_)
         {
             max_sequence_number_ = seq_num;
         }
     }
 }

更新一下max_sequence_number_,这个值是发送端StatefulWriter中消息seq的最大值。

heartbeat_response_的TimedEvent,需要执行的是用来发送acknack 的消息的函数

下面这个函数就是heartbeat_response_的执行函数

 void WriterProxy::perform_heartbeat_response()
 {
     StateCode expected = StateCode::IDLE;
     if (!state_.compare_exchange_strong(expected, StateCode::BUSY))
     {
         // Stopped from another thread -> abort
         return;
     }
 ​
     reader_->send_acknack(this, this, heartbeat_final_flag_.load());
 ​
     expected = StateCode::BUSY;
     state_.compare_exchange_strong(expected, StateCode::IDLE);
 }

调用了StatefulReader的send_acknack函数

 void StatefulReader::send_acknack(
         const WriterProxy* writer,
         const SequenceNumberSet_t& sns,
         RTPSMessageSenderInterface* sender,
         bool is_final)
 {
 ​
     std::unique_lock<RecursiveTimedMutex> lock(mp_mutex);
 ​
     if (!writer->is_alive())
     {
         return;
     }
 ​
     if (writer->is_on_same_process())
     {
         return;
     }
 ​
     acknack_count_++;
 ​
 ​
     EPROSIMA_LOG_INFO(RTPS_READER, "Sending ACKNACK: " << sns);
 ​
     RTPSMessageGroup group(getRTPSParticipant(), this, sender);
     group.add_acknack(sns, acknack_count_, is_final);
 }

group.add_acknack这个我们参考之前的发送消息部分FastDDS 源码解析(十七)处理PDP消息——EDP匹配,发送的流程都是一样的只是内容改成了acknack

这里面有个比较有趣的数据结构:

SequenceNumberSet_t 这个对象最终会转化为一个位图,具体见3.3节部分

1.3 acknack的抓包

image-20241024163451431.png

这是我们抓包acknack的详细内容

readerEntityID:ENTITYID_BUILTIN_PUBLICATIONS_READER

writerEntityID:ENTITYID_BUILTIN_PUBLICATIONS_WRITER

这个表示的是发送方是ENTITYID_BUILTIN_PUBLICATIONS_READER,接收方是ENTITYID_BUILTIN_PUBLICATIONS_WRITER

readerSNState:表示的是现在reader向writer请求什么消息。

分为bitmapBase:1这儿是1,可以表示一个64位的int

numBits取值范围是 1-256,在这儿占用4个字节

bitmap:1 长度可变的, 最多占用32个字节,最少4个字节,这个占用字节的长短是numBits决定的

bitmap是一个位图,bitmapBase是一个初始的数据,在这个消息中,bitmapBase=1,表示我们现在需要 seqnumber = 1开始的第1个消息。

最多可以表示 256个消息,就是从 seqnumber = bitmapBase开始的第1,2,3------256个message。用256位(32个字节)的数据表示。每一位表示一条消息。

当numBits = 256的时候,可以表示256个message。那么bitmap就占用256位。

当numBits = 1的时候,可以表示1个message。那么bitmap就占用32位。

Count 表明这是这个Writer 发送给 Reader的第几条acknack消息

这个acknack消息就是告诉Writer,我们这边现在需要writer发送这些消息。

1.4heartbeat 和 acknack的作用

heartbeat 主要是writer告诉reader,我这边history中有这些消息范围的消息可以被获取。

reader收到消息之后,查看自己的history,发送acknack告诉writer,我现在有这些消息还没有收到,writer收到消息之后就给reader发送相应的消息。

2.EDP消息模型

这是我们用tcpdump抓的消息:

image-20240124153844456.png

我们看一下EDP是如何工作的。

sequenceDiagram
		participant ParticipantA		
		participant ParticipantB
			
		ParticipantA ->> ParticipantB: 1.ParticipantA发送PDP组播消息(序号71的消息)
		ParticipantB ->> ParticipantA: 2.ParticipantB收到ParticipantA发送的PDP消息,马上发送一条PDP消息(序号78的消息)
		ParticipantB ->> ParticipantA: 3.ParticipantB收到ParticipantA发送的PDP消息,匹配EDP后,马上发送3条EDP heartbeat消息(序号79,80,81的消息)
		ParticipantA ->> ParticipantB: 4.ParticipantA收到ParticipantB发送的PDP消息后,马上发送PDP消息(序号85的单播消息,87的组播消息)
		ParticipantA ->> ParticipantB: 5.ParticipantA收到ParticipantB发送的PDP消息,匹配EDP后,马上发送3条EDP heartbeat消息(序号91,94,97的消息)
		ParticipantA ->> ParticipantB: 6.ParticipantA收到ParticipantB发送的EDP heartbeat消息后,马上发送acknack消息(序号100,103,107的消息)
		ParticipantB ->> ParticipantA: 7.ParticipantB收到ParticipantA发送的EDP heartbeat消息后,马上发送acknack消息(序号109,110,116的消息)

1序号71 的pdp 消息 是 192.168.49.77的ip的 ParticipantA 发送给 239.255.0.1的pdp 多播消息

2.192.168.49.1的participantB 收到了这个多播PDP消息,马上发送pdp 消息(上面我们已经说到相关逻辑了,在收到pdp消息之后会发送发一条pdp消息出去)

我们可以看一下序号 78 的消息,这条消息是 192.168.49.1的 participantB发送给 192.168.49.77的participantA的pdp消息

其实192.168.49.1的 participantB 还发送一条发送给 239.255.0.1的pdp 多播消息

我们的tcpdump 只抓到了 发送地址 或 接收地址是 192.168.49.77的消息,所以,这条多播消息,我们没有抓到

3.192.168.49.1的participantB 收到了这个多播PDP消息,完成EDP,匹配,然后这些EDP,WLP节点发送heartbeat给EDP,WLP节点,

一共发送了3条heartbeat消息。(序号79,80,81的消息)

image-20241024154619592.png

我们看一下这张图,这三个heartbeat 是SEDPPubWriter,SEDPSubWriter,RTPSMsgWriter发送给SEDPPubReader,SEDPSubReader,RTPSMsgReader的heartbeat

如图:是消息79对应的抓包,可以看到是SEDPPubWriter发送给SEDPPubReader的消息。这个heartbeat表示SEDPPubWriter里面有一条消息(lastSeqNumber是1)

image-20241120111500610.png

如图:是消息80对应的抓包,可以看到是SEDPSubWriter发送给SEDPSubReader的消息。消息80对应的抓包表明SEDPSubWriter是没有消息的(lastSeqNumber是0)

image-20241120111724201.png

79和80是有区别的,消息79对应的抓包表明SEDPPubWriter里面有一条消息(lastSeqNumber是1),消息80对应的抓包表明SEDPSubWriter是没有消息的(lastSeqNumber是0)什么意思那,就是此时ParticipantA这一侧已经创建了一个PubWriter(UserWriter),然后SEDPPubWriter需要将这个消息告诉SEDPPubReader,只有SEDPPubWriter将这个消息传递给SEDPPubReader之后,如果ParticipantB这一侧已经创建了一个UserReader,那么UserWriter才能和UserReader通信。

4.192.168.49.77的participantA收到PDP消息之后, 发送给 239.255.0.1的PDP多播消息(序号87的消息)和发送给192.168.49.1的PDP单播消息(序号85的消息)

5.192.168.49.77的participantA收到PDP消息之后,完成EDP,匹配,然后这些EDP,WLP节点发送heartbeat给EDP,WLP节点,

一共发送了3条heartbeat消息。这三个heartbeat 是SEDPPubWriter,SEDPSubWriter,RTPSMsgWriter发送给SEDPPubReader,SEDPSubReader,RTPSMsgReader的heartbeat。(序号91,94,97的消息)

如图:是序号为91的消息对应的抓包,是SEDPPubWriter发送给SEDPPubReader的heart消息,这条消息告诉SEDPPubReader,SEDPPubWriter这边没有消息

image-20241120155827368.png

如图:是序号为94的消息对应的抓包,是SEDPSubWriter发送给SEDPSubReader的heart消息,这条消息告诉SEDPSubReader,SEDPSubWriter这边有一条消息。

image-20241120155915460.png

6.192.168.49.77的participantA收到heartbeat后,发送3条acknack消息。分别是SEDPPubReader,SEDPSubReader,RTPSMsgReader发送给SEDPPubWriter,SEDPSubWriter,RTPSMsgWriter的acknack消息。(序号100,103,107的消息)

如图:是序号为100的消息对应的抓包,是SEDPPubReader发送给SEDPPubWriter的acknack消息,这条消息是对序列号为79的消息的回应。79消息说我这边有一条消息1,然后100消息说我这边这条消息1还没收到

image-20241120112805827.png

如图:是序号为103的消息对应的抓包,是SEDPSubReader发送给SEDPSubWriter的acknack消息,这条消息是对序列号为80的消息的回应。消息80说,我这边还没有消息,消息103说,我期待一条消息,如果你有请发送给我

image-20241120143710196.png

7.192.168.49.1的participantB收到heartbeat后,发送3条acknack消息。分别是SEDPPubReader,SEDPSubReader,RTPSMsgReader发送给SEDPPubWriter,SEDPSubWriter,RTPSMsgWriter的acknack消息。(序号109,110,116的消息)

如图:是消息109对应的抓包,可以看到是SEDPPubReader发送给SEDPPubWriter的消息。消息109是对消息91的回应 ,消息91说,我这边还没有消息,消息109说,我期待一条消息,如果你有请发送给我

image-20241120144838689.png

如图:是消息110对应的抓包,可以看到是SEDPSubReader发送给SEDPSubWriter的消息。这条消息是对序列号为94的消息的回应。94消息说我这边有一条消息1,然后110消息说我这边这条消息1还没收到。

image-20241120145028895.png

ParticipantB这一侧已经创建了一个UserReader,然后SEDPSubWriter需要将这个消息告诉SEDPSubReader,只有SEDPSubWriter将这个消息传递给SEDPSubReader之后,同时ParticipantA侧创建的UserWriter,已经同步过来了,那么UserWriter才能和UserReader通信。

第一篇 一个例子

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

第二篇fastdds的组成

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

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

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

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

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

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

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

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

第三篇组网建立连接

pdp建连

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

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

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

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

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

FastDDS 源码解析(十五)接收PDP消息(下)

FastDDS 源码解析(十六)处理PDP消息——PDP匹配

EDP建连

FastDDS 源码解析(十七)处理PDP消息——EDP匹配

FastDDS 源码解析(十八)EDP阶段发送心跳heartbeat

FastDDS 源码解析(十九)EDP阶段处理heartbeat消息,发送acknack消息