![ROS学习笔记(4):通信机制之服务通信](https://cdn.jsdelivr.net/gh/renyuhui0415/blogimg@main/202311291846470.png)
ROS学习笔记(4):通信机制之服务通信
![](https://cdn.jsdelivr.net/gh/renyuhui0415/blogimg@main/202311052032318.jpg)
服务通信
前面介绍了话题通信,虽然使用频率较高,但是有一个小小的不足之处,就是话题通信的数据是单向传输的,订阅者被动接收发布者的消息。那么如果想要主动接收数据怎么办?这就要好好介绍今天的主角--服务通信了。
服务通信也是ROS中一种极其常用的通信模式,服务通信是基于请求响应模式的,是一种应答机制。也即: 一个节点A向另一个节点B发送请求,B接收处理请求并产生响应结果返回给A。适用于偶然的、对实时性有要求、有一定逻辑处理需求的数据传输场景。
理论模型
该模型中涉及三个角色:ROS Master(管理者)、Server(服务端)、Client(客户端)
Server向ROS Master注册,有PRC、ROSRPC地址
Client向ROS Master查询 指定服务
ROS Master进行匹配,匹配成功返回Server的 ROSRPC URL
Client向Server发送请求数据
Server向Client返回响应数据
注意:Server不需要协商的过程,直接返回ROSPRC地址。
服务通信与话题通信的区别如下图
详细区别请看该问答:ROS Topic 和 ROS Service 有哪些区别?
自定义srv
在使用服务通信的时候,数据载体是srv,和msg一样,需要我们手动创建。
srv 文件内的可用数据类型与 msg 文件一致,且定义 srv 实现流程与自定义 msg 实现流程类似:
按照固定格式创建srv文件
编辑配置文件
编译生成中间文件
咱们就用一个实例来进行演示。
编写服务通信,客户端提交两个整数至服务端,服务端求和并响应结果到客户端。
定义srv文件
在ROS包下新建一个srv文件夹,在srv文件夹里面新建一个srv文件
1 | 这里我又新建了一个工作空间、ROS包,你们可以选择在原来的工作空间内新建一个ROS包 |
服务通信中,数据分成两部分,请求与响应,在 srv 文件中请求和响应使用 --- 分割.
AddTwoNum.srv内容如下
1 | int32 num1 |
编辑配置文件
和自定义msg编辑配置文件差不多,不同的是添加的是自定义srv,而不是自定义msg
首先回到ROS包下,修改 package.xml文件内容,添加编译依赖项、执行依赖项
1 | 此时路径:~/service_communication/src/request_response/srv |
下面修改ROS包下的CMakeLists.txt文件
1 | 此时路径:~/service_communication/src/request_response/srv |
添加CMake编译时找到依赖的包 message_generation
添加自定义srv文件 AddTwoNum.srv
生成srv
添加了当前项目依赖的其他catkin项目
查看srv中间文件
回到工作空间下,编译
1 | cd ~/service_communication/ |
编译成功。
可以在工作空间下的devel文件夹下查找中间文件。
C++可以用的中间文件在 ~/service_communication/devel/include/request_response
其中service_communication为工作空间名字,request_response为ROS包的名字。
Python可以用的中间文件在 ~/service_communication/devel/lib/python3/dist-packages/request_response/srv
可以看一下官方示例:创建msg、srv官方示例
C++服务通信实例
再次明确一下目标,编写服务通信,客户端提交两个整数至服务端,服务端求和并响应结果到客户端。
Server实现
首先在ROS包下的src文件夹里面创建 cpp文件,用于实现Server。
1 | cd ~/service_communication/src/request_response/src/ |
代码如下
1 |
|
对于这段代码,需要解释的有以下内容
1 | ros::ServiceServer server = nh.advertiseService("AddInts",handle_request); |
首先ros::ServiceServer是server的数据类型,是用来提供服务的服务器端接口。server由nh.advertiseService方法创建。
advertiseService有9个重载,这里我们用的是第二个。第一个参数为创建服务的名字,第二个参数为回调函数,用于处理客户端请求。
关于回调函数,传递的两个参数分别为 请求数据、响应数据。
接下来就是修改ROS包下的CMakeLists.txt了
add_dependencies(server_cpp ${PROJECT_NAME}_gencpp)这一行是为了防止srv文件没有编译完就直接编译cpp文件而导致报错。
返回工作空间,编译、刷新环境变量,运行节点
但是这时候没有请求服务器,看不到什么东西,此时可以使用rosservice call命令来请求。
1 | rosservice call [service] [args] |
Client实现
首先在ROS包下的src文件夹里面创建 cpp文件,用于实现Client。
1 | cd ~/service_communication/src/request_response/src/ |
文件内容如下
1 |
|
这里,为了快速检验框架是否正确,直接把请求数据写好了,不是根据命令行传参的,后面会改进的。
对于这段代码,有如下几点需要解释。
1 | ros::ServiceClient client = nh.serviceClient<request_response::AddTwoNum>("AddInts"); |
ros::ServiceClient表明client的的数据类型,提供基于句柄的接口,为客户端连接提供服务。
client由 serviceClient方法创建,有3个重载,我们使用的是第二个。<>里面的是服务类型,即自己生成的类;第一个参数为要连接的服务的名称。
1 | request_response::AddTwoNum data; |
request_response::AddTwoNum为自己定义的srv生成的类,下面有request、response两个成员。而request有num1、num2两个成员变量,它们表示要发送的请求数据。
然后修改ROS包下的CMakeLists.txt文件。
回到工作空间编译,刷新环境变量,运行节点。
可以看到,Client、Server都是没问题的。接下来在这个框架下面优化即可。
前面咱们直接给请求数据赋值了,这是不符合要求的,我们需要根据命令行的参数去发送请求信息。
运行client_cpp节点的命令应该是下面这个格式
1 | rosrun request_response client_cpp num1 num2 |
这时候就需要用到main函数的两个参数argc、argv了。
argc(Argument Count):表示命令行参数的数量。它是一个整数,并且至少为 1,因为第一个参数总是程序的名称或者路径。
argv(Argument Vector):是一个指向字符串数组的指针,这个数组包含了每个命令行参数的具体值。argv[0] 是程序的名称,argv[1] 是传递给程序的第一个参数,以此类推,直到 argv[argc-1]。数组的最后一个元素 argv[argc] 是一个空指针(NULL),用于标识数组的结束。 根据这两个,咱们重新写一些数据部分
1 |
|
看效果图
注意:这里是先启动了服务端,再启动客户端。如果先启动客户端的话,会报错,因为服务端还未启动。
这里咱们可以使用 client.waitForExistence 函数等待服务端启动 (client.waitForExistence)[https://docs.ros.org/en/noetic/api/roscpp/html/classros_1_1ServiceClient.html#a530e3f1d55cf50ab00b8e5bbd9e8230a]
或者使用 ros::service::waitForService("AddInts"); "AddInts"为服务名字。
ros::service::waitForService()
Python服务通信实例
还是上面的需求,用Pyhton来实现一下。
Server实现
首先在ROS包下新建一个scripts文件夹,然后在该文件夹内创建一个python文件。
1 | cd ~/service_communication/src/request_response/ |
文件内容
1 | #! /usr//env binpython |
1 | server = rospy.Service("AddInts_py",AddTwoNum,handle_request) |
就是创建服务端。第一个参数为服务名字,第二个参数为srv生成的类;第三个参数为处理请求数据的函数。
修改ROS包下的CMakeLists.txt文件
运行节点,使用rosservice call测试
通过,接下来开始写client
Client实现
首先ROS包下的scripts文件夹内创建一个python文件。
1 | cd ~/service_communication/src/request_response/scripts |
文件内容
1 | import rospy |
修改CMakeLists.txt文件
最后运行测试
这里咱们把请求数据固定了,再优化一下,可以根据命令行的参数自定义请求数据。
在sys下有一个列表可以判断参数个数,咱们利用这个来实现。
1 | import rospy |
通过ServiceProxy创建一个客户端,第一个参数为服务名字,第二个参数为srv生成的类
官方示例:官方示例
- Title: ROS学习笔记(4):通信机制之服务通信
- Author: StarHui
- Created at : 2023-12-05 21:58:04
- Updated at : 2023-12-05 22:34:20
- Link: https://renyuhui0415.github.io/post/service_communication.html
- License: This work is licensed under CC BY-NC-SA 4.0.