rosmaster是一个中转。
每个node都是从roscpp中继承了一堆东西。
1. topic的消息如何来的
当一个nodesubscribe一个topic时,该node就会建立一个subscription结构。含有一个subscriptionQueue,用来保存收到的msg.
谁往queue里面填冲消息?
首先了解一下TransportPublisherLink, 对于一个clientnode来说,他说subscribe的topic有很多publisher,他们可能是别的进程,TransportPublisherLink就是他们在clientnode进程内部的一个代表。
TransportPublisherLink来了数据,当那些publisher向该topic发送消息时,本clientnode对应的TransportPublisherLink::onMessageLength就被触发了,说明来数据了:
->TransportPublisherLink::onMessageLength,callback是绑定在一个connection上的。当该transportconnection上有数据时,就会调用此callback.
->TransportPublisherLink::onMessage,继而会触发此函数
->TransportPublisherLink::handleMessage,publisher_link的作用就是收到消息后转发给关联的subscription.
->Subscription::handleMessage,这个函数将该消息放入subscriptionqueue, 并且往callbackQuque里面插入一个callback,该callback会异步的被callbackQuqueManager的thread处理,callbackQueueManager维护一些workerthread, 这些workerthread会从callbackQueue里面取出一个callback执行,执行时,会从subscriptionqueue里面取出消息数据来处理。
->SubscriptionQueue::push并callback_queue_->addCallback
2 TransportPublisherLink的建立
好了,知道了,是TransportPublisherLink受到消息后,向subscriptionQueue和callbackQueue里面插入信息了,那么TransportPublisherLink怎么建立的,他如何收到消息的?
我们根据movebase的一个/move_base_simple/goal的topic来说起。
Move base node 会subscribe该topic,而运行于pc上的rviz会向该topic发送消息。用来告诉movebase目的地。
大体流程是:
1. move base启动后subscribe到该topic,此时rviz还没有启动。movebase node 会见里subscription结构
2. 当rviz启动后,rviz会链接到rosmaster
3. rosmaster 得知rviz会advertise该topic,于是就会使用python实现的xmlrpc远程调用接口调用publisherUpdate来通知clientnode有puhlisher更新。
4. client node这边收到xmlrpc的远程过程调用,会调用绑定在该remoemethod上的callback.
TopicManager::pubUpdateCallback(XmlRpc::XmlRpcValue& params,XmlRpc::XmlRpcValue& result),这个callback是通过xmlrpc_manager_->bind("publisherUpdate",boost::bind(&TopicManager::pubUpdateCallback, this, _1, _2));绑定的。那么这个callback干了什么?这样clientnode就知道有publisher来了。才能和rviz建立通知,并且clientnode能够知道来者的ip和port,以便于直接和publisher建立p2p链接,而不用rosmaster中转。
这个callback会
TopicManager::pubUpdate(const string&topic, const vector<string> &pubs)会根据topic找到对应的subscription并将新的publisher列表告诉subscription.
->Subscription::pubUpdate() ,subscription首先检查新来的publisher是否已经建立链接了,如果没有,会开始建立链接:
->Subscription::negotiateConnection这里尝试和新的publisher协商,并建立一个pendingConnection.当协商结束,pendingConnection的callback会被调用,最后成为一个正常的connection.
具体过程是:首先建立一个XmlRpcClient(peer_host,peer_port,"/", );然后调用他的XmlRpcClient::executeNonBlock(constchar* method, params)执行”requestTopic”这个rpc.这个过程是异步的。会立刻返回。那么这个远程过程调用就会发给rviz, rviz接受后,就有结果回来,就会有异步的callback通知给xmlRpcClient.
再具体一点xmlRpcClient的executeNonBlock先建立和rviz的链接,建立socket,然后建立异步connect.然后prepare一份request,这个request包含了要被远程调用的method和参数。但是并不马上发送。有专门的角色完成该工作。此时,xmlRpcClient维护了一个socket,并且也有一份request数据。,他把自己注册给XmlRpcDispatch,由xmlRpcDispatch的work()函数负责检查该fd有没有数据io,并处理数据。检查工作在一个xmlRPCManager的线程中执行:
XMLRPCManager::serverThreadFunc()
-》XmlRpcServer::work,简单调用xmlRpcDispatch::work
-》XmlRpcDispatch::work,dispatcher维护一个sourcelist, 他select各个xmlrpcsource的fd,看有没有读写事件发生,如果有,就掉用source的handleEvent处理。xmlrpcsource实际上封装了一个socketfd. 而xmlRpcClient就是一个xmlRpcSource,并且被注册进来了。
-》XmlRpcClient::handleEvent,这个函数会发送request,也会接受response.
->writeResponse
->XmlRpcSocket::nbWrite
上面提到,Subscription::negotiateConnection会建立pendingConnection,这些connection会被添加到XMLRPCManager::instance()->addASyncConnection(conn);里面。
XMLRPCManager::serverThreadFunc()除了负责触发socket的io,还负责不断检查pendingconnection是否已经完成。
voidXMLRPCManager::serverThreadFunc()这个函数是一个循环,他不断检查所有的connection,调用他们的check()函数:
-》PendingConnection::check(),检查pendingConnection是否已经ok,已经建立链接,如果是就回调subscription。而check实际是通过检查XmlRpcClient::executeCheckDone(XmlRpcValue&result)函数来实现的,该函数实际上检查connectionState是否_IDLE,那么connectionState何时成为_IDLE?当然是收到了request的结果了。我们知道XmlRpcCLient::handleEvent会处理socketio,如果收到了结果,继而会调用到XmlRpcClient::readResponse(),这个函数就会把connectionState设置为_IDLE.
一旦pendingConnection被检查发现完成了工作,就掉用:
->void Subscription::pendingConnectionDone(constPendingConnectionPtr& conn, XmlRpcValue&result),这个函数会建立如下几个物体:
TransportTCPPtr transport(newTransportTCP(&PollManager::instance()->getPollSet()));
if (transport->connect(pub_host,pub_port)) //链接topic.
{
ConnectionPtr connection(newConnection()); //connection对象
TransportPublisherLinkPtr pub_link(newTransportPublisherLink(name_,shared_from_this(), xmlrpc_uri,transport_hints_));
connection->initialize(transport, false,HeaderReceivedFunc());
pub_link->initialize(connection);
ConnectionManager::instance()->addConnection(connection);
boost::mutex::scoped_lock lock(publisher_links_mutex_);
addPublisherLink(pub_link);
}
可见,这里就是建立了connection了。
3 后面有时间可以看看connection,ConnectionManager, TransportTCP的管理