ros源码之初始化函数init()调用的几个初始化函数

转:https://blog.csdn.net/wanghuiquan0712/article/details/78052093

在ROS的init(argc, argv, node_name)初始化函数中,依次调用了下面五个函数完成初始化:

network::init(remappings);
master::init(remappings);
// names:: namespace is initialized by this_node
this_node::init(name, remappings, options); // name参数指的就是当前节点
file_log::init(remappings);
param::init(remappings);

1. network::init(remappings)

该函数从输入参数remappings提取信息,完成对 g_host 和 g_tcpros_server_port 两个变量的赋值。

    g_host:(1)首先尝试 remappings[ __hostname ];(2)然后尝试 remappings[ __ip ];(3)最后尝试 determineHost()。
    g_tcpros_server_port:尝试通过 remappings[ __tcpros_server_port]赋值。

void init(const M_string& remappings){
    //查找remappings中是否存在 __hostname 元素
    M_string::const_iterator it = remappings.find("__hostname");
    //存在的话把remapping中相应的键值赋值给全局变量g_host
    if (it != remappings.end()){
        g_host = it->second; //g_host是一个std::string类型的变量,在network.cpp文件的一开头就已定义。 std::string g_host;
      }
    //如果没找到 则查找__ip元素
    else{
        it = remappings.find("__ip");
     //如果找到了,则将该元素的值赋值给“g_host”
if (it != remappings.end()){ g_host = it->second; } } //查找 __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"); } } //如果没找到__hostname元素,g_host为空,那么调用determineHost()函数 if (g_host.empty()){ g_host = determineHost();//determineHost()也定义在./src/ros_comm/roscpp/src/libros/network.cpp文件中 } }

关于数据类型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_string> M_stringPtr;
}

关于类型转换器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;

2. master::init(remappings)

该函数从输入参数remappings提取信息,对g_uri进行赋值,然后再将g_uri解析成g_host和g_port。

  • g_uri:(1)尝试通过remappings[ __master ]进行赋值;(2)尝试通过ROS_MASTER_URI的环境变量值进行赋值。

master::init()函数定义在./src/ros_comm/roscpp/src/libros/master.cpp文件中,具体实现代码如下。

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 } // Split URI into
//对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()函数

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

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。

3. this_node::init(name, remappings, options)

该函数对ThisNode类进行初始化,具体地是对该类的成员变量 name_ 和 namespace_ 进行赋值。另外,在ROS程序中,ThisNode类的实例化用了单例模式,整个ROS程序只有singleton一个ThisNode类的对象。
    name_:(1)用该函数的输入参数name对该变量进行赋值;(2)在remappings中查找remappings[ _name ],如果存在,则用该项的值覆盖name,并将disable_anon置成true。
    namespace_:(1)尝试用ROS_NAMESPACE的环境变量值对其进行赋值;(2)在remappings中查找remappings[ _ns ],如果存在,则用该项的值覆盖namespace。

除此之外,该函数还
    调用了names::init(remappings),将remappings映射为g_remappings和g_unresolved_remappings两个变量
    调用了ros::console::setFixedFilterToken,增加了一项g_extra_fixed_tokens[node] = name_

this_node::init()函数的源代码在./src/ros_comm/roscpp/src/libros/this_node.cpp
中,具体实现如下:

//./src/ros_comm/roscpp/src/libros/this_node.cpp

void init(const std::string& name, const M_string& remappings, uint32_t options)
{
  ThisNode::instance().init(name, remappings, options);
}

其中,ThisNode是文件./src/ros_comm/roscpp/src/libros/this_node.cpp中定义的一个类,该类的具体定义如下:

//./src/ros_comm/roscpp/src/libros/this_node.cpp

class ThisNode
{
  std::string name_;
  std::string namespace_;

  ThisNode() : name_("empty") {}//构造对象时将name_置为空

public:
  static ThisNode& instance()
  {
    static ThisNode singleton;//整个ROS程序只有singleton一个拷贝,详见编程中的“单例模式”。
    return singleton;
  }

  const std::string& getName() const
  {
    return name_;
  }

  const std::string& getNamespace() const
  {
    return namespace_;
  }

  void init(const std::string& name, const M_string& remappings, uint32_t options);
};

this_node::init()函数实际上直接调用的是ThisNode类中的void init(const std::string& name, const M_string& remappings, uint32_t options);函数。该函数的定义如下:

//./src/ros_comm/roscpp/src/libros/this_node.cpp

void ThisNode::init(const std::string& name, const M_string& remappings, uint32_t options)
{
  char *ns_env = NULL;
#ifdef _MSC_VER
  _dupenv_s(&ns_env, NULL, "ROS_NAMESPACE");
#else
  ns_env = getenv("ROS_NAMESPACE");//获取ROS_NAMESPACE的环境变量名
#endif

  if (ns_env)//如果环境变量ns_env已被赋值
  {
    namespace_ = ns_env;//将ROS_NAMESPACE的环境变量名赋值给namespace_
    //namespace_是类ThisNode的成员变量
#ifdef _MSC_VER
    free(ns_env);
#endif
  }

  //检测通过参数传入的节点名不能为空
  if (name.empty()) {
    throw InvalidNameException("The node name must not be empty");
  }

  name_ = name; //将传入的节点名赋值给变量name_
  //name_是类ThisNode的成员变量

  bool disable_anon = false;
  //在输入参数remappings查找键为"__name"的项
  M_string::const_iterator it = remappings.find("__name");
  if (it != remappings.end())//如果找到了
  {
    name_ = it->second;//将对应项的值赋值给name_
    disable_anon = true;
  }
  //在输入参数remappings查找键为"__ns"的项
  it = remappings.find("__ns");//如果找到了
  if (it != remappings.end())
  {
    namespace_ = it->second;//将对应项的值赋值给变量namespace_
  }

  if (namespace_.empty())//如果namespace_为空
  {
    namespace_ = "/";
  }

  namespace_ = (namespace_ == "/")
    ? std::string("/") 
    : ("/" + namespace_)
    ;


  std::string error;
  //对照命名规则检查namespace_,看看是否合法。
  if (!names::validate(namespace_, error))
  {
    std::stringstream ss;
    ss << "Namespace [" << namespace_ << "] is invalid: " << error;
    throw InvalidNameException(ss.str());
  }

  // names must be initialized here, because it requires the namespace to already be known so that it can properly resolve names.
  // It must be done before we resolve g_name, because otherwise the name will not get remapped.
  names::init(remappings);//将remappings映射为g_remappings和g_unresolved_remappings两个变量
  //检查name_的合法性
  if (name_.find("/") != std::string::npos)
  {
    throw InvalidNodeNameException(name_, "node names cannot contain /");
  }
  if (name_.find("~") != std::string::npos)
  {
    throw InvalidNodeNameException(name_, "node names cannot contain ~");
  }

  name_ = names::resolve(namespace_, name_);//进行格式化整理

  if (options & init_options::AnonymousName && !disable_anon)
  {
    char buf[200];
    snprintf(buf, sizeof(buf), "_%llu", (unsigned long long)WallTime::now().toNSec());
    name_ += buf;
  }

  ros::console::setFixedFilterToken("node", name_);
}

从代码可以看出,该函数完成了以下几个功能:

    获取ROS_NAMESPACE的环境变量名;
    给变量name_赋值,并进行一些格式化处理。name_是类ThisNode的成员变量;
    给变量namespace_赋值,并进行一些格式化处理。namespace_是类ThisNode的成员变量;

根据类ThisNode的定义,该类的成员变量就只有name_和namespace_两个变量。因此,该函数可以看做是根据输入参数,对ThisNode的对象进行初始化。

而根据ThisNode::instance()函数,该类在程序中只有唯一的一个对象。即调用this_node::init()的时候完成对该类唯一对象的初始化。

另外,上述函数调用了

    names::validate(namespace_, error)(上述代码第57行)
    names::init(remappings)(上述代码第66行)
    ros::console::setFixedFilterToken(上述代码第86行)

三个函数。为了更好理解代码,我们下面对这三个函数做一简述。

names::validate(namespace_, error)

上述代码调用了函数names::validate(namespace_, error),该函数定义在./src/ros_comm/roscpp/src/libros/names.cpp
中。具体实现如下。

bool validate(const std::string& name, std::string& error)
{
  if (name.empty())
  {
    return true; //如果name为空,则返回true
  }

  //检查首字符,首字符只能是~ / 或 alpha
  char c = name[0];
  if (!isalpha(c) && c != '/' && c != '~')
  {
    std::stringstream ss;
    ss << "Character [" << c << "] is not valid as the first character in Graph Resource Name [" << name << "].  Valid characters are a-z, A-Z, / and in some cases ~.";
    error = ss.str();
    return false;
  }

  //逐个检查name中的每个字符是否为合法字符
  for (size_t i = 1; i < name.size(); ++i)
  {
    c = name[i];
    if (!isValidCharInName(c))
    {
      std::stringstream ss;
      ss << "Character [" << c << "] at element [" << i << "] is not valid in Graph Resource Name [" << name <<"].  Valid characters are a-z, A-Z, 0-9, / and _.";
      error = ss.str();
      return false;
    }
  }
  return true;
}

names::init(remappings)

该函数定义在./src/ros_comm/roscpp/src/libros/names.cpp文件中,作用是将remappings映射为g_remappings和g_unresolved_remappings两个变量,其中g_remappings是按照一定规则整理过的remappings,而g_unresolved_remappings是初始传入的remappings参数

//./src/ros_comm/roscpp/src/libros/names.cpp

void init(const M_string& remappings)
{
  //该函数的作用是将remappings映射为g_remappings和g_unresolved_remappings两个变量
  M_string::const_iterator it = remappings.begin();
  M_string::const_iterator end = remappings.end();
  for (; it != end; ++it) //遍历M_string中的每个元素
  {
    const std::string& left = it->first; //left为键
    const std::string& right = it->second; //right为值
    //键不为空 且 键的第一个字符不为“_” 且 键不等于ThisNode对象的name_成员变量
    if (!left.empty() && left[0] != '_' && left != this_node::getName())
    {
      std::string resolved_left = resolve(left, false);
      std::string resolved_right = resolve(right, false);
      g_remappings[resolved_left] = resolved_right;
      g_unresolved_remappings[left] = right;
    }
  }
}

其中调用了resolve()函数,该函数也定义在./src/ros_comm/roscpp/src/libros/names.cpp中,执行一些简答的格式化操作,在此不进行详述。

ros::console::setFixedFilterToken

该文件的实现在./src/ros_comm/rosconsole/src/rosconsole/rosconsole.cpp文件中,具体代码如下:

void setFixedFilterToken(const std::string& key, const std::string& val)
{
  g_extra_fixed_tokens[key] = val;
}

从代码可以看出,该函数主要是对变量g_extra_fixed_tokens进行赋值。

总结

当初始化函数this_node::init()被ros::init()调用时,实际上调用了ROS程序中ThisNode类唯一的实例中的init(name, remappings, options)函数,作用是对该唯一的实例进行初始化。
该函数的具体作用如下:
1. 获取ROS_NAMESPACE的环境变量名;
2. 给变量name_赋值,并进行一些格式化处理。name_是类ThisNode的成员变量;
3. 给namespace_赋值,并进行一些格式化处理。namespace_是类ThisNode的成员变量;
4. 将remappings映射为g_remappings和g_unresolved_remappings两个变量;

4. file_log::init(remappings)

该函数主要是根据环境变量,生成日志文件的路径和文件名,并赋值给g_log_directory。

从file_log::init()的名字我们可以猜测,该函数用于对日志文件的初始化。 file_log::init()函数定义在./src/ros_comm/roscpp/src/libros/file_log.cpp 中,具体实现代码和注释如下

void init(const M_string& remappings)
{
  std::string log_file_name;
  M_string::const_iterator it = remappings.find("__log");
  //在remappings中找到键为"__log"的项
  if (it != remappings.end())
  {
    log_file_name = it->second; //如果找到了,将对应的值赋值给log_file_name
  }

  {
    // Log filename can be specified on the command line through __log
    // If it's been set, don't create our own name
    if (log_file_name.empty())//如果log_file_name是个空串
    {
      // Setup the logfile appender
      // Can't do this in rosconsole because the node name is not known
      pid_t pid = getpid();//获取当前进程号
      std::string ros_log_env;
      if ( get_environment_variable(ros_log_env, "ROS_LOG_DIR"))//获取"ROS_LOG_DIR"的环境变量值
      {
        log_file_name = ros_log_env + std::string("/");//在获取的环境变量后面增加“/”
      }
      else//如果不存在"ROS_LOG_DIR"这个环境变量
      {
        if ( get_environment_variable(ros_log_env, "ROS_HOME"))//获取"ROS_HOME"的环境变量值
        {
          log_file_name = ros_log_env + std::string("/log/");//在获取的环境变量后面增加“/log/”
        }
        else//如果不存在环境变量"ROS_HOME"
        {
          if( get_environment_variable(ros_log_env, "HOME") )//获取"ROS_HOME"的环境变量值
          {
            std::string dotros = ros_log_env + std::string("/.ros/");//在获取的环境变量后面增加“/.ros/”
            fs::create_directory(dotros);//创建相应文件夹
            log_file_name = dotros + "log/";
            fs::create_directory(log_file_name);//创建相应文件夹
          }
        }
      }//end of "else//如果不存在"ROS_LOG_DIR"这个环境变量

      //处理节点的名字,并接到log_file_name后面
      for (size_t i = 1; i < this_node::getName().length(); i++)
      {
        if (!isalnum(this_node::getName()[i]))
        {
          log_file_name += '_';
        }
        else
        {
          log_file_name += this_node::getName()[i];
        }
      }

      char pid_str[100];
      snprintf(pid_str, sizeof(pid_str), "%d", pid);//将pid以整形变量的形式写入pid_str
      log_file_name += std::string("_") + std::string(pid_str) + std::string(".log");
    }

    //返回log_file_name对应文件的完整路径
    log_file_name = fs::system_complete(log_file_name).string();
    g_log_directory = fs::path(log_file_name).parent_path().string();
  }
}

该函数大部分代码用于生成一个变量log_file_name,该变量是ROS程序日志文件的路径和值。最终,将该变量的值赋值给全局变量g_log_directory。
该变量在ros::file_log名字空间下,在file_log.cpp文件的开头处声明

get_environment_variable()

在上述代码中,使用到了get_environment_variable()函数,该函数定义在文件./src/roscpp_core/cpp_common/include/ros/platform.h中。函数的功能是获取相应函数变量的值。具体实现如下:

inline bool get_environment_variable(std::string &str, const char* environment_variable) {
    char* env_var_cstr = NULL;
    #ifdef _MSC_VER
      _dupenv_s(&env_var_cstr, NULL,environment_variable);
    #else
      env_var_cstr = getenv(environment_variable);
    #endif
    if ( env_var_cstr ) {
        str = std::string(env_var_cstr);
        #ifdef _MSC_VER
          free(env_var_cstr);
        #endif
        return true;
    } else {
        str = std::string("");
        return false;
    }
}

在file_log::init()中,主要调用get_environment_variable()函数获取ROS_HOME、ROS_LOG_DIR的环境变量的值。

5. param::init(remappings)函数

该函数定义在./src/ros_comm/roscpp/src/libros/param.cpp文件中。具体代码如下:

//./src/ros_comm/roscpp/src/libros/param.cpp
void init(const M_string& remappings)
{
  M_string::const_iterator it = remappings.begin();//remappings变量的头元素 M_string::const_iterator end = remappings.end();//remappings变量的末元素 for (; it != end; ++it)//依次遍历remappings变量的所有元素  { const std::string& name = it->first;//提取键 const std::string& param = it->second;//提取值 if (name.size() < 2)//跳过键的长度小于2的元素  { continue; } if (name[0] == '_' && name[1] != '_')//如果键以“__”开头  { //为name赋予一个本地名称,用符号"~"代替“__” std::string local_name = "~" + name.substr(1); bool success = false; try { int32_t i = boost::lexical_cast<int32_t>(param);//尝试将param转化成整型 //将local_name规整化, ros::param::set(names::resolve(local_name), i); success = true;//将成功标志置上  } catch (boost::bad_lexical_cast&) { } if (success)//如果成功标志已被置上,则越过后续过程  { continue; //此时,即param成功被转化为整型  } try { double d = boost::lexical_cast<double>(param);//尝试将param转化成浮点型 //将local_name规整化 ros::param::set(names::resolve(local_name), d); success = true;//将成功标志置上  } catch (boost::bad_lexical_cast&) { } if (success)//如果成功标志已被置上,则越过后续过程  { continue; //此时,即param成功被转化为浮点型  } if (param == "true" || param == "True" || param == "TRUE") { ros::param::set(names::resolve(local_name), true); } else if (param == "false" || param == "False" || param == "FALSE") { ros::param::set(names::resolve(local_name), false); } else { ros::param::set(names::resolve(local_name), param); } } } XMLRPCManager::instance()->bind("paramUpdate", paramUpdateCallback); }

在上述文件中,多次调用了ros::param::set()函数,该函数在param::init()中发挥了重要的作用。

ros::param::set()

ros::param::set()函数的定义也在文件./src/ros_comm/roscpp/src/libros/param.cpp中。有一系列的重载函数:
- void set(const std::string& key, const XmlRpc::XmlRpcValue& v)
- void set(const std::string& key, const std::string& s)
- void set(const std::string& key, const char* s)
- void set(const std::string& key, double d)
- void set(const std::string& key, int i)
- void set(const std::string& key, bool b)
除了第一个函数外,后面的函数都是将第二个参数转化成相应了XmlRpcValue类型(该类型的介绍见后续“插曲2”),然后在调用第一个函数。其中,在param::init()中调用的是其中void set(const std::string& key, bool b)这种形式,其实现代码如下:

void set(const std::string& key, bool b) { XmlRpc::XmlRpcValue v(b); ros::param::set(key, v); }

下面我们对第一个函数void set(const std::string& key, const XmlRpc::XmlRpcValue& v)进行分析。

void set(const std::string& key, const XmlRpc::XmlRpcValue& v) { //对key做一些规整化,赋值给mapped_key std::string mapped_key = ros::names::resolve(key); XmlRpc::XmlRpcValue params, result, payload; params[0] = this_node::getName(); params[1] = mapped_key; params[2] = v; { // Lock around the execute to the master in case we get a parameter update on this value between // executing on the master and setting the parameter in the g_params list. boost::mutex::scoped_lock lock(g_params_mutex); if (master::execute("setParam", params, result, payload, true)) { // Update our cached params list now so that if get() is called immediately after param::set() // we already have the cached state and our value will be correct if (g_subscribed_params.find(mapped_key) != g_subscribed_params.end()) { g_params[mapped_key] = v; } invalidateParentParams(mapped_key); } } }

在分析ROS源代码的过程中,发现上述代码段内涵比较丰富。
首先,出现了一个新的变量类型:XmlRpc::XmlRpcValue。其次,调用了一个函数master::execute(“setParam”, params, result, payload, true),该函数用于在master(节点管理器)上执行XML-RPC通信机制。

从这里开始,我们将进入ROS节点通讯的核心调用机制:XML-RPC。XML-RPC协议是XML Remote Procedure call的简称,是一种简单、稳定和易于理解的规范化远程过程调用的分布式网络协议。它允许软件间通过发送和接受XML格式的消息进行远程调用。ROS系统中采用XML-RPC协议进行各节点之间的通信。

下面我们将在“插曲2”中简单描述一下XmlRpc::XmlRpcValue类,然后在“插曲3”中描述ros::master::execute()函数。由于涉及到ROS的XML-RPC协议实现的内容非常丰富,后续我将会专门对其进行解析,在本文中暂不涉及过于深入的内容。

XmlRpc::XmlRpcValue类

XmlRpc::XmlRpcValue类定义在文件./src/ros_comm/xmlrpcpp/include/xmlrpcpp/XmlRpcValue.h中。该类定义了ROS程序完成远程过程调用(Remote Procedure Call,RPC)所需要的一些变量。

该类定义了一系列变量。该类的实现在文件./src/ros_comm/xmlrpcpp/src/XmlRpcValue.cpp中。

XmlRpcValue类对若干基本的C++的数据类型进行了封装,涉及的主要数据类型包括如下几个:

bool          asBool;
int           asInt;
double asDouble; struct tm* asTime; std::string* asString; BinaryData* asBinary; //typedef std::vector<char> BinaryData; ValueArray* asArray; //typedef std::vector<XmlRpcValue> ValueArray; ValueStruct* asStruct; //typedef std::map<std::string, XmlRpcValue> ValueStruct;

在定义了这些数据类型后,重载了一些操作符,便于ROS程序的使用。

ros::master::execute()函数

ros::master::execute()函数定义在文件./src/ros_comm/roscpp/src/libros/master.cpp中,作用是在master(节点管理器)上执行XML-RPC(使用http协议做为传输协议的rpc机制,使用xml文本的方式传输命令和数据)。
函数定义如下:

bool ros::master::execute   (   const std::string &     method,
    const XmlRpc::XmlRpcValue & request, XmlRpc::XmlRpcValue & response, XmlRpc::XmlRpcValue & payload, bool wait_for_master )

    ethod:要调用的 RPC 方法
    request:The arguments to the RPC call //传递给RPC的参数
    response:[out] The resonse that was received. //接收到的回应
    payload: [out] The payload that was received. //
    wait_for_master:Whether or not this call should loop until it can contact the master //是否一直循环等待与master建立连接

该函数的实现代码如下,定义在文件./src/ros_comm/roscpp/src/libros/master.cpp中。在此只是先展示出来,先暂不对其进行深入的分析。从代码中可以看出,该函数调用了XMLRPCManager、XmlRpc::XmlRpcClient两个类的内容,这部分内容涉及到ROS中XML-RPC通信的具体实现,我们将在后续的内容中详述。

bool execute(const std::string& method, const XmlRpc::XmlRpcValue& request, XmlRpc::XmlRpcValue& response, XmlRpc::XmlRpcValue& payload, bool wait_for_master) { ros::WallTime start_time = ros::WallTime::now(); std::string master_host = getHost(); //获取g_host的值 uint32_t master_port = getPort(); //获取g_port的值 //根据master_host, master_port的值获取XMLRPC通信的客户端 XmlRpc::XmlRpcClient *c = XMLRPCManager::instance()->getXMLRPCClient(master_host, master_port, "/"); bool printed = false; bool slept = false; bool ok = true; bool b = false; do { { #if defined(__APPLE__) boost::mutex::scoped_lock lock(g_xmlrpc_call_mutex); #endif //c是根据master_host, master_port的值获取XMLRPC通信的客户端指针(XmlRpc::XmlRpcClient *c) b = c->execute(method.c_str(), request, response); } ok = !ros::isShuttingDown() && !XMLRPCManager::instance()->isShuttingDown(); if (!b && ok) { if (!printed && wait_for_master) { ROS_ERROR("[%s] Failed to contact master at [%s:%d]. %s", method.c_str(), master_host.c_str(), master_port, wait_for_master ? "Retrying..." : ""); printed = true; } if (!wait_for_master) { XMLRPCManager::instance()->releaseXMLRPCClient(c); return false; } if (!g_retry_timeout.isZero() && (ros::WallTime::now() - start_time) >= g_retry_timeout) { ROS_ERROR("[%s] Timed out trying to connect to the master after [%f] seconds", method.c_str(), g_retry_timeout.toSec()); XMLRPCManager::instance()->releaseXMLRPCClient(c); return false; } ros::WallDuration(0.05).sleep(); slept = true; } else { if (!XMLRPCManager::instance()->validateXmlrpcResponse(method, response, payload)) { XMLRPCManager::instance()->releaseXMLRPCClient(c); return false; } break; } ok = !ros::isShuttingDown() && !XMLRPCManager::instance()->isShuttingDown(); } while(ok); if (ok && slept) { ROS_INFO("Connected to master at [%s:%d]", master_host.c_str(), master_port); } XMLRPCManager::instance()->releaseXMLRPCClient(c); return b; }

 总结
根据分析,ros::init()调用的五个函数,即:

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

中,最后一个函数涉及到执行XML-RPC相关操作,而XML-RPC协议是ROS节点通讯的核心调用机制,后续我们会着重进行分析。前面4个函数的功能都主要是对一些全局变量进行赋值。被赋值的变量有(与前面不同,在此我们加上的命名空间):

ros::network::g_host
ros::network::g_tcpros_server_port
ros::master::g_uri
ros::this_node::ThisNode.name_
ros::this_node::ThisNode.namespace_
ros::console::g_extra_fixed_tokens[node]
ros::file_log::g_log_directory

具体这些变量被赋值后,发挥了怎样的作用,我们在后续对ROS的XML-RPC通信实现进行了分析之后,再进行细致地解析。

猜你喜欢

转载自www.cnblogs.com/kerngeeksund/p/11185192.html