上一篇博客我们介绍了ROS的通信方式中的topic(话题)通信,我们知道topic是ROS中的一种单向的异步通信方式。然而有些时候单向的通信满足不了通信要求,比如当一些节点只是临时而非周期性的需要某些数据,如果用topic通信方式时就会消耗大量不必要的系统资源,造成系统的低效率高功耗。
这种情况下,就需要有另外一种请求-查询式的通信模型。这篇博客我们来介绍ROS通信中的另一种通信方式——service(服务)。
service方式在通信模型上与topic做了区别。Service通信是双向的,它不仅可以发送消息,同时还会有反馈。所以service包括两部分,一部分是请求方(Clinet),另一部分是应答方/服务提供方(Server)。这时请求方(Client)就会发送一个request,要等待server处理,反馈回一个reply,这样通过类似“请求-应答”的机制完成整个服务通信。
这种通信方式的示意图如下:
Node B是server(应答方),提供了一个服务的接口,叫做/Service,我们一般都会用string类型来指定service的名称,类似于topic。Node A向Node B发起了请求,经过处理后得到了反馈。
Service是同步通信方式,所谓同步就是说,此时Node A发布请求后会在原地等待reply,直到Node B处理完了请求并且完成了reply,Node A才会继续执行。Node A等待过程中,是处于阻塞状态的成通信。这样的通信模型没有频繁的消息传递,没有冲突与高系统资源的占用,只有接受请求才执行服务,简单而且高效。
我们对比一下这两种最常用的通信方式,加深我们对两者的理解和认识,具体见下表:
注意:远程过程调用(Remote Procedure Call,RPC),可以简单通俗的理解为在一个进程里调用另一个进程的函数。在下面的具体示例中也有体现。
客户端Client的编程实现
首先我们来实现下面的服务模型,主要的功能就是实现在我们的小海龟仿真客户端下添加一只新的小海龟。serve端是小海龟仿真器,client端则是本节中我们需要实现的功能。client主要用来发布一个生成小海龟的请求给serve端,serve端在完成请求后会反馈一个response给client端。它们之间的通讯桥梁就是我们的Service,在这个例子中就是spawn,整个的管理者依旧是Rosmaster。
与上篇博客一样,我们依旧创建一个功能包:
cd catkin_ws/src/
catkin_create_pkg learning_service roscpp rospy std_msgs geometry_msgs turtlesim
进入src文件夹,新建一个turtle_spawn.cpp
,将以下代码拷贝进去:
/**
* 该例程将请求/spawn服务,服务数据类型turtlesim::Spawn
*/
#include <ros/ros.h>
#include <turtlesim/Spawn.h>
int main(int argc, char** argv)
{
// 初始化ROS节点
ros::init(argc, argv, "turtle_spawn");
// 创建节点句柄
ros::NodeHandle node;
// 发现/spawn服务后,创建一个服务客户端,连接名为/spawn的service
ros::service::waitForService("/spawn");
ros::ServiceClient add_turtle = node.serviceClient<turtlesim::Spawn>("/spawn");
// 初始化turtlesim::Spawn的请求数据
turtlesim::Spawn srv;
srv.request.x = 2.0;
srv.request.y = 2.0;
srv.request.name = "turtle2";
// 请求服务调用
ROS_INFO("Call service to spwan turtle[x:%0.6f, y:%0.6f, name:%s]",
srv.request.x, srv.request.y, srv.request.name.c_str());
add_turtle.call(srv);
// 显示服务调用结果
ROS_INFO("Spwan turtle successfully [name:%s]", srv.response.name.c_str());
return 0;
};
需要指出的是,ros::service::waitForService("/spawn");
这行语句是一个阻塞型的API,如果没有这个spawn的例程就会一直