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

从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.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()主要调用了以下几个函数完成初始化:

  • 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.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是一个什么数据类型:


插曲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.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。


插曲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()完成的工作做一个完整的总结。

你可能感兴趣的:(ROS解析)