服务发现
【Apollo 6.0】 cyber rt是如何使用Reader读取到Writer发送的数据(顶层逻辑)
【Apollo 6.0】 cyber rt是如何使用Reader读取到Writer发送的数据(底层逻辑)
在上一章中我们分析了底层通信的流程,其中对于服务发现部分我们没有进行分析,这里我们来分析一下。
上一章中对于reader和writer,在init的时候,都会调用JoinTheTopology加入服务的拓扑:
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);
}
JoinTheTopology主要就是使用了channel_manager_,那么这个 channel_manager_作为channel的管理器,管理着节点的接入和退出工作,channel_manager_在Reader::Init()中进行了初始化工作
channel_manager_ =
service_discovery::TopologyManager::Instance()->channel_manager();
channel_manager是在TopologyManager中被创建的,TopologyManager为了网络拓扑的管理单例类,管理着三个相关的拓扑网络,分析TopologyManager:
TopologyManager::TopologyManager()
: init_(false),
node_manager_(nullptr),
channel_manager_(nullptr),
service_manager_(nullptr),
participant_(nullptr),
participant_listener_(nullptr) {
Init();
}
bool TopologyManager::Init() {
if (init_.exchange(true)) {
return true;
}
node_manager_ = std::make_shared<NodeManager>();
channel_manager_ = std::make_shared<ChannelManager>();
service_manager_ = std::make_shared<ServiceManager>();
CreateParticipant();
bool result =
InitNodeManager() && InitChannelManager() && InitServiceManager();
if (!result) {
AERROR << "init manager failed.";
participant_ = nullptr;
delete participant_listener_;
participant_listener_ = nullptr;
node_manager_ = nullptr;
channel_manager_ = nullptr;
service_manager_ = nullptr;
init_.store(false);
return false;
}
return true;
}
可以看到在构造TopologyManager的时候调用了其Init函数,这里我们逐步的来进行解析:
Manager::Manager()
: is_shutdown_(false),
is_discovery_started_(false),
allowed_role_(0),
change_type_(proto::ChangeType::CHANGE_PARTICIPANT),
channel_name_(""),
publisher_(nullptr),
subscriber_(nullptr),
listener_(nullptr) {
host_name_ = common::GlobalData::Instance()->HostName();
process_id_ = common::GlobalData::Instance()->ProcessId();
}
node_manager_ = std::make_shared<NodeManager>()
NodeManager::NodeManager() {
allowed_role_ |= 1 << RoleType::ROLE_NODE;
change_type_ = ChangeType::CHANGE_NODE;
channel_name_ = "node_change_broadcast";
}
channel_manager_ = std::make_shared<ChannelManager>();
ChannelManager::ChannelManager() {
allowed_role_ |= 1 << RoleType::ROLE_WRITER;
allowed_role_ |= 1 << RoleType::ROLE_READER;
change_type_ = ChangeType::CHANGE_CHANNEL;
channel_name_ = "channel_change_broadcast";
exempted_msg_types_.emplace(message::MessageType<message::RawMessage>());
exempted_msg_types_.emplace(message::MessageType<message::PyMessageWrap>());
}
service_manager_ = std::make_shared<ServiceManager>();
ServiceManager::ServiceManager() {
allowed_role_ |= 1 << RoleType::ROLE_SERVER;
allowed_role_ |= 1 << RoleType::ROLE_CLIENT;
change_type_ = ChangeType::CHANGE_SERVICE;
channel_name_ = "service_change_broadcast";
}
可以看到上面三个manager在构造的时候只是做简单的设置,下面继续分析CreateParticipant
bool TopologyManager::CreateParticipant() {
std::string participant_name =
common::GlobalData::Instance()->HostName() + '+' +
std::to_string(common::GlobalData::Instance()->ProcessId());
participant_listener_ = new ParticipantListener(std::bind(
&TopologyManager::OnParticipantChange, this, std::placeholders::_1));
participant_ = std::make_shared<transport::Participant>(
participant_name, 11511, participant_listener_);
return true;
}
Participant::Participant(const std::string& name, int send_port,
eprosima::fastrtps::ParticipantListener* listener)
: shutdown_(false),
name_(name),
send_port_(send_port),
listener_(listener),
fastrtps_participant_(nullptr) {}
在CreateParticipant中最重要的就是transport::Participant,他主要就是去创建RTSP服务,cyber的服务发现,就是使用RTSP去实现的。
bool TopologyManager::InitNodeManager() {
return node_manager_->StartDiscovery(participant_->fastrtps_participant());
}
bool TopologyManager::InitChannelManager() {
return channel_manager_->StartDiscovery(participant_->fastrtps_participant());
}
bool TopologyManager::InitServiceManager() {
return service_manager_->StartDiscovery(participant_->fastrtps_participant());
}
eprosima::fastrtps::Participant* Participant::fastrtps_participant() {
if (shutdown_.load()) {
return nullptr;
}
std::lock_guard<std::mutex> lk(mutex_);
if (fastrtps_participant_ != nullptr) {
return fastrtps_participant_;
}
CreateFastRtpsParticipant(name_, send_port_, listener_);
return fastrtps_participant_;
}
bool Manager::StartDiscovery(RtpsParticipant* participant) {
if (participant == nullptr) {
return false;
}
if (is_discovery_started_.exchange(true)) {
return true;
}
if (!CreatePublisher(participant) || !CreateSubscriber(participant)) {
AERROR << "create publisher or subscriber failed.";
StopDiscovery();
return false;
}
return true;
}
通过CreateFastRtpsParticipant进行FastRtps的创建与初始化,之后将会使用该对象进行服务发现,且将会使用环境变量CYBER_DOMAIN_ID作为domain_id,因此如果两台在同个局域网的机器,如果想要进行通信的话,则CYBER_DOMAIN_ID需要一致。
并且在StartDiscovery中创建了Publisher和Subscriber,后面就可以通过这两个进行读写。
当创建完Rtsp之后,那么就要等待其他节点的接入/退出,然后服务发现通知原有节点,那么节点是什么时候接入/退出的呢。
对于服务发现来说,当一个节点接入的时候,需要发送接入的消息,因此我们可以查找Publisher::write的相关代码,进行反向查找,Publisher是在Manager中进行创建的,因此进行查找:
Manager::Join
Manager::Publish
publisher_->write
可以看到顶层是Join函数,实现了节点加入的消息通知,在代码中搜索Join(这里使用的是VS CODE的转到引用功能)
可以看到node_manager、service_manager和channel_manager都调用了父类的Join函数,我们这里分析的是Reader和Writer模式,因为Reader和Writer是由node创建的,因此分析node_manager:
explicit NodeChannelImpl(const std::string& node_name)
: is_reality_mode_(true), node_name_(node_name) {
node_attr_.set_host_name(common::GlobalData::Instance()->HostName());
node_attr_.set_host_ip(common::GlobalData::Instance()->HostIp());
node_attr_.set_process_id(common::GlobalData::Instance()->ProcessId());
node_attr_.set_node_name(node_name);
uint64_t node_id = common::GlobalData::RegisterNode(node_name);
node_attr_.set_node_id(node_id);
is_reality_mode_ = common::GlobalData::Instance()->IsRealityMode();
if (is_reality_mode_) {
node_manager_ =
service_discovery::TopologyManager::Instance()->node_manager();
node_manager_->Join(node_attr_, RoleType::ROLE_NODE);
}
}
可以看到在NodeChannelImpl的构造函数当中会调用node_manager_->Join在加入网络,之后node将会调用NodeChannelImpl::CreateReader来创建Reader,根据上一章的分析,在Reader::Init()会调用JoinTheTopology来加入拓扑
当node和reader被创建的时候都会调用Join进行通知加入了node和reader,因此我们需要分析下Join,看是如何实现的
bool Manager::Join(const RoleAttributes& attr, RoleType role,
bool need_publish) {
if (is_shutdown_.load()) {
ADEBUG << "the manager has been shut down.";
return false;
}
RETURN_VAL_IF(!((1 << role) & allowed_role_), false);
RETURN_VAL_IF(!Check(attr), false);
ChangeMsg msg;
Convert(attr, role, OperateType::OPT_JOIN, &msg);
Dispose(msg);
if (need_publish) {
return Publish(msg);
}
return true;
}
在Join里面最主要的就是Dispose和Publish这个两个函数,Publish不用说了就是将节点加入网络的消息进行发送,因此需要对Dispose进行分析,Dispose在Manager中是一个纯虚函数,其实现都在三个子类当中:
void NodeManager::Dispose(const ChangeMsg& msg) {
if (msg.operate_type() == OperateType::OPT_JOIN) {
DisposeJoin(msg);
} else {
DisposeLeave(msg);
}
Notify(msg);
}
void ChannelManager::Dispose(const ChangeMsg& msg) {
if (msg.operate_type() == OperateType::OPT_JOIN) {
DisposeJoin(msg);
} else {
DisposeLeave(msg);
}
Notify(msg);
}
继续分析DisposeJoin
void NodeManager::DisposeJoin(const ChangeMsg& msg) {
auto node = std::make_shared<RoleNode>(msg.role_attr(), msg.timestamp());
uint64_t key = node->attributes().node_id();
// duplicated node
if (!nodes_.Add(key, node, false)) {
RolePtr existing_node;
if (!nodes_.Search(key, &existing_node)) {
nodes_.Add(key, node);
return;
}
RolePtr newer_node = existing_node;
if (node->IsEarlierThan(*newer_node)) {
nodes_.Add(key, node);
} else {
newer_node = node;
}
if (newer_node->attributes().process_id() == process_id_ &&
newer_node->attributes().host_name() == host_name_) {
AERROR << "this process will be terminated due to duplicated node["
<< node->attributes().node_name()
<< "], please ensure that each node has a unique name.";
AsyncShutdown();
}
}
}
void ChannelManager::DisposeJoin(const ChangeMsg& msg) {
ScanMessageType(msg);
Vertice v(msg.role_attr().node_name());
Edge e;
e.set_value(msg.role_attr().channel_name());
if (msg.role_type() == RoleType::ROLE_WRITER) {
if (msg.role_attr().has_proto_desc() &&
msg.role_attr().proto_desc() != "") {
message::ProtobufFactory::Instance()->RegisterMessage(
msg.role_attr().proto_desc());
}
auto role = std::make_shared<RoleWriter>(msg.role_attr(), msg.timestamp());
node_writers_.Add(role->attributes().node_id(), role);
channel_writers_.Add(role->attributes().channel_id(), role);
e.set_src(v);
} else {
auto role = std::make_shared<RoleReader>(msg.role_attr(), msg.timestamp());
node_readers_.Add(role->attributes().node_id(), role);
channel_readers_.Add(role->attributes().channel_id(), role);
e.set_dst(v);
}
node_graph_.Insert(e);
}
可以看到对于node来说就是根据node_id加入到nodes_当中,这个nodes_底层就是一个Map。而对于channel来说就是根据node_id和channel_id加入到对的map当中,而且还会将channel的关系加入到图node_graph_当中,可以通过node_graph_来知道channel的流向。
在Dispose当中,还能看到调用了Notify这个函数,这个函数是做什么的呢,其实就是发送信号,在Reader::JoinTheTopology()当中调用了channel_manager_->AddChangeListener来添加信号的槽函数,因此通过这个信号将会执行OnChannelChange
...
change_conn_ = channel_manager_->AddChangeListener(std::bind(
&Reader<MessageT>::OnChannelChange, this, std::placeholders::_1))
...
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自己加入,因此change_msg.role_type()为proto::RoleType::ROLE_READER所以不会执行该函数,但是如果有其他的相同channel的writer加入节点的话,那么将会调用到Enable启动接收器。
当执行完DisposeJoin将会调用Publish发送加入节点的消息,那么我们就可分析下Subscriber。在Manager::StartDiscovery中调用了Manager::CreateSubscriber来创建Subscriber,其中注册了一个回调函数Manager::OnRemoteChange
bool Manager::CreateSubscriber(RtpsParticipant* participant) {
RtpsSubscriberAttr sub_attr;
RETURN_VAL_IF(
!AttributesFiller::FillInSubAttr(
channel_name_, QosProfileConf::QOS_PROFILE_TOPO_CHANGE, &sub_attr),
false);
listener_ = new SubscriberListener(
std::bind(&Manager::OnRemoteChange, this, std::placeholders::_1));
subscriber_ = eprosima::fastrtps::Domain::createSubscriber(
participant, sub_attr, listener_);
return subscriber_ != nullptr;
}
看下Manager::OnRemoteChange代码就知道整个服务发现的流程是怎么样的了:
void Manager::OnRemoteChange(const std::string& msg_str) {
if (is_shutdown_.load()) {
ADEBUG << "the manager has been shut down.";
return;
}
ChangeMsg msg;
RETURN_IF(!message::ParseFromString(msg_str, &msg));
if (IsFromSameProcess(msg)) {
return;
}
RETURN_IF(!Check(msg.role_attr()));
Dispose(msg);
}
当收到节点加入的消息之后,这里又调用了一次Dispose,这样就完成了节点的接入通知。
当Publish发送消息的时候,不只是执行了Manager::OnRemoteChange的回调,还记得在TopologyManager::CreateParticipant当中创建ParticipantListener时传入的回调TopologyManager::OnParticipantChange吗,这个回调当Participant有数据传输的时候,也会被调用:
void TopologyManager::OnParticipantChange(const PartInfo& info) {
ChangeMsg msg;
if (!Convert(info, &msg)) {
return;
}
if (!init_.load()) {
return;
}
if (msg.operate_type() == OperateType::OPT_LEAVE) {
auto& host_name = msg.role_attr().host_name();
int process_id = msg.role_attr().process_id();
node_manager_->OnTopoModuleLeave(host_name, process_id);
channel_manager_->OnTopoModuleLeave(host_name, process_id);
service_manager_->OnTopoModuleLeave(host_name, process_id);
}
change_signal_(msg);
}
TopologyManager作为三个manager的管理类,当节点退出网络的时候,将会从三个manager当中删除该节点。
这里还有一个疑问,当后面加入的节点,是怎么知道原有节点的呢?cyber这里使用的是rtsp的qos去做的,当设置了KEEP_ALL_HISTORY_QOS跟TRANSIENT_LOCAL_DURABILITY_QOS时,每个新加入的节点会拿到之前的所有节点。
到了这里我们可以总结下服务发现的整个流程:
加入节点:
Node::Node —> NodeChannelImpl::NodeChannelImpl —> Manager::Join —> NodeManager::Dispose —> Manager::Publish —> Manager::OnRemoteChange
Node发送完节点接入信息后:
NodeChannelImpl::CreateReader —> Reader::Init() —> Reader::JoinTheTopology() —> Manager::Join —> ChannelManager::Dispose —> Manager::Publish —> Manager::OnRemoteChange
当其他节点收到Node接入和Channel接入的消息之后,将会创建对应的Node和Chaanel用来进行数据通信
大致的流程就是这样,至此cyber服务发现的流程就解析完毕。