转: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通信实现进行了分析之后,再进行细致地解析。