• 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: " "export ROS_MASTER_URI=http://localhost:11311 " "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
    	file = %s
    	line=%d
    ", __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通信实现进行了分析之后,再进行细致地解析。

  • 相关阅读:
    Python 实现AEC CBC 加密解密方式
    redis 发布订阅方法与缺陷
    python paramiko 传输下载文件
    Redis 配置文件
    Redis 命令
    window11 | 虚拟机vmWare安装windows11
    十万个为什么 | 文化001-为什么猜灯谜又叫做打灯谜
    ffmpeg | 常用命令使用
    ffmpeg | 常用命令使用
    Adobe系列 | Animate(01)-软件安装
  • 原文地址:https://www.cnblogs.com/Glucklichste/p/11185192.html
Copyright © 2020-2023  润新知