上一篇文章我们介绍了ros最小话题系统的制作,本篇将进一步介绍ROS的另一种节点间的交互形式---服务

 

首先,什么是服务?

 

它是节点间的另外一种交互方式(这是句废话。。。),它提供了一种有应答的通信方式。

 

其次,为什么要有它?

 

这个问题比较深奥,我们得先回头去看看“话题”的特点。

 

话题是单向的,按照一定频率发送的一种通信方式。在这种方式下,发出消息的一方是不知道消息有没有人收到的。另一方面,如果只想发送一次比较重要的消息,也是办不到的。

 

因此,我们可以做出这样的类比,话题有点像是一种被封装过了的UDP传输机制,而服务则像是一种被封装过的TCP机制。注意这里我说的是“像”,这是因为它们的底层其实并没有想象的这么简单,以后本人研究过后会专门做这部分的介绍。

 

下面,我们将制作一个最小的服务系统,来看看服务该怎么用起来。

 

这个系统是这样的,我们制作一个服务器,该服务器接收三个整数,然后输出三个整数的和(也是一个整数)。

 

客户端通过请求服务,将要做加法的三个数给服务器,然后服务器返回结果给它。

 

首先强调一下,本篇文章的工作空间和包沿用第二篇文章的。

 

在制作的过程中,我们需要定义一个service文件,该文件告诉ROS我们这个服务器的输入输出是什么东西。具体的,在包“printHelloRosPK”中,新建一个叫做“srv”的文件夹,然后在里面创建一个文件,名字随便取,本文取的“add.srv”。因为我们要输入3个整数,输出它们的和,因此文件的内容如下:

 
int32 A
int32 B
int32 C
---
int32 sum
 

前三行是我们要输入的三个整数,第四行是分割,第五行是输出。

 

另外,由于我们在第二篇文章创建包的时候没有包含服务需要的依赖项,因此需要加进去,方法是打开包下面的“package.xml”,然后将下面一句话添加进去。

 
 <build_depend>message_generation</build_depend>

 

然后打开CMakeLists.txt,找到

 
find_package(catkin REQUIRED COMPONENTS
  roscpp
  std_msgs
)
 

添加 message_generation,变成

 
find_package(catkin REQUIRED COMPONENTS
  roscpp
  std_msgs
  message_generation
)
 

搜索"srv"关键字,看到如下的代码

 
## Generate services in the 'srv' folder
# add_service_files(
#   FILES
#   Service1.srv
#   Service2.srv
# )
 

把“#”去掉,然后修改文件名称,修改后如下:

 
# Generate services in the 'srv' folder
 add_service_files(
   FILES
   add.srv
 )
 

找到

 
# generate_messages(
#   DEPENDENCIES
#   std_msgs
# )
 

把注释去掉

 
 generate_messages(
   DEPENDENCIES
   std_msgs
 )
 

这样准备工作就做完了,我们可以检查ROS是否能够识该服务描述。输入:

 
rossrv show add

 

成功的话输出如下:

   

接着编写服务器和客户端:

 

service.cpp

 
#include "ros/ros.h"  
#include "printHelloRosPK/add.h"  
 
//服务主功能函数,输入为client给的数据,输出处理结果 
bool add(printHelloRosPK::add::Request  &req,  
         printHelloRosPK::add::Response &res)
{  
  res.sum = req.A + req.B+ req.C;  
  ROS_INFO("request: x=%ld, y=%ld, z=%ld", (long int)req.A, (long int)req.B, (long int)req.C);  
  ROS_INFO("sending back response: [%ld]", (long int)res.sum);  
  return true;  
}  
  
int main(int argc, char **argv)  
{  
  ros::init(argc, argv, "service");  
  ros::NodeHandle n;  
  
  ros::ServiceServer service = n.advertiseService("add", add);//建立service,并在ROS内发布出来 
  ROS_INFO("Ready to add three ints.");  
  ros::spin();  
  
  return 0;  
}
 

client.cpp

 
#include "ros/ros.h"  
#include "printHelloRosPK/add.h"//由编译系统自动根据我们先前创建的srv文件生成的对应该srv文件的头文件
#include <cstdlib>  
  
int main(int argc, char **argv)  
{  
  ros::init(argc, argv, "add_three_ints_client");  
  
  ros::NodeHandle n;  
  //为add_three_ints_service创建一个client。ros::ServiceClient对象待会用来调用service。
  ros::ServiceClient client = n.serviceClient<printHelloRosPK::add>("add");
  printHelloRosPK::add srv; //实例化一个由ROS编译系统自动生成的service类,并给其request成员赋值 
  srv.request.A = 1;  
  srv.request.B = 2;  
  srv.request.C = 3;  
 
  //调用service。由于service的调用是模态过程(调用的时候占用进程阻止其他代码的执行),一旦调用完成,将返回调用结果。
  //如果service调用成功,call()函数将返回true,srv.response里面的值将是合法的值。
  //如果调用失败,call()函数将返回false,srv.response里面的值将是非法的
  if (client.call(srv))  
  {  
    ROS_INFO("Sum: %ld", (long int)srv.response.sum);  
    return 0;
  }  
  else  
  {  
    ROS_ERROR("Failed to call service add_three_ints");  
    return 1;  
  }  
}
 

在CMakeLists.txt末尾添加文件位置及依赖关系:

 
add_executable(service /home/weixin/HelloRos/src/printHelloRosPK/src/service.cpp)#定义了这个工程会生成一个文件名为"topicSend"的可执行文件
target_link_libraries(service ${catkin_LIBRARIES})#指定在链接目标文件的时候需要链接的外部库
add_dependencies(service beginner_tutorials_generate_messages_cpp)#为可执行文件添加对生成的消息文件的依赖
 
add_executable(client /home/weixin/HelloRos/src/printHelloRosPK/src/client.cpp)
target_link_libraries(client ${catkin_LIBRARIES})
add_dependencies(client beginner_tutorials_generate_messages_cpp)
 

接着编译下就可以了:

 
catkin_make