在初始化模块完成之后我们就可以开始NodeHandle模块代码的学习了。
ros::NodeHandle n;
NodeHandle模块是用于管理节点操作和通信的对象。NodeHandle允许节点创建、发布和订阅消息,创建服务和客户端,以及管理节点的参数。
例如发布者的创建就由NodeHandle进行。
ros::Publisher chatter_pub = n.advertise<std_msgs::String>("chatter", 1000);
需要说明的是,使用 nh.advertise
和 nh.subscribe
时,节点会通过 XML-RPC 将这些信息发送给主节点,主节点维护一个全局的发布与订阅关系表。这里只是简单提一下,并不会继续深入,本章主要还是观看NodeHandle模块创建时的源码。
当NodeHandle没有参数设置直接创建时,他会走头文件的这个重载构造函数。
NodeHandle(const std::string& ns = std::string(), const M_string& remappings = M_string());
这里的ns 即命名空间, remappings就是之前的重映射表。如果没有参数输入,就将给两个空值给他们。
需要说明的是,命名空间并不是之前创建的节点名。命名空间允许开发者将话题、服务和参数组织成逻辑分组,以避免命名冲突和管理复杂性。例如:
ros::NodeHandle nh("~");
ros::Publisher cmd_vel_pub = nh.advertise<geometry_msgs::Twist>("cmd_vel", 10);
ros::Subscriber laser_sub = nh.subscribe("scan", 10, laserCallback);
ros::ServiceServer move_base_server = nh.advertiseService("move_base", moveBaseCallback);
nh.setParam("max_speed", 1.0);
这里的 "~"
表示当前节点的私有命名空间,它会在节点名后面追加一个斜杠("/"
)。在这种情况下,如果节点名是 "my_node"
,那么私有命名空间就是 "/my_node/~"
在这个例子中,所有的话题(如 "/my_node/~cmd_vel"
、"/my_node/~scan"
)、服务(如 "/my_node/~move_base"
)、参数(如 "/my_node/~max_speed"
)都是在 "/my_node/"
命名空间下定义的。这样做既避免了全局名称冲突,又使得节点资源的定义和使用更加清晰和有条理。如果这里不设置那么使用的是初始化的命名空间/my_node/cmd_vel
。
在源代码中他会走这个重载构造函数:
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);
}
那么这里getNamespace 就是获取之前初始化的命名空间了,并且初始化回调队列和集合。下面两行就是之前说的规则了,如果输出~就设置为私有命名空间,之后他进行了非常重要的一个步骤。
1.construct
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;
}
首先他先检查了有没有初始化,没初始化直接退出。下面是一些,验证解析命名空间的操作,我们就不具体看了。当一切准备完成,ok_标志设置为true.这时候他会上一个区域锁,保证多线程安全。如果全局引用计数 g_nh_refcount
为 0 且 ROS 尚未启动,调用 ros::start()
启动 ROS。然后增加全局引用计数 g_nh_refcount
。
start()是init模块的函数,将设置一些变量,然后把所有管理功能的节点全部启动,由于start函数拥有过多代码,我们仅放出其关键部分。
PollManager::instance()->addPollThreadListener(checkForShutdown);
XMLRPCManager::instance()->bind("shutdown", shutdownCallback);
initInternalTimerManager();
TopicManager::instance()->start();
ServiceManager::instance()->start();
ConnectionManager::instance()->start();
PollManager::instance()->start();
XMLRPCManager::instance()->start();
这些管理节点非常重要,我们将在后面的章节详细讲解他们的作用。
最后进行重映射设置参数,但是我们没有设置,所以也不会设置参数。
目前:我们看过代码的流程如下:
总结:该代码的主要作用是启动TopicManager,ConnectionManager和PollManager等模块。