【Apollo 6.0】 cyber rt是如何使用Reader读取到Writer发送的数据(底层逻辑)

Reader是如何读取到Writer的数据(底层逻辑)

【Apollo 6.0】 cyber rt是如何使用Reader读取到Writer发送的数据(顶层逻辑)
【Apollo 6.0】cyber rt是如何进行节点间管理的

上一篇文章(上层逻辑)中讲了reader读取数据的流程,但是里面没有讲到底层的Tansport是这么进行进程间通信,这里我们来解析一下。

在ReceiverManager::GetReceiver调用了transport::Transport::Instance()->CreateReceiver进行Receiver的创建:

template <typename M>
auto Transport::CreateReceiver(
    const RoleAttributes& attr,
    const typename Receiver<M>::MessageListener& msg_listener,
    const OptionalMode& mode) -> typename std::shared_ptr<Receiver<M>> {
  if (is_shutdown_.load()) {
    AINFO << "transport has been shut down.";
    return nullptr;
  }

  std::shared_ptr<Receiver<M>> receiver = nullptr;
  RoleAttributes modified_attr = attr;
  if (!modified_attr.has_qos_profile()) {
    modified_attr.mutable_qos_profile()->CopyFrom(
        QosProfileConf::QOS_PROFILE_DEFAULT);
  }
  
  //根据通信模式来创建Receiver,apollo默认使用的是OptionalMode::HYBRID,也就是default分支
  switch (mode) {
    case OptionalMode::INTRA:
      receiver =
          std::make_shared<IntraReceiver<M>>(modified_attr, msg_listener);
      break;

    case OptionalMode::SHM:
      receiver = std::make_shared<ShmReceiver<M>>(modified_attr, msg_listener);
      break;

    case OptionalMode::RTPS:
      receiver = std::make_shared<RtpsReceiver<M>>(modified_attr, msg_listener);
      break;

    default:
      receiver = std::make_shared<HybridReceiver<M>>(
          modified_attr, msg_listener, participant());
      break;
  }

  RETURN_VAL_IF_NULL(receiver, nullptr);
  if (mode != OptionalMode::HYBRID) {
    receiver->Enable();
  }
  return receiver;
}

OptionalMode::HYBRID这个通信模式其实就是包含了上面三个模式,OptionalMode::HYBRID模式会根据对方的网络状态来选择需要的模式。

因此我们这里分析HybridReceiver:

template <typename M>
HybridReceiver<M>::HybridReceiver(
    const RoleAttributes& attr,
    const typename Receiver<M>::MessageListener& msg_listener,
    const ParticipantPtr& participant)
    : Receiver<M>(attr, msg_listener),
      history_(nullptr),
      participant_(participant) {
  InitMode();
  ObtainConfig();
  InitHistory();
  InitReceivers();
  InitTransmitters();
}

template <typename M>
void HybridReceiver<M>::InitMode() {
  mode_ = std::make_shared<proto::CommunicationMode>();
  mapping_table_[SAME_PROC] = mode_->same_proc();
  mapping_table_[DIFF_PROC] = mode_->diff_proc();
  mapping_table_[DIFF_HOST] = mode_->diff_host();
}

template <typename M>
void HybridReceiver<M>::ObtainConfig() {
  auto& global_conf = common::GlobalData::Instance()->Config();
  if (!global_conf.has_transport_conf()) {
    return;
  }
  if (!global_conf.transport_conf().has_communication_mode()) {
    return;
  }
  mode_->CopyFrom(global_conf.transport_conf().communication_mode());

  mapping_table_[SAME_PROC] = mode_->same_proc();
  mapping_table_[DIFF_PROC] = mode_->diff_proc();
  mapping_table_[DIFF_HOST] = mode_->diff_host();
}
//根据配置里的qos,设置缓存history_长度,可见HybridReceiver学RTPS也设置了读侧的消息缓存。需要注意这个缓存只是为RTPS模式设置的,所以只有当这个消息有qos本地缓存要求时才真正有意义
template <typename M>
void HybridReceiver<M>::InitHistory() {
  HistoryAttributes history_attr(this->attr_.qos_profile().history(),
                                 this->attr_.qos_profile().depth());
  history_ = std::make_shared<History<M>>(history_attr);
  if (this->attr_.qos_profile().durability() ==
      QosDurabilityPolicy::DURABILITY_TRANSIENT_LOCAL) {
    history_->Enable();
  }
}

template <typename M>
void HybridReceiver<M>::InitReceivers() {
  std::set<OptionalMode> modes;
  modes.insert(mode_->same_proc());
  modes.insert(mode_->diff_proc());
  modes.insert(mode_->diff_host());
  auto listener = std::bind(&HybridReceiver<M>::OnNewMessage, this,
                            std::placeholders::_1, std::placeholders::_2);
  for (auto& mode : modes) {
    switch (mode) {
      case OptionalMode::INTRA:
        receivers_[mode] =
            std::make_shared<IntraReceiver<M>>(this->attr_, listener);
        break;
      case OptionalMode::SHM:
        receivers_[mode] =
            std::make_shared<ShmReceiver<M>>(this->attr_, listener);
        break;
      default:
        receivers_[mode] =
            std::make_shared<RtpsReceiver<M>>(this->attr_, listener);
        break;
    }
  }
}

template <typename M>
void HybridReceiver<M>::InitTransmitters() {
  std::unordered_map<uint64_t, RoleAttributes> empty;
  for (auto& item : receivers_) {
    transmitters_[item.first] = empty;
  }
}

HybridReceiver构造函数所调用构造函数就是进行初始化的作用,主要就是初始化mapping_table_ 、receivers_ 和transmitters_ 。对于 mapping_table_的key我们这里讲一下:

//SAME_PROC:同一进程,默认使用INTRA模式通信,这种情况发生在同一个module内的通信,比如同一个module里不同的component相互通信,或是同一个component里自我通信
//DIFF_PROC:不同进程但在同一host,默认使用SHM,这种情况基本上就是单机上不同module互相通信时会用到,也是用得最多的模式
//DIFF_HOST:不同host上的不同进程,默认使用RTPS,这只有在不同host上通信时才用到
//通过GetRelation函数来判断使用什么
template <typename M>
Relation HybridReceiver<M>::GetRelation(const RoleAttributes& opposite_attr) {
  if (opposite_attr.channel_name() != this->attr_.channel_name()) {
    return NO_RELATION;
  }

  if (opposite_attr.host_ip() != this->attr_.host_ip()) {
    return DIFF_HOST;
  }

  if (opposite_attr.process_id() != this->attr_.process_id()) {
    return DIFF_PROC;
  }

  return SAME_PROC;
}

在HybridReceiver::HybridReceiver里创建了三种通信模式的receiver,这里我们将下apollo里使用得最多的ShmReceiver:

template <typename M>
ShmReceiver<M>::ShmReceiver(
    const RoleAttributes& attr,
    const typename Receiver<M>::MessageListener& msg_listener)
    : Receiver<M>(attr, msg_listener) {
  dispatcher_ = ShmDispatcher::Instance();
}

ShmDispatcher作为ShmReceiver的数据分发器:

ShmDispatcher::ShmDispatcher() : host_id_(0) { Init(); }

bool ShmDispatcher::Init() {
  host_id_ = common::Hash(GlobalData::Instance()->HostIp());
  notifier_ = NotifierFactory::CreateNotifier();
  thread_ = std::thread(&ShmDispatcher::ThreadFunc, this);
  //线程属性配置
  scheduler::Instance()->SetInnerThreadAttr("shm_disp", &thread_);
  return true;
}

跟上层通信类似,底层的ShmDispatcher也有个notifier_:

auto NotifierFactory::CreateNotifier() -> NotifierPtr {
  std::string notifier_type(ConditionNotifier::Type());
  auto& g_conf = GlobalData::Instance()->Config();
  if (g_conf.has_transport_conf() && g_conf.transport_conf().has_shm_conf() &&
      g_conf.transport_conf().shm_conf().has_notifier_type()) {
    notifier_type = g_conf.transport_conf().shm_conf().notifier_type();
  }

  ADEBUG << "notifier type: " << notifier_type;

  if (notifier_type == MulticastNotifier::Type()) {
    return CreateMulticastNotifier();
  } else if (notifier_type == ConditionNotifier::Type()) {
    return CreateConditionNotifier();
  }

  AINFO << "unknown notifier, we use default notifier: " << notifier_type;
  return CreateConditionNotifier();
}

auto NotifierFactory::CreateConditionNotifier() -> NotifierPtr {
  return ConditionNotifier::Instance();
}

auto NotifierFactory::CreateMulticastNotifier() -> NotifierPtr {
  return MulticastNotifier::Instance();
}

这里会根据notifier_type来创建ConditionNotifier或者MulticastNotifier。对于ConditionNotifier,该notifier里面也是使用了一个共享内存操作,当write端发送数据的时候会调用其Notify函数,向共享内存中写入数据,然后read端可以调用Listent函数来读取数据,数据是描述read端怎么从数据通信的共享内存中获取发送的数据。而对于MulticastNotifier则是使用了socket进行通知,ip:239.255.0.100 port:8888。具体的代码就不分析了,继续分析ShmDispatcher::Init():

thread_ = std::thread(&ShmDispatcher::ThreadFunc, this)
scheduler::Instance()->SetInnerThreadAttr("shm_disp", &thread_);

这里创建了ShmDispatcher的处理线程,这个线程是数据读取的关键,因为还没有创建对应的数据读取工具对象,因此这里还不做分析,ShmDispatcher::Init()结束,我们继续分析Transport::CreateReceiver。

 if (mode != OptionalMode::HYBRID) {
    receiver->Enable();
  }

当模式不为OptionalMode::HYBRID就启动receiver,但是我们就是这个模式啊,那HybridReceiver::Enable到底是在哪里被调用的呢?

其实就是在上一篇文章里面最后没讲到的Reader::JoinTheTopology()里

template <typename MessageT>
void Reader<MessageT>::JoinTheTopology() {
  // add listener
  change_conn_ = channel_manager_->AddChangeListener(std::bind(
      &Reader<MessageT>::OnChannelChange, this, std::placeholders::_1));

  // get peer writers
  const std::string& channel_name = this->role_attr_.channel_name();
  std::vector<proto::RoleAttributes> writers;
  channel_manager_->GetWritersOfChannel(channel_name, &writers);
  for (auto& writer : writers) {
    receiver_->Enable(writer);
  }
  channel_manager_->Join(this->role_attr_, proto::RoleType::ROLE_READER,
                         message::HasSerializer<MessageT>::value);
}

template <typename MessageT>
void Reader<MessageT>::OnChannelChange(const proto::ChangeMsg& change_msg) {
  if (change_msg.role_type() != proto::RoleType::ROLE_WRITER) {
    return;
  }

  auto& writer_attr = change_msg.role_attr();
  if (writer_attr.channel_name() != this->role_attr_.channel_name()) {
    return;
  }

  auto operate_type = change_msg.operate_type();
  if (operate_type == proto::OperateType::OPT_JOIN) {
    receiver_->Enable(writer_attr);
  } else {
    receiver_->Disable(writer_attr);
  }
}

Reader::JoinTheTopology()里具体功能我们这里还是不做分析,这里粗略的讲解一下,通过channel_manager_->AddChangeListener函数,当reader所对应的channel发生变化的时候,将会触发Reader::OnChannelChange函数,如果是有Writer加入operate_type == proto::OperateType::OPT_JOIN则调用Enable启动receiver。而如果已经保存了channel所对应的writers,则直接遍历启动所有的receiver,好了,这个JoinTheTopology是服务发现功能的相关函数,后面会专门写一篇文章来进行讲解,这里直接分析Enable函数。

template <typename M>
void HybridReceiver<M>::Enable(const RoleAttributes& opposite_attr) {
  auto relation = GetRelation(opposite_attr);
  RETURN_IF(relation == NO_RELATION);

  uint64_t id = opposite_attr.id();
  std::lock_guard<std::mutex> lock(mutex_);
  if (transmitters_[mapping_table_[relation]].count(id) == 0) {
    transmitters_[mapping_table_[relation]].insert(
        std::make_pair(id, opposite_attr));
    receivers_[mapping_table_[relation]]->Enable(opposite_attr);
    ReceiveHistoryMsg(opposite_attr);
  }
}

HybridReceiver::Enable就是根据节点之间的关系来启动对应的通信模式,这里还是分析ShmReceiver:

template <typename M>
void ShmReceiver<M>::Enable(const RoleAttributes& opposite_attr) {
  dispatcher_->AddListener<M>(
      this->attr_, opposite_attr,
      std::bind(&ShmReceiver<M>::OnNewMessage, this, std::placeholders::_1,
                std::placeholders::_2));
}

这里调用了dispatcher_->AddListener来添加回调函数:

template <typename MessageT>
void ShmDispatcher::AddListener(const RoleAttributes& self_attr,
                                const RoleAttributes& opposite_attr,
                                const MessageListener<MessageT>& listener) {
  // FIXME: make it more clean
  auto listener_adapter = [listener](const std::shared_ptr<ReadableBlock>& rb,
                                     const MessageInfo& msg_info) {
    auto msg = std::make_shared<MessageT>();
    RETURN_IF(!message::ParseFromArray(
        rb->buf, static_cast<int>(rb->block->msg_size()), msg.get()));
    listener(msg, msg_info);
  };

  Dispatcher::AddListener<ReadableBlock>(self_attr, opposite_attr,
                                         listener_adapter);
  AddSegment(self_attr);
}

这里构建了一个新的回调,然后调用父类的AddListener:

template <typename MessageT>
void Dispatcher::AddListener(const RoleAttributes& self_attr,
                             const RoleAttributes& opposite_attr,
                             const MessageListener<MessageT>& listener) {
  if (is_shutdown_.load()) {
    return;
  }
  uint64_t channel_id = self_attr.channel_id();

  std::shared_ptr<ListenerHandler<MessageT>> handler;
  ListenerHandlerBasePtr* handler_base = nullptr;
  if (msg_listeners_.Get(channel_id, &handler_base)) {
    handler =
        std::dynamic_pointer_cast<ListenerHandler<MessageT>>(*handler_base);
    if (handler == nullptr) {
      AERROR << "please ensure that readers with the same channel["
             << self_attr.channel_name()
             << "] in the same process have the same message type";
      return;
    }
  } else {
    ADEBUG << "new reader for channel:"
           << GlobalData::GetChannelById(channel_id);
    handler.reset(new ListenerHandler<MessageT>());
    msg_listeners_.Set(channel_id, handler);
  }
  handler->Connect(self_attr.id(), opposite_attr.id(), listener);
}

Dispatcher::AddListener主要干的活就是创建handler然后存入msg_listeners_,并且将回调传入handler->Connect :

template <typename MessageT>
void ListenerHandler<MessageT>::Connect(uint64_t self_id, uint64_t oppo_id,
                                        const Listener& listener) {
  WriteLockGuard<AtomicRWLock> lock(rw_lock_);
  if (signals_.find(oppo_id) == signals_.end()) {
    signals_[oppo_id] = std::make_shared<MessageSignal>();
  }

  auto connection = signals_[oppo_id]->Connect(listener);
  if (!connection.IsConnected()) {
    AWARN << oppo_id << " " << self_id << " connect failed!";
    return;
  }

  if (signals_conns_.find(oppo_id) == signals_conns_.end()) {
    signals_conns_[oppo_id] = ConnectionMap();
  }

  signals_conns_[oppo_id][self_id] = connection;
}

这里根据传入的self_id和oppo_id分别设置对应的signal map,这里的signal其实是模仿QT的信号与槽机制实现的,简单的来说呢,就是多个槽(回调函数)可以绑定一个信号,或者一个槽绑定多个信号(apollo这里应该就实现了前一种)。apollo里是这么实现的:当signal执行其重载的括号操作符的时候,将会调用其绑定的全部槽函数。槽绑定信号是通过Connect函数,因此这里listener绑定了signals_[oppo_id]。回到ShmDispatcher::AddListener继续分析。

void ShmDispatcher::AddSegment(const RoleAttributes& self_attr) {
  uint64_t channel_id = self_attr.channel_id();
  WriteLockGuard<AtomicRWLock> lock(segments_lock_);
  if (segments_.count(channel_id) > 0) {
    return;
  }
  auto segment = SegmentFactory::CreateSegment(channel_id);
  segments_[channel_id] = segment;
  previous_indexes_[channel_id] = UINT32_MAX;
}

auto SegmentFactory::CreateSegment(uint64_t channel_id) -> SegmentPtr {
  std::string segment_type(XsiSegment::Type());
  auto& shm_conf = GlobalData::Instance()->Config();
  if (shm_conf.has_transport_conf() &&
      shm_conf.transport_conf().has_shm_conf() &&
      shm_conf.transport_conf().shm_conf().has_shm_type()) {
    segment_type = shm_conf.transport_conf().shm_conf().shm_type();
  }

  ADEBUG << "segment type: " << segment_type;

  if (segment_type == PosixSegment::Type()) {
    return std::make_shared<PosixSegment>(channel_id);
  }

  return std::make_shared<XsiSegment>(channel_id);
}

AddSegment的作用就是添加共享内存的操作类Segment,这里有两个实现PosixSegment和XsiSegment。两个的差别这里就不做分析了,反正就是里面封装了对共享内存的操作,感兴趣的可以自己分析。将Segment的对象放入到segments_当中,其数据类型为std::unordered_map< uint64_t,std::shared_ptr >。

Enable分析完了,你应该会发出这样的疑问:Enable也只是做相关类的初始化和创建工作,数据的读取流程没有体现啊,到底是在哪里实现的?

这我们就需要回过头来分析刚才没有分析的ShmDispatcher处理线程ThreadFunc:

void ShmDispatcher::ThreadFunc() {
  ReadableInfo readable_info;
  while (!is_shutdown_.load()) {
    if (!notifier_->Listen(100, &readable_info)) {
      ADEBUG << "listen failed.";
      continue;
    }

    if (readable_info.host_id() != host_id_) {
      ADEBUG << "shm readable info from other host.";
      continue;
    }

    uint64_t channel_id = readable_info.channel_id();
    uint32_t block_index = readable_info.block_index();

    {
      ReadLockGuard<AtomicRWLock> lock(segments_lock_);
      if (segments_.count(channel_id) == 0) {
        continue;
      }
      // check block index
      if (previous_indexes_.count(channel_id) == 0) {
        previous_indexes_[channel_id] = UINT32_MAX;
      }
      uint32_t& previous_index = previous_indexes_[channel_id];
      if (block_index != 0 && previous_index != UINT32_MAX) {
        if (block_index == previous_index) {
          ADEBUG << "Receive SAME index " << block_index << " of channel "
                 << channel_id;
        } else if (block_index < previous_index) {
          ADEBUG << "Receive PREVIOUS message. last: " << previous_index
                 << ", now: " << block_index;
        } else if (block_index - previous_index > 1) {
          ADEBUG << "Receive JUMP message. last: " << previous_index
                 << ", now: " << block_index;
        }
      }
      previous_index = block_index;

      ReadMessage(channel_id, block_index);
    }
  }
}

ThreadFunc是ShmDispatcher的线程处理函数,里面最为关键的就是notifier_->Listen,上面讲到当write端发送数据的时候会调用其Notify函数,向共享内存中写入数据,然后read端可以调用Listent函数来读取数据。ThreadFunc这里就在轮询的调用Listent函数,当Listren读取到数据之后,将会分析从而得到如何从数据通信的共享内存拿出数据的方式,然后调用ReadMessage读取数据。

void ShmDispatcher::ReadMessage(uint64_t channel_id, uint32_t block_index) {
  ADEBUG << "Reading sharedmem message: "
         << GlobalData::GetChannelById(channel_id)
         << " from block: " << block_index;
  auto rb = std::make_shared<ReadableBlock>();
  rb->index = block_index;
  if (!segments_[channel_id]->AcquireBlockToRead(rb.get())) {
    AWARN << "fail to acquire block, channel: "
          << GlobalData::GetChannelById(channel_id)
          << " index: " << block_index;
    return;
  }

  MessageInfo msg_info;
  const char* msg_info_addr =
      reinterpret_cast<char*>(rb->buf) + rb->block->msg_size();

  if (msg_info.DeserializeFrom(msg_info_addr, rb->block->msg_info_size())) {
    OnMessage(channel_id, rb, msg_info);
  } else {
    AERROR << "error msg info of channel:"
           << GlobalData::GetChannelById(channel_id);
  }
  segments_[channel_id]->ReleaseReadBlock(*rb);
}

从segments_中拿出channel对应的std::shared_ptr来读取共享内存中的数据, ReadMessage总的来说就是从数据共享内存里面拿出数据,然后再通过OnMessage来进行数据传递:

void ShmDispatcher::OnMessage(uint64_t channel_id,
                              const std::shared_ptr<ReadableBlock>& rb,
                              const MessageInfo& msg_info) {
  if (is_shutdown_.load()) {
    return;
  }
  ListenerHandlerBasePtr* handler_base = nullptr;
  if (msg_listeners_.Get(channel_id, &handler_base)) {
    auto handler = std::dynamic_pointer_cast<ListenerHandler<ReadableBlock>>(
        *handler_base);
    handler->Run(rb, msg_info);
  } else {
    AERROR << "Cannot find " << GlobalData::GetChannelById(channel_id)
           << "'s handler.";
  }
}

从msg_listeners_拿出channel对应的ListenerHandler,执行其Run函数:

template <typename MessageT>
void ListenerHandler<MessageT>::Run(const Message& msg,
                                    const MessageInfo& msg_info) {
  signal_(msg, msg_info);
  uint64_t oppo_id = msg_info.sender_id().HashValue();
  ReadLockGuard<AtomicRWLock> lock(rw_lock_);
  if (signals_.find(oppo_id) == signals_.end()) {
    return;
  }

  (*signals_[oppo_id])(msg, msg_info);
}

*(signals_[oppo_id])(msg, msg_info) 这调用就是上面讲的信号的发送,会执行其所有的槽函数。那么这个槽函数是什么呢,如果有从上一章中一直跟着的读者会发现,虽然经过了层层的传递和多重的封装,但是其最初始的函数就是我们在 ReceiverManager::GetReceiver中调用transport::Transport::Instance()->CreateReceiver传入的lamda。分析到这里应该是把数据从底层到上层的流向讲清楚了,总结下流程:

对于shm来说:
写端:
writer —> Segment::AcquireBlockToWrite —> notifier::Notify

读端:
ShmDispatcher::ThreadFunc —> notifier::Listen —> Segment::AcquireBlockToRead —> ListenerHandler::Run —> (*signals_[oppo_id])(msg, msg_info) —> DataDispatcher —> DataVisitor::buffer —> DataNotifier::Instance()::Notify —> callback —> Scheduler::NotifyProcessor —> 执行协程 —> 从DataVisitor::buffer拿出数据 —> 放入reader的blocker —>执行用户传入的回调(如果有的话)

参考链接:

百度Apollo系统学习-Cyber RT 通信-底层

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值