ROS 2 系列 3 - Service 编程
1. 什么是 Service?和 Topic、Action 有什么不同?
在 ROS 2 中,Service(服务)是基于请求-响应(Request-Reply)模型的同步通信机制。
| 通信机制 | 方向 | 特点 | 适用场景 |
|---|---|---|---|
| Topic | 单向(发布/订阅) | 不问结果,只管发,数据流大 | 传感器数据、雷达点云 |
| Service | 双向(请求/响应) | 一问一答,瞬间完成,无反馈进度 | 查询参数、开关控制、简单计算 |
| Action | 双向(目标/反馈/结果) | 带进度条,可取消,耗时任务 | 导航、机械臂运动、复杂算法 |
Server(服务端):提供功能的节点,等待他人调用。
Client(客户端):发起请求的节点,发送数据及等待服务器计算结果返回。
注意:Service 的通信是同步逻辑(虽然底层异步,但 API 表现为等待结果)。如果服务器计算耗时过长,客户端将一直阻塞等待,所以 Service 适合短平快的任务。
2. 定义 Service 接口(.srv 文件)
和 Action 一样,Service 也需要自定义数据结构。下面在独立的功能包中定义 .srv 文件。
2.1. 创建接口功能包
cd ~/ros2_ws/src
ros2 pkg create --license Apache-2.0 custom_interfaces --build-type ament_cmake2.2. 新建 srv 目录,创建接口文件
cd custom_interfaces
mkdir srv
touch srv/AddTwoInts.srv2.3. 编写 .srv 文件
打开 srv/AddTwoInts.srv,写入以下内容:
int64 a
int64 b
---
int64 sum语法解释:
--上面是请求(Request)部分,包含两个整数a和b。
--下面是响应(Response)部分,包含一个整数sum。
2.4. 修改 CMakeLists.txt 和 package.xml
2.4.1. 修改 CMakeLists.txt
在 find_package 下面添加:
find_package(rosidl_default_generators REQUIRED)在文件末尾添加:
rosidl_generate_interfaces(${PROJECT_NAME}
"srv/AddTwoInts.srv"
)2.4.2. 修改 package.xml
在 <buildtool_depend> 后面添加:
<build_depend>rosidl_default_generators</build_depend>
<exec_depend>rosidl_default_runtime</exec_depend>
<member_of_group>rosidl_interface_packages</member_of_group>2.5. 编译接口包
回到工作空间根目录,只编译这个接口包:
cd ~/ros2_ws
colcon build --packages-select custom_interfaces
source install/setup.bash运行 ros2 interface show custom_interfaces/srv/AddTwoInts,如果打印刚写的三行内容,说明接口编译成功。
3. 创建功能包,编写 Service 服务器
创建专门存放 Python 节点的功能包 service_demo_py。
3.1. 创建 Python 功能包
cd ~/ros2_ws/src
ros2 pkg create --license Apache-2.0 service_demo_py --build-type ament_python --dependencies rclpy custom_interfaces3.2. 编写服务器节点
进入 service_demo_py/service_demo_py/ 目录,新建 service_server.py 文件:
cd service_demo_py/service_demo_py/
touch service_server.py写入以下代码:
#!/usr/bin/env python3
import rclpy
from rclpy.node import Node
# 导入我们刚刚编译生成的服务接口
from custom_interfaces.srv import AddTwoInts
class AddServiceServer(Node):
def __init__(self):
super().__init__('add_service_server')
# 创建服务:服务类型、服务名称、回调函数
self.srv = self.create_service(
AddTwoInts,
'add_two_ints',
self.add_callback
)
self.get_logger().info('Add service is ready. Waiting for requests...')
def add_callback(self, request, response):
"""
回调函数固定格式:接收 request,填充 response。
这个函数执行完,响应就会自动发回给客户端。
"""
# 执行加法运算
response.sum = request.a + request.b
self.get_logger().info(f'Received request: {request.a} + {request.b} = {response.sum}')
return response # 必须返回 response
def main(args=None):
rclpy.init(args=args)
node = AddServiceServer()
rclpy.spin(node) # 保持节点运行,监听请求
node.destroy_node()
rclpy.shutdown()
if __name__ == '__main__':
main()4. 编写 Service 客户端
在同一目录(service_demo_py/service_demo_py/)下,新建 service_client.py 文件:
touch service_client.py写入以下代码:
#!/usr/bin/env python3
import rclpy
from rclpy.node import Node
from custom_interfaces.srv import AddTwoInts
class AddServiceClient(Node):
def __init__(self):
super().__init__('add_service_client')
# 创建客户端
self.client = self.create_client(AddTwoInts, 'add_two_ints')
# 等待服务器上线(最多等待 1 秒循环检查)
while not self.client.wait_for_service(timeout_sec=1.0):
self.get_logger().warn('Service not available, waiting again...')
self.req = AddTwoInts.Request() # 预创建请求对象
def send_request(self, a, b):
# 填充请求数据
self.req.a = a
self.req.b = b
# 异步发送请求(不会阻塞,立即返回一个 Future 对象)
self.future = self.client.call_async(self.req)
# 当服务器返回响应时,自动调用下面的回调函数
self.future.add_done_callback(self.response_callback)
self.get_logger().info(f'Sent request: {a} + {b}')
def response_callback(self, future):
# 取出响应结果
try:
response = future.result()
self.get_logger().info(f'Received response: sum = {response.sum}')
except Exception as e:
self.get_logger().error(f'Service call failed: {e}')
# 收到结果后关闭节点(让程序正常退出)
rclpy.shutdown()
def main(args=None):
rclpy.init(args=args)
node = AddServiceClient()
# 发送请求:计算 3 + 5
node.send_request(3, 5)
rclpy.spin(node) # 保持节点运行,等待回调执行
if __name__ == '__main__':
main()5. 配置 setup.py 入口点
为让 ros2 run 能找到这两个节点,需要修改功能包的 setup.py 文件。
打开 service_demo_py/setup.py,在 entry_points 字段中添加以下内容:
entry_points={
'console_scripts': [
'service_server = service_demo_py.service_server:main',
'service_client = service_demo_py.service_client:main',
],
},6. 编译及运行
6.1. 编译工作空间
回到工作空间根目录,编译整个工作空间(接口包和功能包一起编译):
cd ~/ros2_ws
colcon build
source install/setup.bash6.2. 打开两个终端(都需 source 环境)
终端 1:运行服务端
source ~/ros2_ws/install/setup.bash
ros2 run service_demo_py service_server终端 2:运行客户端
source ~/ros2_ws/install/setup.bash
ros2 run service_demo_py service_client7. 用命令行工具调试
7.1. 查看当前系统中所有运行的服务
ros2 service list7.2. 查看某个服务的接口类型
ros2 service type /add_two_ints7.3. 查看某个服务的接口详情
ros2 interface show custom_interfaces/srv/AddTwoInts7.4. 手动调用服务
ros2 service call /add_two_ints custom_interfaces/srv/AddTwoInts "{a: 10, b: 20}"