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

车载消息中间件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消息(中)FastDDS 源码解析(十二)发送第一条PDP消息(下)---异步发送中介绍了通过flowcontroller发送消息

这一篇我们介绍一下通过datasharing_delivery发送跨进程消息

1.datashare简介

datashare就是数据共享,包括进程间数据共享和进程内数据共享。fastdds的进程间数据发送使用的是信号量和共享内存的方式,实现了zero-copy的方式。从我个人的角度看这块内容比较鸡肋,在大部分的使用场景上这块内容不会被用到。总体而言:fastdds 跨进程通信这块用处不多,耗费的资源较多,性价比不高。

1.1fastdds跨进程实现的简介

fastdds 跨进程的主要作用就是实现消息的共享。

面对的业务场景:

1在同一台机器上,如果有多个fastdds的进程,fastdds进程之间互相发送消息。只需要使用共享内存,那么不需要把消息的内容传递给其他进程,就能实现消息的发送,就是将消息放入共享内存中。两个进程能同时使用这块内存空间,实现了zero-copy。

2.在同一台机器上,如果有多个fastdds的进程,那么从其他机器上发送的消息,如果想要发送给同一机器上的多个fastdds进程,这多个fastdds进程只需要保存1份消息就可以了。也是通过共享内存实现的,实现了zero-copy。

本质上就是以时间换空间,以cpu的占用换取空间的减少。

zero-copy

我们来看一下fastdds 如何实现跨进程通信,以及如何通过共享内存实现zero-copy的。

zero-copy 就是不copy数据实现数据的传输。

严格意义上来讲 fastdds的所谓 zero-copy其实是一次内存copy,socket收到消息,之后需要将数据解析,然后copy到共享内存上,然后各个进程就能实现,所以是一次copy,但对于其他进程而言,就是zero-copy。

对于跨进程通信的 管道 消息队列 socket来说都需要2次内存copy。

信号量则一般不用于传输大数据量。

fastdds跨进程

fastdds跨进程消息通信是 共享内存+信号量结合的方式来实现的

如图所示:App A (进程a)申请一块共享内存,然后映射到本地内存,这块共享内存就是和App B(进程 B)共用的,可以往里面读写数据,这就涉及到一个问题,如何让 APP A 和 APP B进行进程间同步,信号量就完成进程间同步的工作,比如加锁,释放锁,通知等等就是由信号量完成的。

1.2什么是共享内存

从网上找了一张共享内存的大概示意图:

首先 由一方向OS提出申请,此时OS在内核中创建一段物理内存用于进程之间的通信,然后将该共享内存的物理地址通过页表 分别映射到两个进程的虚拟内存的共享区,此时两个进程就能经过虚拟内存再通过页表映射 进而找到共享内存,从而看到同一份资源,然后相互通信。

能够达到节省空间的目的。

1.3fastdds跨进程数据传输的优缺点

在嵌入式使用场景中,fastdds 传输的数据,大部分都是高频小数据量的数据,本身需要大量存储的情况比较少。 就是说能够节省的存储资源比较少。

通过前面的代码分析我们可以看到fastdds其实是个相对比较大型的架构(PDP,EDP,WLP等等),在同一台机器上布置多个节点,开销比较大。所以没有必要在同一台机器上布置多个fastdds,如果有需要的话,以一个fastdds进程接收数据,然后分发给多个其他进程。如果数据量比较大的话,可以再适量增加fastdds的节点。这样更能节省开销。

所以以我多年的代码经验来看,整个这块的设计是比较鸡肋的。但是这块内容是fastdds一个相对比较重要的功能,所以还是需要介绍一下。

我们看一下这张图:

fastdds 就是处于某种比较杂乱的状态,同一台设备上有好几个fastdds的应用程序,各个应用程序可以对外通信,这样即使是同一台设备两个应用程序时间也都要走fastdds的相关通信(跨进程通信)。每个应用都需要集成一遍fastdds,造成了资源的浪费。由于同一台设备上使用了datasharing,数据内存能够得到一定的节省,但是运行内存是增加的(都需要运行fastdds)。

我所设想的设计图:

我们看一下这张图:每个设备只有一个fastdds 节点作为对外节点,其他应用程序对外交流都通过这个对外节点交流,这样节省了资源,fastdds 不用在每个应用程序都部署。设备内的应用程序交互可以使用传统的进程间通信进行。节省了资源,比如对于app B来说就不用fastdds 的相关代码,对于App D ,APPE来说也不用相关代码了。

除非有非常大的数据请求,大部分的应用场景都能使用这样的架构来解决。

2.datashare源码解析

我们看一下fastdds 跨进程通信,共享内存的实现:

2.1接收端的实现

sequenceDiagram participant RTPSReader participant DataSharingNotification participant DataSharingListener RTPSReader ->> RTPSReader: 1.init RTPSReader ->> DataSharingNotification: 2.create_notification DataSharingNotification ->> DataSharingNotification: 3.create_and_init_notification DataSharingNotification ->> DataSharingNotification: 4.create_and_init_shared_segment_notification RTPSReader ->> DataSharingListener: 5.new RTPSReader ->> DataSharingListener: 6.start

1.RTPSReader初始化的时候调用init函数,这个函数主要是初始化共享内存相关的代码

主要干了3件事

a.创建DataSharingNotification 见2

b.根据DataSharingNotification创建DataSharingListener 见5

c.DataSharingListener的start函数 见6

2.调用DataSharingNotification的create_notification函数创建DataSharingNotification,create_notification调用了create_and_init_notification来创建共享内存

3.create_and_init_notification根据传进来的guid 和 shared_dir 来调用create_and_init_shared_segment_notification创建共享内存,如果shared_dir为空创建共享内存,如果不为空创建共享文件

4.create_and_init_shared_segment_notification创建并初始化内存,这个其实分为2块,一块是共享的内存,一块是对共享内存的管理,这个是用信号量实现的,比如我们改变了内存,那么需要通过信号量来通知其他进程,内存改变了,可以来处理新的数据了

5.new了一个DataSharingListener,根据之前我们创建的DataSharingNotification,来创建DataSharingListener

6.调用DataSharingListener的start函数,这儿启动了一个监听线程,监听共享内存的变化

arduino 复制代码
 enum DataSharingKind : fastrtps::rtps::octet
 {
     /**
      * Automatic configuration.
      * DataSharing will be used if requirements are met.
      */
     AUTO = 0x01,
     /**
      * Activate the use of DataSharing.
      * Entity creation will fail if requirements for DataSharing are not met
      */
     ON = 0x02,
     /**
      * Disable the use of DataSharing
      */
     OFF = 0x03
 };

每个Reader或者Writer 都可以配置DataSharingKind。

DataSharingKind分为3类:

on 就是使用数据共享

off 就是不使用数据共享

auto 就是如果当前的Topic被配置成数据共享,那么这个reader 或者writer 就可以使用数据共享,如果topic没有配置成数据共享,那么当前的reader或者writer就不能使用数据共享。

默认是off。就是默认是不开启数据共享的。

步骤1.RTPSReader中的init函数

c 复制代码
 void RTPSReader::init(
         const std::shared_ptr<IPayloadPool>& payload_pool,
         const std::shared_ptr<IChangePool>& change_pool,
         const ReaderAttributes& att)
 {
     payload_pool_ = payload_pool;
     change_pool_ = change_pool;
     fixed_payload_size_ = 0;
     if (mp_history->m_att.memoryPolicy == PREALLOCATED_MEMORY_MODE)
    {
         fixed_payload_size_ = mp_history->m_att.payloadMaxSize;
    }
 ​
     if (att.endpoint.data_sharing_configuration().kind() != OFF)
    {
         using std::placeholders::_1;
         std::shared_ptr<DataSharingNotification> notification =
                 DataSharingNotification::create_notification(
             getGuid(), att.endpoint.data_sharing_configuration().shm_directory());
         if (notification)
        {
             is_datasharing_compatible_ = true;
             datasharing_listener_.reset(new DataSharingListener(
                         notification,
                         att.endpoint.data_sharing_configuration().shm_directory(),
                         att.matched_writers_allocation,
                         this));
 ​
             // We can start the listener here, as no writer can be matched already,
             // so no notification will occur until the non-virtual instance is constructed.
             // But we need to stop the listener in the non-virtual instance destructor.
             datasharing_listener_->start();
        }
    }
 }

主要干了3件事

1.创建DataSharingNotification

2.根据DataSharingNotification创建DataSharingListener

3.DataSharingListener的start函数

步骤2.创建notification

c 复制代码
 std::shared_ptr<DataSharingNotification> DataSharingNotification::create_notification(
         const GUID_t& reader_guid,
         const std::string& shared_dir)
 {
     std::shared_ptr<DataSharingNotification> notification = std::make_shared<DataSharingNotification>();
     if (!notification->create_and_init_notification(reader_guid, shared_dir))
    {
         notification.reset();
    }
     return notification;
 }

步骤3.调用了create_and_init_notification

php 复制代码
 bool DataSharingNotification::create_and_init_notification(
         const GUID_t& reader_guid,
         const std::string& shared_dir)
 {
     if (shared_dir.empty())
    {
         return create_and_init_shared_segment_notification<fastdds::rtps::SharedMemSegment>(reader_guid,
                        shared_dir);
    }
     else
    {
         return create_and_init_shared_segment_notification<fastdds::rtps::SharedFileSegment>(reader_guid,
                        shared_dir);
    }
 }

调用create_and_init_shared_segment_notification

这里面分为2种情况:共享内存和共享文件,一个将内存映射到各自进程,一个将文件映射到各自进程

根据配置选择不同的方式。

步骤4.create_and_init_shared_segment_notification

ini 复制代码
 template <typename T>
     bool create_and_init_shared_segment_notification(
             const GUID_t& reader_guid,
             const std::string& shared_dir)
    {
         segment_id_ = reader_guid;
         //生成共享内存的名字
         segment_name_ = generate_segment_name(shared_dir, reader_guid);
         std::unique_ptr<T> local_segment;
 ​
         try
        {
             uint32_t per_allocation_extra_size = T::compute_per_allocation_extra_size(
                 alignof(Notification), DataSharingNotification::domain_name());
             uint32_t segment_size = static_cast<uint32_t>(sizeof(Notification)) + per_allocation_extra_size;
 ​
             //Open the segment
             T::remove(segment_name_);
 ​
             local_segment.reset(
                 new T(boost::interprocess::create_only,
                 segment_name_,
                 segment_size + T::EXTRA_SEGMENT_SIZE));
        }
   ------
 ​
         try
        {
             // Alloc and initialize the Node
             notification_ = local_segment->get().template construct<Notification>("notification_node")();
             notification_->new_data.store(false);
        }
         
   ------
         segment_ = std::move(local_segment);
         owned_ = true;
         return true;
    }
 ​

创建并初始化内存

步骤5.创建DataSharingListener

c 复制代码
 DataSharingListener::DataSharingListener(
         std::shared_ptr<DataSharingNotification> notification,
         const std::string& datasharing_pools_directory,
         ResourceLimitedContainerConfig limits,
         RTPSReader* reader)
    : notification_(notification)
    , is_running_(false)
    , reader_(reader)
    , writer_pools_(limits)
    , writer_pools_changed_(false)
    , datasharing_pools_directory_(datasharing_pools_directory)
 {
 }

步骤6.start DataSharingListener

arduino 复制代码
 void DataSharingListener::start()
 {
     std::lock_guard<std::mutex> guard(mutex_);
 ​
     // Check the thread
     bool was_running = is_running_.exchange(true);
     if (was_running)
    {
         return;
    }
 ​
     // Initialize the thread
     listening_thread_ = new std::thread(&DataSharingListener::run, this);
 }

start就是启动一个线程监听共享内存的变动,处理新数据

这是这个线程中运行的函数:

rust 复制代码
 void DataSharingListener::run()
 {
     //获取锁  
     std::unique_lock<Segment::mutex> lock(notification_->notification_->notification_mutex, std::defer_lock);
   //如果is_running_是个标志变量,外部可以通过这个变量关闭这个线程。
     while (is_running_.load())
    { 
         lock.lock();
         //查看一下有没有新数据过来
         notification_->notification_->notification_cv.wait(lock, [&]
                {
                     return !is_running_.load() || notification_->notification_->new_data.load();
                });
 ​
         lock.unlock();
 ​
         if (!is_running_.load())
        {
             // Woke up because listener is stopped
             return;
        }
 ​
         do
        {
            //处理新数据
             process_new_data();
 ​
             // If some writer added new data, there may be something to read.
             // If there were matching/unmatching, we may not have finished our last loop
        } while (is_running_.load() &&
        (notification_->notification_->new_data.load() || writer_pools_changed_.load(std::memory_order_relaxed)));
    }
 }

通过启动一个线程来等待,这个线程不断循环等待。首先是监听信号量的变化,没有变化就等待,有变化,就调用process_new_data处理新数据

fastdds 使用了c++的boost来实现共享内存,底层使用了共享内存+信号量。就是共享内存存储数据,信号量来对这些数据进行管理。

这是处理数据的函数process_new_data

scss 复制代码
 void DataSharingListener::process_new_data ()
 {
     
 ​
     std::unique_lock<std::mutex> lock(mutex_);
 ​
     // It is safe to 'forget' any change now
     notification_->notification_->new_data.store(false);
     // All places where this is set to true is locked by the same mutex, memory_order_relaxed is enough
     writer_pools_changed_.store(false, std::memory_order_relaxed);
 ​
     // Loop on the writers looking for data not read yet
     // 从writer_pools_中找到writer,处理新数据
     for (auto it = writer_pools_.begin(); it != writer_pools_.end(); ++it)
    {
         //First see if we have some liveliness asertion pending
         bool liveliness_assertion_needed = false;
         // liveliness 相关
         uint32_t new_assertion_sequence = it->pool->last_liveliness_sequence();
         if (it->last_assertion_sequence != new_assertion_sequence)
        {
             liveliness_assertion_needed = true;
             it->last_assertion_sequence = new_assertion_sequence;
        }
 ​
         // Take the pool to free the lock
         std::shared_ptr<ReaderPool> pool = it->pool;
         lock.unlock();
 ​
         if (liveliness_assertion_needed)
        {
             reader_->assert_writer_liveliness(pool->writer());
        }
 ​
         uint64_t last_payload = pool->end();
         bool has_new_payload = true;
         while (has_new_payload)
        {
             CacheChange_t ch;
             SequenceNumber_t last_sequence = c_SequenceNumber_Unknown;
             //从共享内存中获取CacheChange_t对象
             pool->get_next_unread_payload(ch, last_sequence, last_payload);
             has_new_payload = ch.sequenceNumber != c_SequenceNumber_Unknown;
 ​
             if (has_new_payload && ch.sequenceNumber > SequenceNumber_t(0, 0))
            {
                 
               //gap消息是 Writer发送给Reader,标识HistoryCache中的一些Change不再可用,也不会再发给Reader
                 //根据收到的消息的sequenceNumber,来确定是否有些消息 已经永久丢失,相当于模拟了一条gap消息出来,处理一些永久丢失的消息
                 if (last_sequence != c_SequenceNumber_Unknown && ch.sequenceNumber > last_sequence + 1)
                {
                     ······
                     reader_->processGapMsg(pool->writer(), last_sequence + 1, SequenceNumberSet_t(ch.sequenceNumber));
                }
 ​
                 if (last_sequence == c_SequenceNumber_Unknown && ch.sequenceNumber > SequenceNumber_t(0, 1))
                {
                     ······
                     reader_->processGapMsg(pool->writer(), SequenceNumber_t(0, 1), SequenceNumberSet_t(
                                 ch.sequenceNumber));
                }
 ​
                ······
 //这儿是处理这个新收到的消息
                 if (reader_->processDataMsg(&ch))
                {
                     pool->release_payload(ch);
                     pool->advance_to_next_payload();
                }
            }
 ​
             if (writer_pools_changed_.load(std::memory_order_relaxed))
            {
                 // Break the while on the current writer (it may have been removed)
                 break;
            }
        }
 ​
         // Lock again for the next loop
         lock.lock();
 ​
         if (writer_pools_changed_.load(std::memory_order_relaxed))
        {
             // Break the loop over the writers (itearators may have been invalidated)
             break;
        }
    }
 }

这是共享内存接收端的相关逻辑。

主要是如何处理消息:

1.根据消息的seqnumber 来模拟gap message(gap消息是 Writer发送给Reader,标识writer的HistoryCache中的一些Change不再可用,也不会再发给Reader)

2.调用reader的processDataMsg函数来处理消息

上面是共享内存接收端的相关逻辑

2.2发送端的逻辑

下面我们简单介绍一下发送端的相关逻辑:

在发送端,会申请一个共享内存和接收端的共享内存一一对应,两边是同一块内存。

2.2.1发送端申请共享内存

在发送端申请共享内存是在这两个地方:

1.是RTPSWriter初始化的时候,如果这个RTPSWriter 设置了data_sharing,就会为这个RTPSWriter申请共享内存

2.是在StatefulReader 和 StatelessReader 匹配到新的远端的RTPSWriter的时候,会为这个RTPSWriter申请一个共享内存

这是第一种情况RTPSWriter初始化的时候创建共享内存

c 复制代码
 void RTPSWriter::init(
         const std::shared_ptr<IPayloadPool>& payload_pool,
         const std::shared_ptr<IChangePool>& change_pool,
         const WriterAttributes& att)
 {
    ------
     if (att.endpoint.data_sharing_configuration().kind() != OFF)
    {
         std::shared_ptr<WriterPool> pool = std::dynamic_pointer_cast<WriterPool>(payload_pool);
         if (!pool || !pool->init_shared_memory(this, att.endpoint.data_sharing_configuration().shm_directory()))
        {
              ------
        }
    }
 ​
    ------ 
 }

这是第二种情况,匹配到远端RTPSWriter的时候,申请一个共享内存。

c 复制代码
 bool DataSharingListener::add_datasharing_writer(
         const GUID_t& writer_guid,
         bool is_volatile,
         int32_t reader_history_max_samples)
 {
     std::lock_guard<std::mutex> lock(mutex_);
 ​
     ------
     std::shared_ptr<ReaderPool> pool =
             std::static_pointer_cast<ReaderPool>(DataSharingPayloadPool::get_reader_pool(is_volatile));
     if (pool->init_shared_memory(writer_guid, datasharing_pools_directory_))
    {
         if (0 >= reader_history_max_samples ||
                 reader_history_max_samples >= static_cast<int32_t>(pool->history_size()))
        {
             ------
        }
         writer_pools_.emplace_back(pool, pool->last_liveliness_sequence());
         writer_pools_changed_.store(true);
         return true;
    }
 ​
     return false;
 }

那么在两种场景下可以用到数据共享

1.RTPSWriter配置成可以使用数据共享,那么RTPSWriter会申请一块共享内存,在RTPSWriter发送数据的时候,所有和RTPSWriter在一台物理设备上的reader可以直接使用这个共享内存,来获取消息。

2.当RTPSReader匹配到远端的RTPSWriter的时候会为这个Writer申请一块共享内存,当这个RTPSWriter收到消息的时候,会将消息存储到共享内存中,这样这台机器上的所有RTPSReader都可以直接使用这块内存中的消息。

2.22发送端的源码解析

回到发送端,这个就是在第十篇中介绍到的,在发送消息的时候,会用datasharing_delivery 和 FlowController来发送消息。

datasharing_delivery就是使用跨进程通信来发送消息,FlowController就是使用网络通信来发送消息。

我们看一下datasharing_delivery是如何操作的。

sequenceDiagram participant StatelessWriter participant WriterPool Participant ReaderLocator Participant DataSharingListener Participant DataSharingNotifier Participant DataSharingNotification StatelessWriter ->> StatelessWriter: 1.datasharing_delivery StatelessWriter ->> WriterPool: 2.add_to_shared_history StatelessWriter ->> ReaderLocator: 3.datasharing_notify ReaderLocator ->> DataSharingListener: 4.notify ReaderLocator ->> DataSharingNotifier: 5.notify DataSharingNotifier ->> DataSharingNotification: 6.notify

1.StatelessWriter(PDP消息都是以StatelessWriter发送的,StatefulWriter类似) 调用datasharing_delivery

主要干了2件事

a.调用WriterPool的add_to_shared_history 见步骤2

b.调用ReaderLocator的datasharing_notify 见步骤3

2.WriterPool的add_to_shared_history 就是将change放入到共享内存中

3.调用ReaderLocator的datasharing_notify,

如果reader 和 writer是同一进程,直接调用DataSharingListener::notify 见步骤4

否则调用DataSharingNotifier 的notify见步骤5

4.直接调用process_new_data,处理消息

5.DataSharingNotifier 的notify调用了DataSharingNotification 的notify函数,见步骤6

6.通过信号量通知reader,数据已经到达

步骤1:

rust 复制代码
 bool StatelessWriter::datasharing_delivery(
         CacheChange_t* change)
 {
     auto pool = std::dynamic_pointer_cast<WriterPool>(payload_pool_);
     assert(pool != nullptr);
 ​
     pool->add_to_shared_history(change);
     EPROSIMA_LOG_INFO(RTPS_WRITER, "Notifying readers of cache change with SN " << change->sequenceNumber);
     for (std::unique_ptr<ReaderLocator>& reader : matched_datasharing_readers_)
    {
         if (!reader_data_filter_ || reader_data_filter_->is_relevant(*change, reader->remote_guid()))
        {
             reader->datasharing_notify();
        }
    }
     return true;
 }

主要干了2件事:

1.add_to_shared_history 也就是步骤2

2.datasharing_notify,对reader发送通知 也就是步骤3

matched_datasharing_readers_ 这个一开始是空,需要在pdp edp阶段,收到消息之后,与之匹配的reader的数据才会放入。

步骤2

scss 复制代码
 void add_to_shared_history(
             const CacheChange_t* cache_change)
    {
         assert(cache_change);
         assert(cache_change->serializedPayload.data);
         assert(cache_change->payload_owner() == this);
         assert(free_history_size_ > 0);
 ​
         // Fill the payload metadata with the change info
         PayloadNode* node = PayloadNode::get_from_data(cache_change->serializedPayload.data);
         node->status(ALIVE);
         node->data_length(cache_change->serializedPayload.length);
         node->source_timestamp(cache_change->sourceTimestamp);
         node->writer_GUID(cache_change->writerGUID);
         node->instance_handle(cache_change->instanceHandle);
         if (cache_change->write_params.related_sample_identity() != SampleIdentity::unknown())
        {
             node->related_sample_identity(cache_change->write_params.related_sample_identity());
        }
 ​
         // Set the sequence number last, it signals the data is ready
         node->sequence_number(cache_change->sequenceNumber);
 ​
         // Add it to the history
   // 通过address获取一个handle
   // 这个address可以理解为本地地址,共享内存地址在本地内存上的映射
   // 这个handle 可以理解为共享内存的一个全局地址,那么其他进程通过这个handle可以将共享内存映射到本地内存上,就能使用数据了
   // 这个history_是一个数组,用于存放handle
         history_[static_cast<uint32_t>(descriptor_->notified_end)] = segment_->get_offset_from_address(node);
         EPROSIMA_LOG_INFO(DATASHARING_PAYLOADPOOL, "Change added to shared history"
                 << " with SN " << cache_change->sequenceNumber);
         advance(descriptor_->notified_end);
         --free_history_size_;
    }

1.通过cache_change 找到PayloadNode

对PayloadNode参数进行初始化

2.segment_->get_offset_from_address(node) 获取一个handle,

3.history_ 是存放全局地址的一个数组

这块操作就是将change 放入到共享内存中

步骤3:

scss 复制代码
 void ReaderLocator::datasharing_notify()
 {
     RTPSReader* reader = nullptr;
   //是否同进程
     if (is_local_reader())
    {
         reader = local_reader();
    }
 ​
     if (reader)
    {
         reader->datasharing_listener()->notify(true);
    }
     else
    {   
         
         datasharing_notifier()->notify();
    }
 }

同进程,直接调用DataSharingListener::notify 见步骤4

否则调用DataSharingNotifier 的notify见步骤5

步骤4:

scss 复制代码
 void DataSharingListener::notify(
         bool same_thread)
 {
   
     if (same_thread)
    {
         process_new_data();
    }
     else
    {
       
         //DataSharingNotification
         notification_->notify();
    }
 }
 ​

直接process_new_data,处理消息

步骤5 DataSharingNotifier 的notify:

scss 复制代码
 void notify() override
    {
         if (is_enabled())
        {
             EPROSIMA_LOG_INFO(RTPS_WRITER, "Notifying reader " << shared_notification_->reader());
             shared_notification_->notify();
        }
    }

最终还是走到DataSharingNotification 的notify函数

步骤6:

arduino 复制代码
 DataSharingNotification
 inline void notify()
    {
         std::unique_lock<Segment::mutex> lock(notification_->notification_mutex);
         notification_->new_data.store(true);
         lock.unlock();
         notification_->notification_cv.notify_all();
    }

这儿就是发送消息了,在步骤2中已经将数据存储到共享内存中了,在这里只是通过信号量通知reader,数据已经到达。

见2.1的步骤6,reader 中正有一个线程在监听信息,收到信息后,从共享内存中读取消息,处理消息。

2.3类图

classDiagram ReaderLocator *-- DataSharingNotifier ReaderLocator *-- RTPSReader DataSharingNotifier *--DataSharingNotification RTPSReader *-- DataSharingListener DataSharingListener *--DataSharingNotification class ReaderLocator { +IDataSharingNotifier dataSharing_notifier_ +RTPSReader* local_reader_ } class RTPSReader { +IDataSharingListener datasharing_listener_ }

1.每个Writer,有1个到多个存储Reader信息的ReaderLocator

2.如果Reader是能够共享内存的reader,则会初始化DataSharingNotifier

3.DataSharingNotifier 有个DataSharingNotification对象

4.如果这个ReaderLocator对应的reader是同一个Participant中的reader

那么ReaderLocator 有一个指针指向这个reader

5.RTPSReader 有一个DataSharingListerner

6.DataSharingListerner有一个DataSharingNotification对象

车载消息中间件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消息(下)---异步发送

相关推荐
宋1381027972014 分钟前
Manus Xsens Metagloves虚拟现实手套
人工智能·机器人·vr·动作捕捉
禁默39 分钟前
第六届机器人、智能控制与人工智能国际学术会议(RICAI 2024)
人工智能·机器人·智能控制
网易独家音乐人Mike Zhou9 小时前
【卡尔曼滤波】数据预测Prediction观测器的理论推导及应用 C语言、Python实现(Kalman Filter)
c语言·python·单片机·物联网·算法·嵌入式·iot
Robot25114 小时前
Figure 02迎重大升级!!人形机器人独角兽[Figure AI]商业化加速
人工智能·机器人·微信公众平台
FreeIPCC18 小时前
谈一下开源生态对 AI人工智能大模型的促进作用
大数据·人工智能·机器人·开源
施努卡机器视觉20 小时前
电解车间铜业机器人剥片技术是现代铜冶炼过程中自动化和智能化的重要体现
运维·机器人·自动化
zhd15306915625ff1 天前
库卡机器人日常维护
网络·机器人·自动化·机器人备件
古月居GYH1 天前
ROS一键安装脚本
人工智能·机器人·ros
清流君1 天前
【运动规划】移动机器人运动规划与轨迹优化全解析 | 经典算法总结
人工智能·笔记·算法·机器人·自动驾驶·运动规划
Matlab程序猿小助手1 天前
【MATLAB源码-第218期】基于matlab的北方苍鹰优化算法(NGO)无人机三维路径规划,输出做短路径图和适应度曲线.
开发语言·嵌入式硬件·算法·matlab·机器人·无人机