接着上一篇博客ROS源代码阅读(1):找切入点,我们从ros::init()函数出发,开始探索ROS源代码。
ros::init()函数的声明在ROS代码中的./src/ros_comm/roscpp/include/ros/init.h
文件中。
重载为以下三种形式:
//./src/ros_comm/roscpp/include/ros/init.h
void init(int &argc, char **argv, const std::string& name, uint32_t options = 0);
void init(const M_string& remappings, const std::string& name, uint32_t options = 0);
void init(const VP_string& remapping_args, const std::string& name, uint32_t options = 0);
该函数的具体实现在./src/ros_comm/roscpp/src/libros/init.cpp
文件中。我们先选择其中最简单的一个形式的实现进行分析:
//./src/ros_comm/roscpp/src/libros/init.cpp
void init(const M_string& remappings, const std::string& name, uint32_t options)
{
if (!g_atexit_registered)
{
g_atexit_registered = true;
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
#ifndef WIN32
signal(SIGPIPE, SIG_IGN);
#endif
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;//置上初始化标记
}
}
可以看出,ROS程序的初始化函数ros::init()主要调用了以下几个函数完成初始化:
从名字上看,该函数用于对网络的初始化。其实现在./src/ros_comm/roscpp/src/libros/network.cpp
中。实现代码如下:
//./src/ros_comm/roscpp/src/libros/network.cpp
void 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
{
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
if (g_host.empty())
{
g_host = determineHost();
}
}
} // namespace network
} // namespace ros
根据上述代码,在正式解析该函数之前,我们关注到输入参数的数据类型是M_string的一个引用,那么我们先来看看M_string是一个什么数据类型:
该函数的输入参数是const M_string& remappings
。数据类型M_string的定义./src/roscpp_core/cpp_common/include/ros/datatypes.h
中,该文件定义了ROS实现用到的若干种数据类型。代码如下:
//./src/roscpp_core/cpp_common/include/ros/datatypes.h
namespace ros {
typedef std::vector<std::pair<std::string, std::string> > VP_string;
typedef std::vector<std::string> V_string;
typedef std::set<std::string> S_string;
typedef std::map<std::string, std::string> M_string;
typedef std::pair<std::string, std::string> StringPair;
typedef boost::shared_ptr M_stringPtr;
}
可以看到,M_string为一个键/值的数据类型都是std::string的map容器。关于与此处map容器相关的内容见我的博文std:map与迭代器简析。
解决了数据类型M_string的问题,我们细看代码。代码的第一个功能模块如下,该模块主要对变量g_host进行赋值,见代码和注释。
//查找remappings中键为“__hostname”的元素
M_string::const_iterator it = remappings.find("__hostname");
//如果找到了,则将该元素的值赋值给“g_host”
if (it != remappings.end())
{
g_host = it->second;
}
//如果没找到,则:
else
{
//查找键为“__ip”的元素
it = remappings.find("__ip");
//如果找到了,则将该元素的值赋值给“g_host”
if (it != remappings.end())
{
g_host = it->second;
}
}
其中,g_host是一个std::string类型的变量,在network.cpp文件的一开头就已定义。
std::string g_host;
该函数的第二个功能模块和相应的注释如下:
//查找键为"__tcpros_server_port"的元素
it = remappings.find("__tcpros_server_port");
//如果找到了
if (it != remappings.end())
{
try//尝试将对应元素的值(std::string)转化成uint16_t类型
{
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");
}
}
其中又涉及到C++的类型转换器boost::lexical_cast。
boost::lexical_cast为数值之间的转换(conversion)提供了一揽子方案,比如:将一个字符串”712”转换成整数712,代码如下:
string s = "712";
int a = lexical_cast<int>(s);
这种方法的好处是:如果转换发生了意外,lexical_cast会抛出一个bad_lexical_cast异常,可以在程序中进行捕捉。
以上模块就是用boost::lexical_cast进行转换(该函数包含在头文件boost/lexical_cast.hpp
中),然后捕捉bad_lexical_cast异常。从上述代码中看出,该模块的作用是为变量g_tcpros_server_port赋值,该变量的定义在network.cpp的开头,且默认值为0:
uint16_t g_tcpros_server_port = 0;
第三个模块的代码如下:
//当g_host为空的时候,调用函数determineHost()为其赋值。
if (g_host.empty())
{
g_host = determineHost();
}
这段代码所调用的determineHost()也定义在./src/ros_comm/roscpp/src/libros/network.cpp
文件中。对于该函数我们在此暂不深入。这段代码的作用就是:当g_host为空(在模块1中暂未被赋值)时,调用determineHost对其进行赋值。
综上所述,network:init()函数主要完成了g_host和g_tcpros_server_port两个变量的赋值。
master::init()函数定义在./src/ros_comm/roscpp/src/libros/master.cpp
文件中。具体实现代码如下。关于输入参数类型M_string,我已在上文中介绍。关于这段代码的实现细节network::init类似,也是根据输入参数对若干变量赋值,我直接将代码注释附上,不再分模块详述。
void init(const M_string& remappings)
{
//构建迭代器,查找remappings中键为"__master"的节点。
M_string::const_iterator it = remappings.find("__master");
//如果找到了,则将该节点对应的值赋值给g_uri
if (it != remappings.end())
{
g_uri = it->second;
}
//如果g_uri没有被赋值(即刚刚没找到相应节点)
if (g_uri.empty())
{
char *master_uri_env = NULL;
//设法给master_uri_env赋值
#ifdef _MSC_VER
_dupenv_s(&master_uri_env, NULL, "ROS_MASTER_URI");
#else
master_uri_env = getenv("ROS_MASTER_URI");
#endif
if (!master_uri_env)//如果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;
#ifdef _MSC_VER
// http://msdn.microsoft.com/en-us/library/ms175774(v=vs.80).aspx
free(master_uri_env);
#endif
}//if(g_uri.empty())
//对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_BREAK()和network::splitURI(g_uri, g_host, g_port)。
ROS_BREAK()函数的定义在./src/ros_comm/rosconsole/include/ros/assert.h
文件中,具体如下:
#define ROS_BREAK() \
do { \
ROS_FATAL("BREAKPOINT HIT\n\tfile = %s\n\tline=%d\n", __FILE__, __LINE__); \
ROS_ISSUE_BREAK() \
} while (0)
该函数用于中断ROS程序的执行,并显示在哪个文件哪行发生了中断。该函数的具体细节在此先不详述。
该函数定义在./src/ros_comm/roscpp/src/libros/network.cpp
文件中,具体实现如下,我同时将注释附上。
bool splitURI(const std::string& uri, std::string& host, uint32_t& port)
{
// 提取uri中去掉协议的部分,赋值给host
if (uri.substr(0, 7) == std::string("http://"))
host = uri.substr(7);
else if (uri.substr(0, 9) == std::string("rosrpc://"))
host = uri.substr(9);
// 将uri中的端口号(:后面部分)抽取,赋值给port_str
std::string::size_type colon_pos = host.find_first_of(":");
if (colon_pos == std::string::npos)
return false; //如果uri中没有端口号,则该函数返回失败
std::string port_str = host.substr(colon_pos+1);
//删除端口号中第一个“/”
std::string::size_type slash_pos = port_str.find_first_of("/");
if (slash_pos != std::string::npos)
port_str = port_str.erase(slash_pos);
port = atoi(port_str.c_str());//将字符串转化为整形
host = host.erase(colon_pos);//从host变量中删除第一个“:”
return true;
}
即该函数的作用是对uri进行解析,将去掉协议部分赋值给host,将端口号赋值给port。
综上所述,master::init()的作用是在参数remappings中提取出变量g_uri的值,然后再将g_uri解析成g_host和g_port。
在本文中,我们简析了ROS的初始化函数ros::init()。发现该函数主要调用了以下几个函数完成初始化:
然后,我们对其中的前两个函数进行的简单的分析。