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