[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消息(下)---异步发送

相关推荐
我爱C编程5 小时前
基于Qlearning强化学习的机器人路线规划matlab仿真
matlab·机器人·强化学习·路线规划·qlearning·机器人路线规划
野蛮的大西瓜5 小时前
开源呼叫中心中,如何将ASR与IVR菜单结合,实现动态的IVR交互
人工智能·机器人·自动化·音视频·信息与通信
憧憬一下14 小时前
PCIe_Host驱动分析_设备枚举
arm开发·嵌入式硬件·嵌入式·pcie·linux驱动开发
向阳逐梦1 天前
基于STM32F4单片机实现ROS机器人主板
stm32·单片机·机器人
朽木成才2 天前
小程序快速实现大模型聊天机器人
小程序·机器人
聆思科技AI芯片2 天前
实操给桌面机器人加上超拟人音色
人工智能·机器人·大模型·aigc·多模态·智能音箱·语音交互
新加坡内哥谈技术2 天前
开源Genesis: 开创机器人研究的全新模拟平台
机器人·开源
野蛮的大西瓜2 天前
文心一言对接FreeSWITCH实现大模型呼叫中心
人工智能·机器人·自动化·音视频·实时音视频·文心一言·信息与通信
高克莱2 天前
【钉钉群聊机器人定时发送消息功能实现】
java·spring boot·机器人·调度任务
小俱的一步步2 天前
钉钉自定义机器人发送群消息(加签方式、http发送)
机器人·钉钉