目录
一、概念
service服务通讯机制是一种双向同步数据传输模式。基于客户端/服务器模型,两部分通信数据类型:一个用于请求,一个用于应答,类似web服务器。
ROS中只允许一个节点提供指定命名的服务。
与topic通讯机制的区别:
比较列表 | 话题topic | 服务service |
---|---|---|
同步性 | 异步 | 同步 |
通信模型 | 发布+订阅 | 客户端+服务器端 |
反馈机制 | 无 | 有 |
缓冲区 | 有 | 无 |
节点关系 | 多对多 | 一(server)对多 |
传输数据格式 | *.msg | *.srv |
适合场景 | 数据传输 | 逻辑处理 |
二、实例
1. 小乌龟例程中的service
首先启动第一个小乌龟:
rosrun turtlesim turtlesim_node
然后查看能够调用的所有service:
调用其中名为/spawn的服务:
如上图红框部分所示,服务器返回指定生成的乌龟名字。同时,在小乌龟仿真中在指定位置出现第二只名为turtle7
的小乌龟,如下图所示。这乌龟可以的,还在听音乐。
最后,我们查看该service的具体数据格式:
与我们的预期一致,—上方是client请求发出的内容,其中包括指定新生成乌龟的位置、朝向、名字,—下方是server返回的内容,string格式的乌龟名字,说明创建成功。
2. 自定义service
与topic话题中需要定义的message文件相似,当我们想要自定义某种服务时,需要提供对应的srv文件来对两个交互节点中具体传输的数据格式进行约束。
这里我们创建一个乘法运算案例,客户端提供两个整数a、b,服务器端计算完成后返回乘法结果到客户端。
将该srv文件命名为multinum.srv
,该文件中的内容如下:
float32 a
float32 b
---
float64 c
将该文件放在名为srv
的文件夹下,将文件夹放在功能包目录下。最终的功能包目录如下所示:
与定义message一样,我们同样要修改对应的package.xml
文件和CmakeLists.txt
文件的对应部分:
package.xml
文件:
<build_depend>message_generation</build_depend>
<exec_depend>message_runtime</exec_depend>
CmakeLists.txt
文件:
第一块:修改find_package部分,确保编译时找到对应文件
find_package(catkin REQUIRED COMPONENTS
roscpp
rospy
std_msgs
message_generation
)
第二块:设置srv文件
add_service_files(
FILES
multinum.srv
)
generate_messages(
DEPENDENCIES
std_msgs
)
第三块:catkin依赖部分
如果我们编写的ros程序不打算给别人使用,这块就无所谓。
catkin_package(
...
CATKIN_DEPENDS roscpp rospy std_msgs message_runtime
...
)
最后,在工作空间编译,通过相关命令测试是否已经安装成功:
如上图所示,我们的service服务查询得到,没问题。
3. 创建服务器节点与客户端节点(c++)
我们利用第二小节中自定义的service格式,来设计两个node进行service通讯。主要功能是client随机生成两个数字传递给service,然后service计算完成之后,将结果回传给client。具体的代码如下所示
server节点编写
#include <ros/ros.h>
#include <learn_service/multinum.h>
bool multi_callback(learn_service::multinum::Request &req, learn_service::multinum::Response &res){
res.c = req.a * req.b;
ROS_INFO("computing result is %f", res.c);
return true;
}
int main(int argc, char **argv){
ros::init(argc, argv, "multi_server");
ros::NodeHandle nh;
ros::ServiceServer server = nh.advertiseService("multi_two_num",multi_callback);
ROS_INFO("waiting two numbers...");
ros::spin();
return 0;
}
client节点编写
#include <ros/ros.h>
#include <learn_service/multinum.h>
#include<cstdlib>
#include <time.h>
int main(int argc, char **argv){
srand(int(time(0)));
double a = rand()/(double(RAND_MAX)/100);
double b = rand()/(double(RAND_MAX)/100);
ros::init(argc, argv, "multi_client");
ros::NodeHandle nh;
ros::ServiceClient client = nh.serviceClient<learn_service::multinum>("multi_two_num");
learn_service::multinum srv;
srv.request.a = a;
srv.request.b = b;
if (client.call(srv)){
ROS_INFO("%f * %f = %f", a, b, srv.response.c);
}else{
ROS_ERROR("Failed to call service");
}
return 0;
}
运行结果
首先我们将服务器节点开启,然后需要计算的时候调用client节点,服务器计算好结果之后回传给client。具体效果如下所示:
4. 创建服务器节点与客户端节点(python)
同样的东西,我们用python再实现一次。
server节点编写
#!/usr/bin/env python
# coding:utf-8
import rospy
from learn_service.srv import *
def callback_func(req):
answer = req.a*req.b
rospy.loginfo("result is %f", answer)
return multinumResponse(answer)
def server():
rospy.init_node("python_server")
s = rospy.Service("multi_2num",multinum,callback_func)
rospy.loginfo("Waiting 2 numbers...")
rospy.spin()
if __name__=="__main__":
server()
重点:
- 启动服务的函数接口:
rospy.Service("service名", srv数据类型, 回调函数)
- 回调函数中传入的是请求request,返回的是response
- 返回的数据类型是自定义的,比如上例中,我们自定义的srv数据类型是
multinum
,那么对应的response数据类型为multinumResponse
client节点编写
#!/usr/bin/env python
# coding:utf-8
import rospy
from learn_service.srv import *
import random
def client():
rospy.init_node("python_client")
rospy.wait_for_service("multi_2num")
try:
c = rospy.ServiceProxy("multi_2num",multinum)
a = random.random() * 100
b = random.random() * 100
response = c.call(a,b)
rospy.loginfo("%f * %f = %f", a, b, response.c)
except rospy.ServiceException, e:
rospy.logerr("service call failed: %s", e)
if __name__=="__main__":
client()
重点:
-
rospy.wait_for_service("service名称")
可以使得该client_node节点直到对应service服务器开始工作之后,代码才继续往下运行 - 启动client的函数接口:
rospy.ServiceProxy("service名称",自定义srv数据类型)
- 向服务器发送请求使用
2
步骤中返回类的call
函数:response = client.call(srv中request相关数据)
,call函数的参数为request的具体参数,本例中就是要乘的a,b两数;或者为对应的request数据格式,本例为multinumRequest(a,b)
运行结果
如下所示,跟预期一致,没有问题。
三、总结
到此,我们用两章的内容,基本涵盖ros通讯中的两大最常用的方式。ros还有一种action通讯方式,平时基本用不到。所以接下来教程也不会涉及到这部分内容。
下一章,我们来一起探索ros中的tf坐标变换。