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通信實現進行了分析之后,再進行細致地解析。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM