车载消息中间件FastDDS 源码解析(一)FastDDS 介绍和使用
车载消息中间件FastDDS 源码解析(二)RtpsParticipant的创建(上)
车载消息中间件FastDDS 源码解析(三)RtpsParticipant的创建(中)
车载消息中间件FastDDS 源码解析(四)RtpsParticipant的创建(下)
车载消息中间件FastDDS 源码解析(五)BuiltinProtocols(上)
车载消息中间件FastDDS 源码解析(六)BuiltinProtocols(中)EDP
车载消息中间件FastDDS 源码解析(七)BuiltinProtocols(下)WLP&TypeLookupManager
车载消息中间件FastDDS 源码解析(八)TimedEvent
车载消息中间件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>::value, bool>::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::mutex> lock(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>::value, bool>::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::mutex> lock(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>::value, bool>::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 之后的流程可以看一下上一篇,有过详细的介绍。