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

79 阅读11分钟

车载消息中间件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消息(中)

1.FlowController详细解析

1.1简介

FlowController看名字就是大概什么意思,就是发送流的控制器。

FlowControllerImpl 是一个泛型类模版。里面有两个类型参数PublishMode, SampleScheduling

PublishMode负责FlowControllerImpl 同步异步发送逻辑

SampleScheduling负责FlowControllerImpl在异步发送过程中的调度策略

 template<typename PublishMode, typename SampleScheduling>
 class FlowControllerImpl : public FlowController
 {
     using publish_mode = PublishMode;
     using scheduler = SampleScheduling;
 ......
 private:
 ...... 
    scheduler sched;
    publish_mode async_mode;
 }

我们可以看到FlowControllerImpl 是一个模版类,有两个模版参数PublishMode 和 SampleScheduling。配置的模版参数不一样,我们的Datawriter发送行为就不一样。

我们分别介绍一下这两个参数:

1.2 PublishMode

PublishMode:

classDiagram
FlowControllerPureSyncPublishMode <|-- FlowControllerSyncPublishMode
      FlowControllerAsyncPublishMode <|-- FlowControllerSyncPublishMode
      FlowControllerAsyncPublishMode <|-- FlowControllerLimitedAsyncPublishMode   

看了一下类图这是PublishMode类的继承关系。

PublishMode一共有四种

FlowControllerPureSyncPublishMode 调用add_new_sample_impl的时候,会先同步发送,调用 deliver_sample_nts,如果发送失败调用enqueue_new_sample_impl异步发送。FlowControllerPureSyncPublishMode没有异步发送,enqueue_new_sample_impl直接返回false。

FlowControllerAsyncPublishMode 调用add_new_sample_impl的时候,直接调用enqueue_new_sample_impl 异步发送

FlowControllerSyncPublishMode 继承了FlowControllerAsyncPublishMode 和 FlowControllerPureSyncPublishMode

调用add_new_sample_impl的时候,会先同步发送,调用 deliver_sample_nts,如果发送失败调用enqueue_new_sample_impl异步发送

FlowControllerLimitedAsyncPublishMode 继承了 FlowControllerAsyncPublishMode

调用add_new_sample_impl的时候,直接调用enqueue_new_sample_impl 异步发送

对于pdp阶段的statelesswriter来说就是FlowControllerPureSyncPublishMode的writer,由于没有异步的发送,也就没有scheduler

1.3SampleScheduling

SampleScheduling有四种

FlowControllerFifoSchedule 先进先出的调度

FlowControllerRoundRobinSchedule 轮询,每个datawriter 每次轮训发送一个

FlowControllerHighPrioritySchedule 根据优先级来发送

FlowControllerPriorityWithReservationSchedule 每个datawriter保证一个最小的的发送量,剩下的按照优先级来发送

FIFO,ROBIN,优先级调度这些在资源分配程序中都是非常常见的策略,比如内存分配,网络通路控制,还有linux中cpu的调度。

FIFO 就是先进先出,Robin就是轮训。FlowControllerHighPrioritySchedule 根据优先级来发送,而FlowControllerPriorityWithReservationSchedule则是ROBIN 和 FlowControllerHighPrioritySchedule结合的一种发送控制策略。

FIFO

graph LR;
    msg3-->msg2;
    msg2-->msg1;
    msg1-->FlowController;
    FlowController-->msg3';
    msg3'-->msg2';
    msg2'-->msg1';

可以看一下这张示意图,FIFO就是先到先发送,按照顺序执行发送操作。

ROBIN

graph LR;
    msg3-->msg2;
    msg2-->msg1;
    msg1-->FlowController;
    msg6-->msg5;
    msg5-->msg4;
    msg4-->FlowController;
    msg9-->msg8;
    msg8-->msg7;
    msg7-->FlowController;
    FlowController-->msg9';
    msg9'-->msg6';
    msg6'-->msg3';
    msg3'-->msg8';
    msg8'-->msg5';
    msg5'-->msg2';
    msg2'-->msg7';
    msg7'-->msg4';
    msg4'-->msg1';

ROBIN采用轮训的方式,对多个队列进行调度。ROBIN以环形的方式轮询多个队列。如果轮询的队列不为空,则从该队列取走一个消息发送;如果该队列为空,则直接跳过该队列,调度器不等待。

HighPriority

graph LR;
    高优先级队列-->msg6
    msg6-->FlowController;
    中优先级队列-->msg2
    msg2-->msg1;
    msg1-->FlowController;
    低优先级队列-->msg5;
    msg5-->msg4;
    msg4-->msg3;
    msg3-->FlowController;
    FlowController-->msg5';
    msg5'-->msg4';
    msg4'-->msg3';
    msg3'-->msg2';
    msg2'-->msg1';
    msg1'-->msg6';

调度就是严格按照队列优先级的高低顺序进行调度。只有高优先级队列中的消息全部调度完毕后,低优先级队列才有调度机会。

假设程序中有3个采用优先级调度算法的队列,分别为高优先(High)队列、中优先(Medium)队列、和低优先(Low)队列,它们的优先级依次降低。如图所示,其中消息编号表示报文到达顺序。

在消息发送的时候,首先让高优先队列中的消息发送,直到高优先队列中的消息发送完,然后发送中优先级队列中的消息,直到发送完,接着是低优先队列。在调度低优先级队列时,如果高优先级队列又有消息到来,则会优先调度高优先级队列。这样,较高优先级队列的消息将会得到优先发送,而较低优先级的消息后发送。

PriorityWithReservation

每个队列保证一个最小的的发送量,剩下的按照优先级来发送。

FIFO,ROBIN是比较成熟的策略了,在早期的网络系统,存储系统,linux 内存管理,cpu调度中都有见过。

1.4 FlowControllerFifoSchedule 源码简单举例

 struct FlowControllerFifoSchedule
 {
 ......
 private:
 ​
     //! Scheduler queue. FIFO scheduler only has one queue.
     FlowQueue queue_;
 };

FlowControllerFifoSchedule 有一个FlowQueue队列

 struct FlowQueue
 {
 ......    
 ​
 private:
 ​
     struct ListInfo
    {
      ......
       
         fastrtps::rtps::CacheChange_t head;
         fastrtps::rtps::CacheChange_t tail;
    };
 ​
     //! List of interested new changes to be included.
     //! Should be protected with changes_interested_mutex.
     ListInfo new_interested_;
 ​
     //! List of interested old changes to be included.
     //! Should be protected with changes_interested_mutex.
     ListInfo old_interested_;
 ​
     //! List of new changes
     //! Should be protected with mutex_.
     ListInfo new_ones_;
 ​
     //! List of old changes
     //! Should be protected with mutex_.
     ListInfo old_ones_;
 };

FlowQueue中包含了4个链表,

new_interested_,

old_interested_,

new_ones_,

old_ones_,

new_interested_ 和old_interested_ ,是存储用的。_

new_ones_ 和 old_ones_是发送用的,后面会介绍到

1.5FlowController 与writer 的匹配

在 RTPSParticipantImpl::create_writer 这个函数中,我们看到在RTPSParticipantImpl创建writer的过程中,会为writer 配置一个FlowController,这个flowcontoller是由flow_controller_factory统一管理的

flow_controller_factory .retrieve_flow_controller(guid_str, param);这个就是根据参数来获取FlowController

 FlowController* FlowControllerFactory::retrieve_flow_controller(
         const std::string& flow_controller_name,
         const fastrtps::rtps::WriterAttributes& writer_attributes)
 {
     FlowController* returned_flow = nullptr;
 ​
     // Detect it has to be returned a default flow_controller.
     if (0 == flow_controller_name.compare(FASTDDS_FLOW_CONTROLLER_DEFAULT))
    {
         if (fastrtps::rtps::SYNCHRONOUS_WRITER == writer_attributes.mode)
        {
             if (fastrtps::rtps::BEST_EFFORT == writer_attributes.endpoint.reliabilityKind)
            {
                 returned_flow = flow_controllers_[pure_sync_flow_controller_name].get();
            }
             else
            {
                 returned_flow = flow_controllers_[sync_flow_controller_name].get();
            }
        }
         else
        {
             returned_flow = flow_controllers_[async_flow_controller_name].get();
        }
    }
  ......
 ​
     return returned_flow;
 }

如果是reliabilityKind == BEST_EFFORT 同时writer_attributes.mode == SYNCHRONOUS_WRITER,则是纯同步的flow_controller

如果writer_attributes.mode == ASYNCHRONOUS_WRITER,则是异步的flow_controller。

我们Simple PDP的StatelessWriter ,reliabilityKind是BEST_EFFORT,同时writer_attributes.mode == SYNCHRONOUS_WRITER,那么它配置的flow_controller就是纯同步的flow_controller。

而simple EDP阶段的StatefulWriter,reliabilityKind是RELIABLE,writer_attributes.mode == SYNCHRONOUS_WRITER,那么它匹配的

flow_controller是一个FlowControllerSyncPublishMode的flow_controller。

1.6类图

classDiagram
 RTPSWriter *--FlowController
      FlowController *--SampleScheduling
      FlowController *--PublishMode
      SampleScheduling <--FlowControllerFifoSchedule
      FlowControllerFifoSchedule *--FlowQueue
      class RTPSWriter {
      	+FlowController flow_controller_
      	
      }
      class FlowController {
      	+SampleScheduling sched
      	+PublishMode async_mode
      }
      class FlowControllerFifoSchedule {
      	+FlowQueue queue_
      }
      

1.会给每个RTPSWriter 配置一个FlowController

2.FlowController 有两个模版类参数:PublishMode 和 SampleScheduling

3.PublishMode一共有四种

FlowControllerPureSyncPublishMode

FlowControllerAsyncPublishMode

FlowControllerSyncPublishMode

FlowControllerLimitedAsyncPublishMode

4.SampleScheduling 一共有四种

FlowControllerFifoSchedule

FlowControllerRoundRobinSchedule

FlowControllerHighPrioritySchedule

FlowControllerPriorityWithReservationSchedule

5.每个SampleScheduling 中都有一个FlowQueue列表

2.FlowControllerImpl 的异步发送

异步发送类似于一个生产者消费者模型。

2.1消息的生产端

我们先看一下在消息的生产端

我们看一下上一篇开头部分,如果有同步发送的功能则先同步发送,发送失败再调用enqueue_new_sample_impl这个函数将消息放入队列。

 template<typename PubMode = PublishMode>
     typename std::enable_if<!std::is_same<FlowControllerPureSyncPublishMode, PubMode>::valuebool>::type
     enqueue_new_sample_impl(
             fastrtps::rtps::RTPSWriter* writer,
             fastrtps::rtps::CacheChange_t* change,
             const std::chrono::time_point<std::chrono::steady_clock>& /* TODO max_blocking_time*/)
    {
         assert(!change->writer_info.is_linked.load());
         // Sync delivery failed. Try to store for asynchronous delivery.
         std::unique_lock<std::mutexlock(async_mode.changes_interested_mutex);
         sched.add_new_sample(writer, change);
         async_mode.cv.notify_one();
 ​
         return true;
    }
 ​
     

如果配置的PublishMode不是FlowControllerPureSyncPublishMode,就是不是纯同步模式,就将数据存储到队列中

 /*! This function is used when PublishMode = FlowControllerPureSyncPublishMode.
      * In this case there is no async mechanism.
      */
     template<typename PubMode = PublishMode>
     //纯同步的模式,不会放入队列,使用异步线程发送
     typename std::enable_if<std::is_same<FlowControllerPureSyncPublishMode, PubMode>::valuebool>::type
     constexpr enqueue_new_sample_impl(
             fastrtps::rtps::RTPSWriter*,
             fastrtps::rtps::CacheChange_t*,
             const std::chrono::time_point<std::chrono::steady_clock>&) const
    {
         // Do nothing. Return false.
         return false;
    }

如果是FlowControllerPureSyncPublishMode纯同步模式,就不会放入队列中的,直接返回false。

在StatelessWriter 中配置的FlowController是FlowControllerPureSyncPublishMode是纯同步的,就不会放入队列,在StatefulWriter中一般配置的是FlowControllerAsyncPublishMode,有异步的功能,就会将消息放入队列中。

最终调用了sched.add_new_sample(writer, change)

我们看到这个sched 就是SampleScheduling的一个对象(参看1.1节)

FlowControllerFifoSchedule(参看1.4节)的add_new_sample 函数

 void add_new_sample(
             fastrtps::rtps::RTPSWriter*,
             fastrtps::rtps::CacheChange_t* change)
    {
         queue_.add_new_sample(change);
    }

将change 存入 Flowqueue中

调用了Flowqueue的add_new_sample,将change放入new_interested_ 中去

 void add_new_sample(
             fastrtps::rtps::CacheChange_t* change) noexcept
    {
         new_interested_.add_change(change);
    }

有add_new_sample 就有add_old_sample

add_new_sample 是发送新的消息,add_old_sample就是发送history中已经有的消息。

add_new_sample将消息存在new_interested中去,add_old_sample将消息存在old_interested中。

 add_old_sample_impl(
             fastrtps::rtps::RTPSWriter* writer,
             fastrtps::rtps::CacheChange_t* change,
             const std::chrono::time_point<std::chrono::steady_clock>& /* TODO max_blocking_time*/)
    {
         if (!change->writer_info.is_linked.load())
        {
             std::unique_lock<std::mutexlock(async_mode.changes_interested_mutex);
             sched.add_old_sample(writer, change);
             async_mode.cv.notify_one();
 ​
             return true;
        }
 ​
         return false;
    }
 ​
     
     template<typename PubMode = PublishMode>
     typename std::enable_if<std::is_same<FlowControllerPureSyncPublishMode, PubMode>::valuebool>::type
     constexpr add_old_sample_impl(
             fastrtps::rtps::RTPSWriter*,
             fastrtps::rtps::CacheChange_t*,
             const std::chrono::time_point<std::chrono::steady_clock>&) const
    {
         return false;
    }

如果是FlowControllerPureSyncPublishMode就是纯同步模式,则直接返回flase,也就是纯同步模式add_old_sample_impl 不会有任何操作

如果不是纯同步模式则调用sched.add_old_sample(writer, change);

最终调用了sched.add_new_sample(writer, change)

我们看到这个sched 就是SampleScheduling的一个对象(参看1.1节)

FlowControllerFifoSchedule(参看1.4节)的add_new_sample 函数

 void add_old_sample(
             fastrtps::rtps::RTPSWriter*,
             fastrtps::rtps::CacheChange_t* change)
    {
         queue_.add_old_sample(change);
    }

将change 存入 Flowqueue中

调用了Flowqueue的add_new_sample,将change放入old_interested_中去

  void add_old_sample(
             fastrtps::rtps::CacheChange_t* change) noexcept
    {
         old_interested_.add_change(change);
    }

最终将消息存储到old_interested_ 队列中。

Flowqueue 中的4个链表,这里面new_interested_ 和old_interested_

graph LR;
    add_new_sample-->new_interested_;
    add_old_sample-->old_interested_;
    new_interested_ --- Flowqueue;
    old_interested_ --- Flowqueue;

2.2消息的消费端

 /*!
      * Initialize asynchronous thread.
      */
     template<typename PubMode = PublishMode>
 //如果不是纯同步的模式,运行一个线程来发送非同步的消息
 //纯同步的模式不会有异步线程来发送消息
     typename std::enable_if<!std::is_same<FlowControllerPureSyncPublishMode, PubMode>::value, void>::type
     initialize_async_thread()
    {
         bool expected = false;
         if (async_mode.running.compare_exchange_strong(expected, true))
        {
             // Code for initializing the asynchronous thread.
             async_mode.thread = std::thread(&FlowControllerImpl::run, this);
        }
    }

我们看一下这块内容,在FlowControllerImpl初始化阶段调用了initialize_async_thread

如果不是FlowControllerPureSyncPublishMode,就会启用一个线程,不断轮训发送消息。

那么在异步发送的过程中,消息会被不断的放入到队列中,等待被发送。下面这个是线程的run函数

 /*!
      * Function run by the asynchronous thread.
      */
     void run()
    {
         while (async_mode.running)
        {
             // There are writers interested in removing a sample.
             // 这是async_mode的一个变量,起到了一个锁的作用
           // 当有writer想要remove change的时候就跳过,不执行操作
             if (0 != async_mode.writers_interested_in_remove)
            {
                 continue;
            }
 ​
             std::unique_lock<std::mutex> lock(mutex_);
             fastrtps::rtps::CacheChange_t* change_to_process = nullptr;
 ​
             //Check if we have to sleep.
            {
                 
                 std::unique_lock<std::mutex> in_lock(async_mode.changes_interested_mutex);
                 // Add interested changes into the queue.
               // 将 new_interested_ 存入new_ones_ && 将 old_interested_ 存入到 old_ones_
                 sched.add_interested_changes_to_queue_nts();
 ​
                 //如果get_next_change_nts 为空,则继续循环              
                 while (async_mode.running &&
                        (async_mode.force_wait() || nullptr == (change_to_process = sched.get_next_change_nts())))
                {
                     // Release main mutex to allow registering/unregistering writers while this thread is waiting.
                     lock.unlock();
                     bool ret = async_mode.wait(in_lock);
 ​
                     in_lock.unlock();
                     lock.lock();
                     in_lock.lock();
 ​
                     if (ret)
                    {
                       // 重置带宽限制
                         sched.trigger_bandwidth_limit_reset();
                    }
                     // 将 new_interested_ 存入new_ones_ && 将 old_interested_ 存入到 old_ones_
                     sched.add_interested_changes_to_queue_nts();
                }
            }
 ​
             fastrtps::rtps::RTPSWriter* current_writer = nullptr;
             while (nullptr != change_to_process)
            {
                 // Fast check if next change will enter.
                 // 带宽限制,带宽达到上限,则break   
                 // 这个只有在FlowControllerLimitedAsyncPublishMode 才起作用
                 if (!async_mode.fast_check_is_there_slot_for_change(change_to_process))
                {
                     break;
                }
 ​
                 if (nullptr == current_writer || current_writer->getGuid() != change_to_process->writerGUID)
                {
                     auto writer_it = writers_.find(change_to_process->writerGUID);
                     assert(writers_.end() != writer_it);
 ​
                     current_writer = writer_it->second;
                }
 ​
                 if (!current_writer->getMutex().try_lock())
                {
                     break;
                }
 ​
                 fastrtps::rtps::LocatorSelectorSender& locator_selector =
                         current_writer->get_async_locator_selector();
               // 设置writer 和 locator_selector             
                 async_mode.group.sender(current_writer, &locator_selector);
                 locator_selector.lock();
 ​
                 // Remove previously from queue, because deliver_sample_nts could call FlowController::remove_sample()
                 // provoking a deadlock.
               // 下面代码主要从队列中取出
                 fastrtps::rtps::CacheChange_t* previous = change_to_process->writer_info.previous;
                 fastrtps::rtps::CacheChange_t* next = change_to_process->writer_info.next;
                 previous->writer_info.next = next;
                 next->writer_info.previous = previous;
                 change_to_process->writer_info.previous = nullptr;
                 change_to_process->writer_info.next = nullptr;
                 change_to_process->writer_info.is_linked.store(false);
 ​
               //这里面是statefulwriter的deliver_sample_nts
                 fastrtps::rtps::DeliveryRetCode ret_delivery = current_writer->deliver_sample_nts(
                     change_to_process, async_mode.group, locator_selector,
                     std::chrono::steady_clock::now() + std::chrono::hours(24));
 //如果发送失败,则将change放回队列中去
                 if (fastrtps::rtps::DeliveryRetCode::DELIVERED != ret_delivery)
                {
                     // If delivery fails, put the change again in the queue.
                     change_to_process->writer_info.is_linked.store(true);
                     previous->writer_info.next = change_to_process;
                     next->writer_info.previous = change_to_process;
                     change_to_process->writer_info.previous = previous;
                     change_to_process->writer_info.next = next;
 ​
                     async_mode.process_deliver_retcode(ret_delivery);
 ​
                     locator_selector.unlock();
                     current_writer->getMutex().unlock();
                     // Unlock mutex_ and try again.
                     break;
                }
 ​
                 locator_selector.unlock();
                 current_writer->getMutex().unlock();
 ​
                 sched.work_done();
 ​
                 if (0 != async_mode.writers_interested_in_remove)
                {
                     // There are writers that want to remove samples.
                     // 跳出循环
                     break;
                }
 ​
                 // Add interested changes into the queue.
               // 将 new_interested_ 存入new_ones_ && 将 old_interested_ 存入到 old_ones_
                {
                     std::unique_lock<std::mutex> in_lock(async_mode.changes_interested_mutex);
                     sched.add_interested_changes_to_queue_nts();
                }
 //获取下一个要发送的change
                 change_to_process = sched.get_next_change_nts();
            }
 ​
             async_mode.group.sender(nullptr, nullptr);
        }
    }

这里面有两个关键函数

 void add_interested_changes_to_queue_nts()
    {
         // This function should be called with mutex_ and interested_lock locked, because the queue is changed.
         queue_.add_interested_changes_to_queue();
    }
 ​
 void add_interested_changes_to_queue() noexcept
    {
         // This function should be called with mutex_ and interested_lock locked, because the queue is changed.
         new_ones_.add_list(new_interested_);
         old_ones_.add_list(old_interested_);
    }

就是将new_interested_ 中的数据放入new_ones_,

old_interested_ 中的数据放入old_ones_,

 fastrtps::rtps::CacheChange_t* get_next_change_nts()
    {
         return queue_.get_next_change();
    }
 ​
 fastrtps::rtps::CacheChange_t* get_next_change() noexcept
    {
         if (!is_empty())
        {
             return !new_ones_.is_empty() ?
                    new_ones_.head.writer_info.next : old_ones_.head.writer_info.next;
        }
 ​
         return nullptr;
    }

获取下一个change,先获取new_ones中的消息,只有new_ones 中的没有了,才获取 old_ones_ 中的消息

  void sender(
             Endpoint* endpoint,
             RTPSMessageSenderInterface* msg_sender)
    {
         assert((endpoint != nullptr && msg_sender != nullptr) || (endpoint == nullptr && msg_sender == nullptr));
         if (endpoint != endpoint_ || msg_sender != sender_)
        {
             flush_and_reset();
        }
 ​
         endpoint_ = endpoint;
         sender_ = msg_sender;
    }
 ​

flush_and_reset()这个函数在上一篇中介绍过就是发送消息。

整个线程会在运行在两个状态上,1个是等待状态,一个是发送状态

代码28-46行是等待状态,当没有消息需要发送的时候就在这个循环里运行

代码50-131行是发送状态,当有消息需要发送的时候就在这个循环里运行

发送的时候就是将

new_interested_ 中的数据存储到new_ones_,

old_interested_ 中的数据存储到old_ones_,

然后不断从new_ones_ 和old_ones_ 中取消息发送。

如果发送失败,则将change 放回原处,继续循环。

2.3简单流程图

graph LR;
    add_new_sample-->new_interested_;
    add_old_sample-->old_interested_;
   
    new_interested_ --- Flowqueue;
    old_interested_ --- Flowqueue;
    new_interested_ --> new_ones_;
    Flowqueue --- new_ones_;
    Flowqueue --- old_ones_;
    old_interested_ --> old_ones_;
    new_ones_-->get_next_change;
    old_ones_-->get_next_change;
    get_next_change --> deliver_sample_nts;

1.FlowQueue中包含了4个链表,

new_interested_,

old_interested_,

new_ones_,

old_ones_,

2.不断往new_interested_ 和old_interested_ 中放入消息

3.FlowControllerImpl启动的线程不断将new_interested_ 和 old_interested_ 中的消息存入

new_ones_ 和 old_ones_ 中

4.FlowControllerImpl启动的线程不断从new_ones_ 和 old_ones_ 中取出消息发送出去

5.deliver_sample_nts 之后的流程可以看一下上一篇,有过详细的介绍。