一、前言

在机器人系统开发中,ROS2 提供多种节点通信方式,其中话题通信服务通信是最核心的两种机制。

  • 话题通信:发布者持续单向推送数据,订阅者被动接收,不存在应答机制,适合激光雷达、相机等连续流式传感器数据传输;
  • 服务通信:采用客户端 / 服务端(C/S)一问一答同步交互模型。客户端主动发起请求,服务端接收请求、执行业务逻辑并返回处理结果,适合单次指令交互场景,例如设备启停、参数查询、运算请求、机械臂定点运动。

很多初学者学习 ROS2 时容易混淆话题与服务的适用场景,同时在自定义.srv接口、客户端服务端交互、编译配置上频繁踩坑。本文严格依照课程实训案例,完整还原需求分析→自定义服务接口→功能包创建→服务端开发→客户端开发→编译配置→运行测试全流程,附带原理剖析、完整源码、运行效果、易错点总结,适合课程作业、实训报告,知识点覆盖全面,逻辑层次完整。

二、案例需求与架构分析

2.1 案例需求

编写 ROS2 服务通信程序,实现整数求和功能:

  1. 客户端在终端传入两个整数,作为请求数据发送至服务端;
  2. 服务端持续监听服务请求,解析收到的两个数字完成求和运算;
  3. 服务端将求和结果封装为响应消息返回客户端;
  4. 两端打印日志,直观展示请求、运算、响应全过程。

运行效果:客户端输入100 200,服务端计算后返回结果300

2.2 服务通信三大核心要素

服务通信模型由 3 部分构成,缺一不可:

  1. 客户端 Client:通信发起方,主动创建连接、封装请求数据,等待接收服务端反馈;
  2. 服务端 Server:通信监听方,长期运行等待客户端接入,收到请求后触发回调函数处理业务,生成响应数据;
  3. 消息载体(.srv 服务接口):约定客户端与服务端的数据传输格式,使用---分隔两段:
    • 上半段:Request(请求数据,客户端→服务端)
    • 下半段:Response(响应数据,服务端→客户端)

2.3 整体开发流程

  1. 创建并编辑.srv自定义服务文件;
  2. 修改接口包CMakeLists.txt配置消息编译规则;
  3. 创建 C++ 服务功能包,添加对应依赖;
  4. 编写服务端源代码;
  5. 编写客户端源代码;
  6. 修改业务包CMakeLists.txt实现编译安装;
  7. 编译工作空间,刷新环境变量;
  8. 多终端启动节点,完成功能测试。

三、自定义服务通信接口 AddInts.srv

3.1 创建.srv 文件

base_interfaces_demo功能包下新建srv文件夹,创建AddInts.srv,写入内容:

文件说明:

  • int32 num1int32 num2:客户端发送的两个加数;
  • --- 分隔请求区与响应区;
  • int32 sum:服务端计算完成后返回的求和结果。

3.2 配置接口包 CMakeLists.txt

想要编译工具识别.srv并自动生成可供 C++/Python 调用的接口代码,需要配置消息生成器:

若为全新接口包,不能缺少find_package(rosidl_default_generators REQUIRED);已有 msg 消息的包仅需要在rosidl_generate_interfaces内追加srv/AddInts.srv路径。

3.3 编译接口包

colcon build --packages-select base_interfaces_demo
source install/setup.bash

3.4 接口校验测试

编译完成后执行如下命令,查看接口定义是否生效:

ros2 interface show base_interfaces_demo/srv/AddInts

终端输出内容与AddInts.srv完全一致,则自定义接口编译成功。

四、创建 C++ 服务功能包

进入工作空间src目录,执行命令创建 C++ 功能包:

ros2 pkg create cpp02_service --build-type ament_cmake --dependencies rclcpp base_interfaces_demo

参数解析:

  1. cpp02_service:功能包名称;
  2. --build-type ament_cmake:指定编译框架为 C++ 使用的 ament_cmake;
  3. rclcpp:ROS2 C++ 客户端库,提供节点、服务、日志、自旋等基础 API;
  4. base_interfaces_demo:依赖自定义服务接口包。

五、C++ 服务端代码实现

文件路径:cpp02_service/src/demo01_server.cpp

重点原理剖析

  1. create_service<消息类型>("服务名", 回调) 服务名称为字符串常量,属于通信匹配标识,客户端服务名字符串必须完全一致,否则无法建立连接
  2. 回调函数参数 req为只读请求数据指针,读取客户端上传参数;res为响应指针,开发者填充数据后,ROS2 底层自动发送给客户端;
  3. rclcpp::spin()自旋机制 ROS2 节点所有消息、服务事件依靠自旋循环处理;若无 spin,节点无法感知客户端请求,回调函数永远不会触发。

六、C++ 客户端代码实现

文件路径:cpp02_service/src/demo02_client.cpp

重点原理剖析

  1. wait_for_service():持续探测对应名称的服务是否存在,解决客户端先行启动而服务端未启动导致通信失败问题;
  2. async_send_request():异步非阻塞发送请求,返回 Future 异步结果对象;
  3. spin_until_future_complete():阻塞节点,直至服务端返回响应,防止提前读取空结果。

七、配置 CMakeLists.txt

打开cpp02_service/CMakeLists.txt,在文件末尾追加编译与安装规则,这是极易遗漏的扣分点

原理:ament 编译系统不会自动编译 cpp 源码,必须手动声明可执行文件、依赖库以及安装路径;缺少 install 指令时,ros2 run无法检索到节点程序。

八、编译与运行测试

8.1 编译功能包

colcon build --packages-select cpp02_service

8.2 终端 1:启动服务端

8.3 终端 2:启动客户端服务端新增日志:

运行结果和课程截图完全一致,功能验证通过。

九、重难点拓展(拉高分数核心板块)

9.1 话题通信与服务通信对比总结

对比项 话题通信 服务通信
交互模式 单向发布订阅,无应答 双向一问一答,同步应答
数据特征 连续周期性数据流 单次按需交互
启动顺序 发布订阅无强制先后 必须先启动服务端
消息文件 .msg .srv(区分请求 / 响应)
典型场景 传感器数据传输 指令控制、数值运算、状态查询

9.2 常见问题与故障排查

  1. ros2 run 提示找不到可执行文件 原因:CMakeLists 未添加编译安装代码;未执行colcon build;新开终端没有source install/setup.bash
  2. 头文件找不到 add_ints.hpp 原因:接口包base_interfaces_demo没有编译;功能包缺少对应依赖。
  3. 客户端长时间无法连接服务端 原因:客户端、服务端填写的服务名字符串不一致,大小写、字符必须完全相同。
  4. 客户端参数缺失直接退出 运行指令格式错误,必须传递两个整数参数。

十、实验总结

本次实验完整实现 ROS2 服务通信 C/S 架构,完成从自定义.srv接口、功能包搭建、服务端与客户端编码、编译部署到功能验证整套工程流程。

  1. 掌握.srv服务接口的语法规则、编译配置与校验方式;
  2. 理解服务通信一问一答交互模型,能够区分服务通信与话题通信适用场景;
  3. 掌握 C++ 服务端创建服务、回调处理请求的标准开发模板;
  4. 掌握 C++ 客户端连接服务、异步发送请求、接收响应的实现方式;
  5. 熟悉 ROS2 ament 编译体系,能够独立编写 CMakeLists.txt 配置,并且自主排查编译、节点通信相关故障。

服务通信是机器人指令交互不可或缺的通信手段,本次两数求和案例的代码框架具备通用性,稍加修改即可应用于电机控制、传感器参数读取、任务指令下发等真实机器人工程项目。

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐