在ROS程序中,初始化ROS节点是函数ros::init()
,启动节点是实例化类ros::NodeHandle
,这两步是ROS程序必不可少的,先后顺序不能变,否则会报错:
本篇分析ros::init,另一篇分析NodeHandle。
原型
使用任何roscpp函数前,必须调用ros::init()
,一般有两种形式:1
2
3ros::init(argc, argv, "my_node_name");
ros::init(argc, argv, "my_node_name", ros::init_options::AnonymousName);
ros::init(argc, argv, "my_node_name", ros::init_options::NoSigintHandler);
argc和argv用于处理remapping参数,使用这种形式后,在命令行中使用参数就无效了。如果还想在命令行中处理,需要在ros::init
之后调用ros::removeROSArgs()
一般只能有一个同名节点在运行,如果再运行一个,前一个节点会自动关闭。使用第二种重载就可以同时运行多个同名节点,比如rviz
和rostopic
就是如此,ROS会在节点名后面加上UTC时间以示区别。
运行同一个节点还有更好的方法,比如已经运行了一个名为Pub的节点:rosrun pub Pub
,我们指定参数__name
可以运行同一个可执行文件,但是节点名不同:1
rosrun pub Pub __name:=newPub
默认发SIGINT信号会终结节点,也就是Ctral+C会退出节点,但也可以自定义信号处理函数,前提是使用第三种重载的ros::init
。
ros::init 源码
ros::init
有三种重载形式,以最简单的一种为例1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34void init(const M_string& remappings, const std::string& name, uint32_t options)
{
if (!g_atexit_registered)
{
g_atexit_registered = true; // 状态
// 注册终止函数,在调用exit()或终止main函数时关闭shutdown()函数
atexit(atexitCallback);
}
if (!g_global_queue)
{
g_global_queue.reset(new CallbackQueue);
}
//上面做了一些预处理,主要部分在下面:
if (!g_initialized) // 若未初始化
{
g_init_options = options;
g_ok = true;
ROSCONSOLE_AUTOINIT; //在console.h中的一段宏定义:Initializes the rosconsole library.
// Disable SIGPIPE
signal(SIGPIPE, SIG_IGN);
// 重点是5个init
network::init(remappings);//初始化网络,实现在network.cpp中
master::init(remappings); //初始化master
// names:: namespace is initialized by this_node
this_node::init(name, remappings, options); //初始化当前节点
file_log::init(remappings);
param::init(remappings);
g_initialized = true;//置上初始化标记
}
}
分析一下代码,首先是注册终止函数,也就是调用exit函数时执行的回调函数:1
2
3
4
5
6
7
8
9void atexitCallback()
{
if (ok() && !isShuttingDown())
{
ROSCPP_LOG_DEBUG("shutting down due to exit() or end of main() without cleanup of all NodeHandles");
g_started = false; // don't shutdown singletons, because they are already destroyed
shutdown(); // 关闭节点
}
}
g_global_queue
的定义是CallbackQueuePtr g_global_queue;
,再查发现类型其实是一个boost共享指针:typedef boost::shared_ptr<CallbackQueue> CallbackQueuePtr;
,这里是共享指针丢弃原来的指向对象,销毁掉,重新指向新new的对象
再往下是一个宏ROSCONSOLE_AUTOINIT,内容不必太详细看,功能就是初始化rosconsole库。
然后执行signal(SIGPIPE, SIG_IGN);
,功能是收到RST包后,不要关闭节点。
最后就是很重要的5个init,我们一一分析
network:init()
这个函数主要就是给g_host和g_tcpros_server_port赋值1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36void init(const M_string& remappings) //该函数在init.cpp中被调用
{
//模块1:
M_string::const_iterator it = remappings.find("__hostname");
if (it != remappings.end())
{
g_host = it->second;
}
else
{
it = remappings.find("__ip");
if (it != remappings.end())
{
g_host = it->second;
}
}
//模块2
it = remappings.find("__tcpros_server_port");
if (it != remappings.end())
{
try
{ // 尝试将对应元素的值(std::string)转化成uint16_t类型,boost 转化
// g_tcpros_server_port初始值为0
g_tcpros_server_port = boost::lexical_cast<uint16_t>(it->second);
}
catch (boost::bad_lexical_cast & ) // 如果上述类型转化发生异常,捕捉
{
throw ros::InvalidPortException("__tcpros_server_port [" + it->second + "] was not specified as a number within the 0-65535 range");
}
}
//模块3,如果上面未能赋值,调用函数determineHost赋值
if (g_host.empty())
{
g_host = determineHost();
}
}M_string
其实是std::map<std::string, std::string>
,也就是标准map容器。
boost::lexical_cast
用于string 和数值之间的转换比如:将一个字符串”712”转换成整数712,代码如下:1
2
3
4
5
6string s = "712";
int a = lexical_cast<int>(s);
s="1523408.78";
float num = boost::lexical_cast<float>(s);
cout<<num<<endl;
浮点数比较大时,会四舍五入用科学计数法表示.
这种方法的好处是:如果转换发生了意外,lexical_cast会抛出一个bad_lexical_cast异常,可以在程序中进行捕捉。
最后,如果之前未能给g_host成功赋值,会调用determineHost
函数,它依次获取环境变量ROS_HOSTNAME
和ROS_IP
,如果没有设置这两个环境变量,就调用gethostname
函数获取主机名,但不能取localhost
,如果这样还不能获取到,只好返回127.0.0.1
master::init()
这个是处理主机节点1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43void init(const M_string& remappings)
{
//构建迭代器,查找remappings中键为"__master"的节点。
M_string::const_iterator it = remappings.find("__master");
if (it != remappings.end())
{
g_uri = it->second;
}
//如果g_uri没有被赋值(即刚刚没找到相应节点)
if (g_uri.empty())
{
char *master_uri_env = NULL;
_dupenv_s(&master_uri_env, NULL, "ROS_MASTER_URI");
master_uri_env = getenv("ROS_MASTER_URI");
if (!master_uri_env)
{
ROS_FATAL( "ROS_MASTER_URI is not defined in the environment. Either " \
"type the following or (preferrably) add this to your " \
"~/.bashrc file in order set up your " \
"local machine as a ROS master:\n\n" \
"export ROS_MASTER_URI=http://localhost:11311\n\n" \
"then, type 'roscore' in another shell to actually launch " \
"the master program.");
ROS_BREAK();
}
g_uri = master_uri_env;
free(master_uri_env);
}
//对g_uri进行解析,把g_uri中去掉协议部分赋值给g_host,并将端口赋值给g_port。
if (!network::splitURI(g_uri, g_host, g_port))
{
ROS_FATAL( "Couldn't parse the master URI [%s] into a host:port pair.", g_uri.c_str());
ROS_BREAK();
}
}
如果没有获得环境变量ROS_MASTER_URI
,那么节点报错退出,可见这个环境变量必不可少,最后把值赋给g_uri
。最后的splitURI
函数显然是一个字符串的处理函数,过程比较复杂,我们知道功能即可