【apollo】cyber底层通信–订阅方如何获取数据
reader的创建
cyber中所有reader的创建都有node来管理,node是拓扑网络中最基本的节点,所有的writer/reader、service/client都统一由node管理,cyber RT framework 在RTOS上层,所有node和writer、reader的概念也都是为了和ROS的接口兼容。
reader的创建代码,这里只列举其中一个:
template <typename MessageT>
auto Node::CreateReader(const ReaderConfig& config,
const CallbackFunc<MessageT>& reader_func)
-> std::shared_ptr<cyber::Reader<MessageT>> {
std::lock_guard<std::mutex> lg(readers_mutex_);
// 同一个node管理下,不能针对同一个topic创建多个reader
if (readers_.find(config.channel_name) != readers_.end()) {
AWARN << "Failed to create reader: reader with the same channel already "
"exists.";
return nullptr;
}
// 这里实际是调用NodeChannelImpl::CreateReader去创建的
auto reader =
node_channel_impl_->template CreateReader<MessageT>(config, reader_func);
if (reader != nullptr) {
readers_.emplace(std::make_pair(config.channel_name, reader));
}
return reader;
}
可以看到,这里其实是调用了NodeChannelImpl::CreateReader去创建的。再看这个函数
template <typename MessageT>
auto NodeChannelImpl::CreateReader(const ReaderConfig& config,
const CallbackFunc<MessageT>& reader_func)
-> std::shared_ptr<Reader<MessageT>> {
// 这里主要是将config转成了attr
proto::RoleAttributes role_attr;
role_attr.set_channel_name(config.channel_name);
role_attr.mutable_qos_profile()->CopyFrom(config.qos_profile);
// 继续调用下一层接口
return this->template CreateReader<MessageT>(role_attr, reader_func,
config.pending_queue_size);
}
template <typename MessageT>
auto NodeChannelImpl::CreateReader(const proto::RoleAttributes& role_attr,
const CallbackFunc<MessageT>& reader_func,
uint32_t pending_queue_size)
-> std::shared_ptr<Reader<MessageT>> {
// topic 不允许为空
if (!role_attr.has_channel_name() || role_attr.channel_name().empty()) {
AERROR << "Can't create a reader with empty channel name!";
return nullptr;
}
proto::RoleAttributes new_attr(role_attr);
FillInAttr<MessageT>(&new_attr);
std::shared_ptr<Reader<MessageT>> reader_ptr = nullptr;
// 实际跑的是reality_mode, 这里才将Reader new出来
if (!is_reality_mode_) {
reader_ptr =
std::make_shared<blocker::IntraReader<MessageT>>(new_attr, reader_func);
} else {
reader_ptr = std::make_shared<Reader<MessageT>>(new_attr, reader_func,
pending_queue_size);
}
RETURN_VAL_IF_NULL(reader_ptr, nullptr);
// new reader后,调用init初始化
RETURN_VAL_IF(!reader_ptr->Init(), nullptr);
return reader_ptr;
}
这里主要还是对config做个到attr的转换,然后再调用下一层接口,下一层接口中才会真正new Reader,并且对Reader做初始化动作。我们看下Reader的构造:
template <typename MessageT>
Reader<MessageT>::Reader(const proto::RoleAttributes& role_attr,
const CallbackFunc<MessageT>& reader_func,
uint32_t pending_queue_size)
: ReaderBase(role_attr),
pending_queue_size_(pending_queue_size),
reader_func_(reader_func) {
// 构造里主要一步就是new blocker
blocker_.reset(new blocker::Blocker<MessageT>(blocker::BlockerAttr(
role_attr.qos_profile().depth(), role_attr.channel_name())));
}
这个blocker主要用于缓存消息,Blocker继承自BlockerBase,我们看下Blocker的代码:
class Blocker : public BlockerBase {
// ......
BlockerAttr attr_;
MessageQueue observed_msg_queue_;
MessageQueue published_msg_queue_;
mutable std::mutex msg_mutex_;
CallbackMap published_callbacks_;
mutable std::mutex cb_mutex_;
MessageType dummy_msg_;
};
observed_msg_queue_ 是观察队列,published_msg_queue_是发布队列,发布队列的作用是用于缓存消息,调用Enqueue可以进行塞队列操作。观察队列是方便用户观察队列中的消息,当调用Observe的时候,就会把发布队列拷贝一份给观察队列。
我们再看init函数:
template <typename MessageT>
bool Reader<MessageT>::Init() {
if (init_.exchange(true)) {
return true;
}
std::function<void(const std::shared_ptr<MessageT>&)> func;
// 这里会看创建reader的时候是否传入callback
if (reader_func_ != nullptr) {
func = [this](const std::shared_ptr<MessageT>& msg) {
this->Enqueue(msg);
this->reader_func_(msg);
};
} else {
func = [this](const std::shared_ptr<MessageT>& msg) { this->Enqueue(msg); };
}
auto sched = scheduler::Instance();
croutine_name_ = role_attr_.node_name() + "_" + role_attr_.channel_name();
// 创建了DataVisitor,可以理解成数据存储器
auto dv = std::make_shared<data::DataVisitor<MessageT>>(
role_attr_.channel_id(), pending_queue_size_);
// Using factory to wrap templates.
// 创建处理协程
croutine::RoutineFactory factory =
croutine::CreateRoutineFactory<MessageT>(std::move(func), dv);
if (!sched->CreateTask(factory, croutine_name_)) {
AERROR << "Create Task Failed!";
init_.store(false);
return false;
}
// 这里是真正创建receiver的地方
receiver_ = ReceiverManager<MessageT>::Instance()->GetReceiver(role_attr_);
this->role_attr_.set_id(receiver_->id().HashValue());
channel_manager_ =
service_discovery::TopologyManager::Instance()->channel_manager();
JoinTheTopology();
return true;
}
init函数可以看到主要是创建了DataVisitor 和 RoutineFactory 还有receiver_
第一步:将用户callback封装了一个func;
第二步:创建DataVisitor 和协程工厂,并创建了task,其实就是将封装的func包装成了协程;
第三步:创建了reader,并且reader和writer不同的地方是:reader都是由ReceiverManager统一管理;
第四步:获取channel_manager,并且加入到拓扑网络中。
我们先看下DataVisitor 主要是做什么的:
template <typename M0>
class DataVisitor<M0, NullType, NullType, NullType> : public DataVisitorBase {
public:
explicit DataVisitor(const VisitorConfig& configs)
: buffer_(configs.channel_id, new BufferType<M0>(configs.queue_size)) {
DataDispatcher<M0>::Instance()->AddBuffer(buffer_);
data_notifier_->AddNotifier(buffer_.channel_id(), notifier_);
}
DataVisitor(uint64_t channel_id, uint32_t queue_size)
: buffer_(channel_id, new BufferType<M0>(queue_size)) {
DataDispatcher<M0>::Instance()->AddBuffer(buffer_);
data_notifier_->AddNotifier(buffer_.channel_id(), notifier_);
}
bool TryFetch(std::shared_ptr<M0>& m0) { // NOLINT
if (buffer_.Fetch(&next_msg_index_, m0)) {
next_msg_index_++;
return true;
}
return false;
}
private:
ChannelBuffer<M0> buffer_;
};
我这里只列出了处理一个消息的,其实还有处理多个消息融合的,这里暂不对多消息融合做分析,只分析单个消息的处理,多消息融合可以看下data_fusion_。DataVisitor本质功能就是数据存储器,buffer_里会存放接收到的数据,TryFetch的作用则是从buffer_中获取数据。buffer_的添加是通过DataDispatcher::AddBuffer,那这个DataDispatcher有何作用?看下这里的这个DataDispatcher::AddBuffer:
template <typename T>
void DataDispatcher<T>::AddBuffer(const ChannelBuffer<T>& channel_buffer) {
std::lock_guard<std::mutex> lock(buffers_map_mutex_);
auto buffer = channel_buffer.Buffer();
BufferVector* buffers = nullptr;
if (buffers_map_.Get(channel_buffer.channel_id(), &buffers)) {
buffers->emplace_back(buffer);
} else {
BufferVector new_buffers = {buffer};
buffers_map_.Set(channel_buffer.channel_id(), new_buffers);
}
}
可以看到DataDispatcher主要是将buffer塞进对应channel的buffers中,buffers_map_是个hashmap,AtomicHashMap<uint64_t, BufferVector> buffers_map_; 这块底层是cyber实现,AtomicHashMap有数量限制。
AddBuffer后会调用DataNotifier::AddNotifier,看下代码:
inline void DataNotifier::AddNotifier(
uint64_t channel_id, const std::shared_ptr<Notifier>& notifier) {
std::lock_guard<std::mutex> lock(notifies_map_mutex_);
NotifyVector* notifies = nullptr;
if (notifies_map_.Get(channel_id, ¬ifies)) {
notifies->emplace_back(notifier);
} else {
NotifyVector new_notify = {notifier};
notifies_map_.Set(channel_id, new_notify);
}
}
AddNotifier主要是在notifies_map_中添加了notifiers。添加的notifiers中是Notifier对象,这个Notifier主要是封装了一个callback。这个callback将会在消息接收到的时候被调用。
我们再看init的其他流程:
DataVisitor构造完事后,调用了croutine::CreateRoutineFactory创建了一个croutine::RoutineFactory,主要是创建协程工厂,并构建出要封装为协程的函数。协程做的就是调用 dv->TryFetch() 取数据,然后执行 Reader::Init()中设置的回调函数。
然后是Scheduler 创建任务,在CreateTask() 函数中,调用 visitor->RegisterNotifyCallback() 函数。可以看下这个函数:
void RegisterNotifyCallback(std::function<void()>&& callback) {
notifier_->callback = callback;
}
这个函数其实就是设置notifier_的回调,当执行该回调的时候会执行Scheduler::NotifyProcessor来通知Processor来执行协程也就是CreateRoutineFactory里的factory.create_routine。本质就是用于唤醒相应的协程来处理该新消息。
再看init中的最后一步,创建receiver:
template <typename MessageT>
auto ReceiverManager<MessageT>::GetReceiver(
const proto::RoleAttributes& role_attr) ->
typename std::shared_ptr<transport::Receiver<MessageT>> {
std::lock_guard<std::mutex> lock(receiver_map_mutex_);
// because multi reader for one channel will write datacache multi times,
// so reader for datacache we use map to keep one instance for per channel
const std::string& channel_name = role_attr.channel_name();
if (receiver_map_.count(channel_name) == 0) {
receiver_map_[channel_name] =
transport::Transport::Instance()->CreateReceiver<MessageT>(
role_attr, [](const std::shared_ptr<MessageT>& msg,
const transport::MessageInfo& msg_info,
const proto::RoleAttributes& reader_attr) {
(void)msg_info;
(void)reader_attr;
PerfEventCache::Instance()->AddTransportEvent(
TransPerf::DISPATCH, reader_attr.channel_id(),
msg_info.seq_num());
data::DataDispatcher<MessageT>::Instance()->Dispatch(
reader_attr.channel_id(), msg);
PerfEventCache::Instance()->AddTransportEvent(
TransPerf::NOTIFY, reader_attr.channel_id(),
msg_info.seq_num());
});
}
return receiver_map_[channel_name];
}
这个函数的功能就是创建一个receiver,当底层通信模块接收到数据之后,将会调用到传入的回调函数,在回调函数当中我们看到了上面讲到的data::DataDispatcher,调用了其Dispatch函数,将收到的数据进行分发。
我们再接着看下DataDispatcher:
template <typename T>
bool DataDispatcher<T>::Dispatch(const uint64_t channel_id,
const std::shared_ptr<T>& msg) {
BufferVector* buffers = nullptr;
if (apollo::cyber::IsShutdown()) {
return false;
}
if (buffers_map_.Get(channel_id, &buffers)) {
for (auto& buffer_wptr : *buffers) {
if (auto buffer = buffer_wptr.lock()) {
std::lock_guard<std::mutex> lock(buffer->Mutex());
buffer->Fill(msg);
}
}
} else {
return false;
}
return notifier_->Notify(channel_id);
}
这里主要还是对buffer填充,并且通过Notify唤醒协程。协程那块我们上面有分析,就会进行TryFetch来获取数据。
参考链接:
https://blog.csdn.net/A707471534/article/details/123303369?spm=1001.2014.3001.5502
https://dingfen.github.io/apollo/2020/11/07/CyberCommu2.html