ROS源代码阅读(2):ROS程序的初始化——从ros:init()出发

来源:互联网 发布:淘宝店家怎么代销 编辑:程序博客网 时间:2024/05/18 01:35

从ros::init()出发

接着上一篇博客ROS源代码阅读(1):找切入点,我们从ros::init()函数出发,开始探索ROS源代码。
ros::init()函数的声明在ROS代码中的./src/ros_comm/roscpp/include/ros/init.h文件中。
重载为以下三种形式:

//./src/ros_comm/roscpp/include/ros/init.hvoid 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.cppvoid 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()主要调用了以下几个函数完成初始化:

  • network::init(remappings);
  • master::init(remappings);
  • this_node::init(name, remappings, options);
  • file_log::init(remappings);
  • param::init(remappings);
    其中,前两个函数(network::init和master::init)比较简单,在此文档中先介绍这两个函数。

1. network::init()

从名字上看,该函数用于对网络的初始化。其实现在./src/ros_comm/roscpp/src/libros/network.cpp中。实现代码如下:

//./src/ros_comm/roscpp/src/libros/network.cppvoid 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是一个什么数据类型:


插曲1:关于数据类型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.hnamespace 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_string> 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。


插曲2:关于类型转换器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两个变量的赋值。

2. master::init()

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)。


插曲2.1 ROS_BREAK()函数

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程序的执行,并显示在哪个文件哪行发生了中断。该函数的具体细节在此先不详述。


插曲2.2 network::splitURI()

该函数定义在./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()。发现该函数主要调用了以下几个函数完成初始化:

  • network::init(remappings);
  • master::init(remappings);
  • this_node::init(name, remappings, options);
  • file_log::init(remappings);
  • param::init(remappings);

然后,我们对其中的前两个函数进行的简单的分析。

后续将从以下几个角度深入:

  1. 继续分析剩下的三个初始化函数:this_node::init,file_log::init和param::init
  2. 在分析函数 network::init()时我们留了个尾巴,即determineHost()函数。我们后续将对determineHost()进行深入分析
  3. 完成上述分析后,从整体的角度,对ros:init()完成的工作做一个完整的总结。