【apollo】cyber底层通信--订阅方如何获取数据

【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, &notifies)) {
    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

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值