前言

在 ROS2 机器人开发体系中,节点通信是多节点协作的核心基础,官方提供了两大核心通信机制:话题通信(Topic)服务通信(Service),二者场景分工明确,是ROS2开发必须掌握的核心能力。

话题通信基于发布/订阅模型,为单向、持续、无应答的数据传输方式,适配激光雷达、摄像头、IMU等周期性输出的传感器数据流;而服务通信基于客户端-服务端(C/S)同步问答模型,由客户端主动发起请求,服务端接收请求、处理业务逻辑并返回唯一响应,属于单次、可控、有反馈的交互方式,完美适配设备启停控制、数值运算、设备状态查询、参数设置等单次指令交互场景。

本文将通过两数求和实战案例,从零实现 ROS2 自定义服务通信全流程,包含自定义服务接口、服务端/客户端C++代码开发、编译配置、运行测试与问题排查,全程可复刻、无遗漏,适合新手入门与工程复用。

一、案例需求与整体架构分析

1.1 业务需求

搭建一套完整的ROS2自定义服务通信程序,实现功能:

  1. 客户端支持终端命令行传入两个整型数字,封装为服务请求数据;

  2. 服务端常驻后台,持续监听服务请求,接收两个整数并完成求和运算;

  3. 服务端将运算结果封装为响应数据,回传给客户端;

  4. 两端打印详细运行日志,完整展示「请求发送-数据处理-结果返回」全流程;

  5. 最终实现效果:终端输入客户端指令 ros2 run cpp02_service demo02_client 100 200,输出求和结果300。

1.2 服务通信三大核心要素

ROS2服务通信必须包含三个核心组件,三者缺一不可,是服务通信的底层核心逻辑:

  1. 服务端(Server):被动监听节点,后台常驻运行,负责注册服务、监听客户端请求、触发回调函数处理业务逻辑、生成并返回响应结果。

  2. 客户端(Client):主动发起交互节点,主动连接服务端、封装请求参数、发送数据、阻塞/异步等待并接收服务端响应结果。

  3. 服务接口(.srv文件):两端数据交互的统一协议,严格规定请求、响应的数据类型与字段。文件中以 --- 分割上下两部分,上段为「客户端→服务端」的请求数据,下段为「服务端→客户端」的响应数据,编译后自动生成C++/Python可调用的结构体。

1.3 整体实现流程

本案例遵循ROS2标准化工程开发流程,6个核心步骤,层层递进、无跳过步骤:

  1. 自定义 .srv 服务接口文件,配置接口包编译规则;

  2. 编译接口包,刷新环境变量,校验接口有效性;

  3. 创建带依赖的C++服务功能包;

  4. 分别编写服务端、客户端完整业务代码;

  5. 配置功能包CMakeLists.txt编译与安装规则;

  6. 整体编译工作空间,多终端启动节点,完成功能测试与问题验证。

二、第一步:自定义服务通信接口 AddInts.srv

2.1 创建.srv 文件

在接口功能包base_interfaces_demo中新建srv文件夹,创建AddInts.srv文件,写入如下内容并保存:

语法核心说明:文件中 --- 为固定分隔符,上方区域为 Request 请求区(客户端传给服务端的数据),下方区域为 Response 响应区(服务端返回客户端的数据)。编译成功后,ROS2 会自动生成 AddInts::RequestAddInts::Response 两个结构体,供C++代码直接调用。

2.2 配置 CMakeLists.txt 编译脚本

原始功能包无法识别.srv文件,必须手动配置编译规则,让编译工具将自定义接口转换为C++可调用的头文件。打开 base_interfaces_demo/CMakeLists.txt,在文件中添加以下完整配置:

2.3 编译接口包

单独编译接口包,避免全量编译浪费时间,提升开发效率,终端执行以下指令:

colcon build --packages-select base_interfaces_demo

编译完成后必须刷新环境变量,让系统识别新生成的自定义接口,否则代码会报头文件找不到错误:

source install/setup.bash

2.4 接口有效性测试

为确保接口编译成功、格式无误,执行接口查询指令,校验接口定义:

ros2 interface show base_interfaces_demo/srv/AddInts

三、第二步:创建 C++ 服务功能包

在工作空间 src 目录下,通过官方指令创建C++类型功能包,自动绑定核心依赖,无需手动配置依赖文件。终端执行创建指令:

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

参数详细解析

  • cpp02_service:自定义功能包名称,用于存放服务端、客户端源码;

  • --build-type ament_cmake:指定编译类型为C++专用的ament编译体系;

  • rclcpp:ROS2 C++核心库,提供节点创建、服务注册、日志输出、自旋等待等基础API;

  • base_interfaces_demo:依赖自定义的服务接口包,用于调用AddInts服务结构体。

四、第三步:C++ 服务端代码实现

4.1 文件路径

cpp02_service/src/ 路径下新建源码文件 demo01_server.cpp

4.2 完整源码

4.3 核心代码逐段解析

  1. 服务创建函数create_service<AddInts>("add_ints", 回调函数) 服务名 add_ints 是全局唯一标识,客户端必须使用完全一致的名称,否则无法建立通信连接;回调函数用于处理所有客户端请求。

  2. 回调函数双参数req 存储客户端传入的请求数据,只读不可修改;res 为空响应体,用于写入业务处理结果,ROS2自动封装返回给客户端。

  3. 自旋函数rclcpp::spin(server) 阻塞主线程,让节点持续运行,实时监听服务事件、触发回调,是服务端能够常驻后台的核心。

五、第四步:C++ 客户端代码实现

5.1 文件路径

cpp02_service/src/ 路径下新建源码文件 demo02_client.cpp

5.2 完整源码

5.3 核心代码解析

  1. wait_for_service(1s):循环轮询服务端状态,解决客户端先启动、服务端未就绪导致的连接失败问题,提升程序稳定性;

  2. async_send_request():异步发送请求,不阻塞主线程,返回Future异步结果对象,适配ROS2标准通信规范;

  3. spin_until_future_complete():阻塞节点运行,直到接收服务端响应,避免提前读取空结果,保证数据完整性;

  4. atoi(argv[]):将终端输入的字符串参数转为整型,实现自定义动态传参,灵活适配不同数值求和测试;

  5. 参数校验:判断终端参数数量,避免用户输入格式错误导致程序崩溃,提升代码健壮性。

六、第五步:配置 CMakeLists.txt 编译规则

源码编写完成后,必须配置编译安装规则,否则编译后无法生成可执行文件,无法通过ros2 run启动节点。打开 cpp02_service/CMakeLists.txt,在文件末尾追加以下完整配置:

功能包创建时已自动生成package.xml的依赖声明,无需手动修改依赖配置,直接编译即可生效。

七、第六步:编译与运行测试

7.1 编译功能包

终端进入工作空间根目录,执行针对性编译指令,仅编译当前功能包,提升效率:

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

7.2 双终端分步启动

终端 1:启动服务端

输出日志:

终端 2:启动客户端,传入参数100 200

输出日志:

两端日志正常输出、数据交互准确,代表整套自定义服务通信程序开发、编译、运行全部完成。

八、拓展知识点:服务通信深度总结

8.1 话题通信 vs 服务通信对比

对比维度

话题通信(Topic)

服务通信(Service)

交互模式

单向发布订阅,无应答、无反馈

双向一问一答,同步响应、有明确反馈

适用场景

传感器、图像、雷达等持续高频数据流

设备控制、数值计算、状态查询、参数设置等单次指令

启动顺序

发布者、订阅者无先后启动要求

必须先启动服务端,再启动客户端

消息载体

.msg 单文件,仅单向数据格式

.srv 文件,区分请求/响应双格式

通信特性

异步通信、无连接校验、数据可能丢失

同步通信、有连接校验、数据可靠传输

8.2 开发高频报错与解决方案

  1. 汇总新手开发90%会遇到的报错问题,精准定位原因、给出可直接执行的解决方案:

  2. 报错1:Could not find executable 核心原因:CMakeLists.txt 未添加编译安装代码、未执行编译指令、未刷新环境变量 解决方案:补全编译配置脚本,执行 colcon build 重新编译,刷新 source install/setup.bash 环境变量

  3. 报错2:客户端无法连接服务端 核心原因:服务端与客户端服务名字符串不一致、自定义接口包未编译、环境未刷新 解决方案:统一两端服务名,重新编译base_interfaces_demo接口包,刷新环境变量后重启终端测试

  4. 报错3:编译报头文件找不到 核心原因:功能包缺少base_interfaces_demo依赖、接口未编译、编译缓存异常 解决方案:删除build、install缓存文件,重新编译接口包,使用带完整依赖的指令创建功能包

  5. 报错4:参数传入无效、数值为0 核心原因:终端参数数量错误、数据类型不匹配 解决方案:严格按照 数字1 数字2 格式传参,保证传入整型数值。

九、文末总结

本文通过两数求和实战案例,完整复刻 ROS2 自定义服务通信全工程开发流程,补齐了常规教程缺失的配置细节、原理讲解与报错排查,全程闭环可落地。通过本文学习,你可完全掌握以下核心能力:

  • 熟练掌握 .srv 自定义服务接口的定义、语法规则、编译配置与有效性测试方法;

  • 精通 ROS2 C++ 服务端节点开发模板,掌握服务注册、回调绑定、业务逻辑处理核心逻辑;

  • 掌握 C++ 客户端节点服务校验、异步请求、阻塞等待、结果解析的标准化开发流程;

  • 理清话题通信与服务通信的核心差异与工程选型标准;

  • 熟练解决服务通信开发中的高频报错,具备独立开发、调试、落地自定义服务的工程能力。

本文代码可直接复用至机器人设备控制、参数调试、状态查询等实际工程场景,是 ROS2 进阶多节点协同开发的核心基础。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐