3. 服务通信

任务目标

本案例实现ROS2中最基础的服务通信(请求-响应模式):创建一个服务端节点,提供两个整数相加的服务;创建一个客户端节点,向服务端发送两个整数并接收求和结果。通过本案例掌握ROS2服务通信的完整流程:接口定义、服务端实现、客户端实现、编译配置与运行验证。

环境与依赖

  • 已安装ROS2(Humble或Foxy版本均可,本案例以Humble为例)
  • 已创建ROS2工作空间 ~/ros2_ws/src
  • 需要 example_interfaces 包(ROS2默认自带),其中包含 AddTwoInts 服务接口(int64 a + int64 b → int64 sum)

实现步骤

在 ~/ros2_ws/src 下创建功能包 service_demo,包含服务端节点 server_node 和客户端节点 client_node。两个节点均使用Python编写,通过 example_interfaces/srv/AddTwoInts 完成通信。

关键完整代码

文件1:service_demo/service_demo/server_node.py(服务端)

import rclpy
from rclpy.node import Node
from example_interfaces.srv import AddTwoInts

class AddTwoIntsServer(Node):
    def __init__(self):
        super().__init__('add_two_ints_server')
        self.srv = self.create_service(AddTwoInts, 'add_two_ints', self.add_callback)
        self.get_logger().info('服务端已启动,等待请求...')

    def add_callback(self, request, response):
        response.sum = request.a + request.b
        self.get_logger().info(f'收到请求: {request.a} + {request.b} = {response.sum}')
        return response

def main(args=None):
    rclpy.init(args=args)
    node = AddTwoIntsServer()
    try:
        rclpy.spin(node)
    except KeyboardInterrupt:
        pass
    finally:
        node.destroy_node()
        rclpy.shutdown()

if __name__ == '__main__':
    main()

文件2:service_demo/service_demo/client_node.py(客户端)

import rclpy
from rclpy.node import Node
from example_interfaces.srv import AddTwoInts

class AddTwoIntsClient(Node):
    def __init__(self):
        super().__init__('add_two_ints_client')
        self.cli = self.create_client(AddTwoInts, 'add_two_ints')
        while not self.cli.wait_for_service(timeout_sec=1.0):
            self.get_logger().info('等待服务端上线...')
        self.req = AddTwoInts.Request()

    def send_request(self, a, b):
        self.req.a = a
        self.req.b = b
        future = self.cli.call_async(self.req)
        rclpy.spin_until_future_complete(self, future)
        if future.result() is not None:
            return future.result().sum
        else:
            self.get_logger().error('服务调用失败')
            return None

def main(args=None):
    rclpy.init(args=args)
    client = AddTwoIntsClient()
    # 发送请求:3 + 5
    result = client.send_request(3, 5)
    if result is not None:
        client.get_logger().info(f'调用结果: 3 + 5 = {result}')
    client.destroy_node()
    rclpy.shutdown()

if __name__ == '__main__':
    main()

文件3:service_demo/setup.py(入口配置,仅显示关键部分)

from setuptools import setup
import os
from glob import glob

package_name = 'service_demo'

setup(
    name=package_name,
    version='0.0.1',
    packages=[package_name],
    data_files=[
        ('share/ament_index/resource_index/packages', ['resource/' + package_name]),
        ('share/' + package_name, ['package.xml']),
        (os.path.join('share', package_name), glob('launch/*.launch.py'))
    ],
    install_requires=['setuptools'],
    zip_safe=True,
    maintainer='your_name',
    maintainer_email='your_email@example.com',
    description='ROS2 service communication demo',
    license='Apache-2.0',
    entry_points={
        'console_scripts': [
            'server_node = service_demo.server_node:main',
            'client_node = service_demo.client_node:main',
        ],
    },
)

编译与运行

# 1. 编译
cd ~/ros2_ws
colcon build --packages-select service_demo
source install/setup.bash

# 2. 运行服务端(终端1)
ros2 run service_demo server_node

# 3. 运行客户端(终端2,需先source环境)
source ~/ros2_ws/install/setup.bash
ros2 run service_demo client_node

验证方法与判定标准

  • 服务端终端输出:启动后显示“服务端已启动,等待请求...”;收到请求后显示“收到请求: 3 + 5 = 8”。
  • 客户端终端输出:显示“调用结果: 3 + 5 = 8”。
  • 命令行验证:在第三个终端执行 ros2 service list 应看到 /add_two_ints;执行 ros2 service type /add_two_ints 应显示 example_interfaces/srv/AddTwoInts。
  • 判定通过条件:客户端输出结果为8,且服务端日志同步显示收到请求并完成计算,即证明请求-响应通信闭环成功。

常见问题

  • 找不到 example_interfaces:执行 sudo apt install ros-humble-example-interfaces 安装。
  • 客户端提示“等待服务端上线”后卡住:确认服务端已成功启动且服务名称一致(均为 add_two_ints)。
  • 编译报错找不到包:确认 package.xml 中已声明对 example_interfaces 和 rclpy 的依赖。