上一篇文章我们介绍了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