在前面几章中,我们分析了NodeHande句柄背后的行为。由于该句柄功能较多,本章将结合前几章的内容,对Nodehandle句柄的行为流程作出一个大致的梳理,详细的剖析请移步之前的章节。
在发布者主程序中,我们创建了一个NodeHandle句柄,用于管理节点操作和通信的对象。
ros::NodeHandle n;
NodeHandle在创建的时候会走以下代码
NodeHandle::NodeHandle(const std::string& ns, const M_string& remappings)
: namespace_(this_node::getNamespace())
, callback_queue_(0)
, collection_(0)
{
std::string tilde_resolved_ns;
if (!ns.empty() && ns[0] == '~')// starts with tilde
tilde_resolved_ns = names::resolve(ns);
else
tilde_resolved_ns = ns;
construct(tilde_resolved_ns, true);
initRemappings(remappings);
}
在此代码中,他先获取了命名空间然后初始化回调队列和集合。该代码中,核心代码为init与construct,init通过之前获得的重映射表去设置参数,constrct用于启动控制节点。
constrcut代码如下:
void NodeHandle::construct(const std::string& ns, bool validate_name)
{
if (!ros::isInitialized())
{
ROS_FATAL("You must call ros::init() before creating the first NodeHandle");
ROS_BREAK();
}
collection_ = new NodeHandleBackingCollection;
unresolved_namespace_ = ns;
// if callback_queue_ is nonnull, we are in a non-nullary constructor
if (validate_name)
namespace_ = resolveName(ns, true);
else
{
namespace_ = resolveName(ns, true, no_validate());
// FIXME validate namespace_ now
}
ok_ = true;
boost::mutex::scoped_lock lock(g_nh_refcount_mutex);
if (g_nh_refcount == 0 && !ros::isStarted())
{
g_node_started_by_nh = true;
ros::start();
}
++g_nh_refcount;
}
在对初始化检查设置标志位之后,它运行了init初始化模块中的start。
start与通信相关的核心代码如下:
TopicManager::instance()->start();
ServiceManager::instance()->start();
ConnectionManager::instance()->start();
PollManager::instance()->start();
XMLRPCManager::instance()->start();
首先他先启动了TopicManager,代码如下:
void TopicManager::start()
{
boost::mutex::scoped_lock shutdown_lock(shutting_down_mutex_);
shutting_down_ = false;
poll_manager_ = PollManager::instance();
connection_manager_ = ConnectionManager::instance();
xmlrpc_manager_ = XMLRPCManager::instance();
xmlrpc_manager_->bind("publisherUpdate", boost::bind(&TopicManager::pubUpdateCallback, this, boost::placeholders::_1, boost::placeholders::_2));
xmlrpc_manager_->bind("requestTopic", boost::bind(&TopicManager::requestTopicCallback, this, boost::placeholders::_1, boost::placeholders::_2));
xmlrpc_manager_->bind("getBusStats", boost::bind(&TopicManager::getBusStatsCallback, this, boost::placeholders::_1, boost::placeholders::_2));
xmlrpc_manager_->bind("getBusInfo", boost::bind(&TopicManager::getBusInfoCallback, this, boost::placeholders::_1, boost::placeholders::_2));
xmlrpc_manager_->bind("getSubscriptions", boost::bind(&TopicManager::getSubscriptionsCallback, this, boost::placeholders::_1, boost::placeholders::_2));
xmlrpc_manager_->bind("getPublications", boost::bind(&TopicManager::getPublicationsCallback, this, boost::placeholders::_1, boost::placeholders::_2));
poll_manager_->addPollThreadListener(boost::bind(&TopicManager::processPublishQueues, this));
}
在TopicManager中,
首先xmlrpc_manager调用了bind函数,将回调函数进行绑定,,并在接收到相应的 XML-RPC 请求时调用该回调函数。在warpper代码中,他设置了一个execute执行函数去执行回调,由于其继承了XmlRpcServerMethod,其构建了一个XmlRpcServerMethod的基类。除此之外,还将发布队列与信号槽进行绑定,以方便之后的异步调用。
在TopicManager之后则是ConnectionManager,代码如下:
void ConnectionManager::start()
{
poll_manager_ = PollManager::instance();
poll_conn_ = poll_manager_->addPollThreadListener(boost::bind(&ConnectionManager::removeDroppedConnections,
this));
// Bring up the TCP listener socket
tcpserver_transport_ = boost::make_shared<TransportTCP>(&poll_manager_->getPollSet());
if (!tcpserver_transport_->listen(network::getTCPROSPort(),
MAX_TCPROS_CONN_QUEUE,
boost::bind(&ConnectionManager::tcprosAcceptConnection, this, boost::placeholders::_1)))
{
ROS_FATAL("Listen on port [%d] failed", network::getTCPROSPort());
ROS_BREAK();
}
// Bring up the UDP listener socket
udpserver_transport_ = boost::make_shared<TransportUDP>(&poll_manager_->getPollSet());
if (!udpserver_transport_->createIncoming(0, true))
{
ROS_FATAL("Listen failed");
ROS_BREAK();
}
}
首先他先取一个PollManager的静态实例,然后绑定了一个定期清除无效连接的函数到poll_manager_这个信号槽。他会在调用signal()的时候被启用。之后创建了一个transportTCP调用了listen函数,在listen函数中他会将创建一个socket套接字,并其与Update函数添加到poll_set_的epoll中,每次触发就进行回调。update如果触发则触发socket的读写回调,在update中如果是服务端且为读事件则建立新链接,否则正常读写。值得注意的是这里的建立新链接的回调则是最开始的tcprosAcceptConnection函数。之后又开了一个UDP。
再之后则是PollManager代码如下:
void PollManager::start()
{
shutting_down_ = false;
thread_ = boost::thread(&PollManager::threadFunc, this);
}
他起了一个线程去执行threadFunc代码如下:
void PollManager::threadFunc()
{
disableAllSignalsInThisThread();
while (!shutting_down_)
{
{
boost::recursive_mutex::scoped_lock lock(signal_mutex_);
poll_signal_();
}
if (shutting_down_)
{
return;
}
poll_set_.update(100);
}
}
在pollmanager中他启动poll_manager的回调函数,运行槽函数,并设置了一个100ms的时延