在机器人操作系统(ROS)的开发体系中,节点间通信是实现机器人功能的核心基础。ROS 提供了三类核心通信机制:话题通信(Topic)、服务通信(Service)与参数服务器(Parameter Server)。其中服务通信是典型的请求 - 响应(Request-Response)同步通信模型,专门用于 “客户端发起一次性请求、服务端处理并返回结果” 的交互场景,是机器人技术中实现指令调用、功能触发、单次计算的关键技术。

  本文将从核心概念、运行原理、自定义消息、代码实现到实操验证,完整讲解机器人技术中服务通信的开发全流程。

一、服务通信的核心概念

  服务通信基于客户端 / 服务器(Client/Server)架构,是一种双向、同步的通信方式,核心包含三个要素:

  服务端(Server):提供具体功能的节点,启动后持续等待请求,接收到客户端请求后执行处理逻辑,并返回响应结果。一个服务名称只能对应一个服务端。

  客户端(Client):发起服务调用的节点,向指定服务发送请求数据,随后阻塞等待服务端返回处理结果。同一个服务可以存在多个客户端。

  服务消息(srv):服务通信的数据格式定义,存储在.srv文件中,分为请求(Request)响应(Response)两部分,中间用---分隔。

和话题通信的 “发布 - 订阅、异步单向、持续数据流” 特性不同,服务通信是一次性、同步双向的交互,更强调请求的完整性和结果反馈。

二、服务通信与话题通信的核心区别

很多初学者容易混淆两种通信方式,这里通过表格清晰对比差异:

对比维度 服务通信(Service) 话题通信(Topic)
通信模型 请求 - 响应(Client/Server) 发布 - 订阅(Publish/Subscribe)
同步特性 同步通信,客户端请求后阻塞等待响应 异步通信,发布者无需感知订阅者状态
数据流向 双向:请求从客户端到服务端,响应从服务端到客户端 单向:数据仅从发布者流向订阅者
节点数量 1 个服务端,可对接多个客户端 支持多发布者、多订阅者
适用场景 一次性指令调用、功能触发、有返回值的单次计算 高频连续数据流传输(传感器数据、运动指令)
实时性 侧重结果可靠性,不适合高频数据传输 适配低延迟、高频率的连续数据场景

三、服务通信的底层运行流程

ROS 服务通信依赖 ROS Master(节点管理器)完成服务的注册与发现,完整运行分为 4 个阶段:

  1. 服务端注册 服务端节点启动后,向 ROS Master 注册自身提供的服务名称和节点网络地址,声明自己可以提供该服务。

  2. 客户端注册与服务发现 客户端节点启动后,向 ROS Master 发送查询请求,查找指定名称的服务;ROS Master 匹配到对应服务端后,将服务端的地址信息返回给客户端。

  3. 建立连接与发送请求 客户端获取服务端地址后,通过 TCP 协议与服务端建立直接连接,将请求数据发送给服务端。

  4. 处理与响应返回 服务端接收到请求后,执行预设的业务逻辑完成处理,随后将响应结果回传给客户端,一次完整的服务通信结束。

补充:服务发现阶段依赖 ROS Master,但连接建立后客户端与服务端直接通信,数据不再经过 ROS Master 转发。

四、自定义服务消息(srv 文件)

ROS 内置了少量标准服务消息,实际开发中通常需要根据业务自定义数据格式,步骤如下:

1. 创建 srv 文件

在功能包根目录新建srv文件夹,创建AddTwoInts.srv文件,内容如下:

# 请求部分:两个待相加的整数
int32 a
int32 b
---
# 响应部分:相加结果
int32 sum

---是固定分隔符,上方为请求数据结构,下方为响应数据结构。

2. 配置编译依赖

package.xml中添加消息生成与运行依赖:

<build_depend>message_generation</build_depend>
<exec_depend>message_runtime</exec_depend>

CMakeLists.txt中配置编译规则:

find_package中添加message_generation

find_package(catkin REQUIRED COMPONENTS
  roscpp
  rospy
  std_msgs
  message_generation
)

注册 srv 文件:

add_service_files(
  FILES
  AddTwoInts.srv
)

激活消息生成:

generate_messages(
  DEPENDENCIES
  std_msgs
)

配置 catkin 运行依赖:

catkin_package(
  CATKIN_DEPENDS roscpp rospy std_msgs message_runtime
)

配置完成后编译功能包,即可生成 C++ 头文件与 Python 调用模块。

五、C++ 版本代码实现

我们以 “两数加法服务” 为例,分别实现服务端和客户端代码。

1. 服务端(Server)代码

在功能包src目录下创建add_server.cpp

#include "ros/ros.h"
#include "service_demo/AddTwoInts.h"  // 自定义服务消息头文件

// 服务回调函数:接收请求,处理后写入响应
bool addCallback(service_demo::AddTwoInts::Request  &req,
                 service_demo::AddTwoInts::Response &res)
{
    // 核心处理逻辑:两数相加
    res.sum = req.a + req.b;
    ROS_INFO("收到客户端请求:a=%d, b=%d,计算结果 sum=%d", req.a, req.b, res.sum);
    return true; // 返回true表示服务处理成功
}

int main(int argc, char **argv)
{
    // 初始化ROS节点
    ros::init(argc, argv, "add_two_ints_server");
    // 创建节点句柄
    ros::NodeHandle n;

    // 创建服务对象,服务名为 add_two_ints,绑定处理回调
    ros::ServiceServer service = n.advertiseService("add_two_ints", addCallback);
    
    ROS_INFO("加法服务已启动,等待客户端请求...");
    
    // 循环等待回调触发
    ros::spin();

    return 0;
}

2. 客户端(Client)代码

src目录下创建add_client.cpp

#include "ros/ros.h"
#include "service_demo/AddTwoInts.h"
#include <cstdlib>

int main(int argc, char **argv)
{
    // 初始化ROS节点
    ros::init(argc, argv, "add_two_ints_client");
    
    // 命令行参数校验
    if (argc != 3)
    {
        ROS_INFO("调用方式: add_two_ints_client [整数a] [整数b]");
        return 1;
    }

    ros::NodeHandle n;
    // 创建服务客户端,绑定服务名
    ros::ServiceClient client = n.serviceClient<service_demo::AddTwoInts>("add_two_ints");
    
    // 构造服务消息对象,填充请求数据
    service_demo::AddTwoInts srv;
    srv.request.a = atoll(argv[1]);
    srv.request.b = atoll(argv[2]);

    // 发起服务调用,阻塞等待响应
    if (client.call(srv))
    {
        ROS_INFO("服务调用成功,计算结果: %d", srv.response.sum);
    }
    else
    {
        ROS_ERROR("服务调用失败,请检查服务端是否启动!");
        return 1;
    }

    return 0;
}

3. 编译配置

CMakeLists.txt中添加可执行文件与链接规则:

# 服务端可执行文件
add_executable(add_server src/add_server.cpp)
target_link_libraries(add_server ${catkin_LIBRARIES})
add_dependencies(add_server ${PROJECT_NAME}_gencpp)

# 客户端可执行文件
add_executable(add_client src/add_client.cpp)
target_link_libraries(add_client ${catkin_LIBRARIES})
add_dependencies(add_client ${PROJECT_NAME}_gencpp)

六、运行验证与常用命令

1. 编译工作空间

回到 ROS 工作空间根目录,执行编译并刷新环境:

catkin_make
source devel/setup.bash

2. 启动 ROS Master

roscore

3. 运行服务端

新开终端,执行:

rosrun service_demo add_server

终端输出 加法服务已启动,等待客户端请求... 即为启动成功。

4. 运行客户端

再开一个终端,传入两个整数参数:

rosrun service_demo add_client 10 20

运行后客户端输出:服务调用成功,计算结果: 30 服务端同步输出:收到客户端请求:a=10, b=20,计算结果 sum=30

5. 常用命令行工具

 查看系统所有服务:rosservice list

 手动命令行调用服务:rosservice call /add_two_ints 5 8

 查看服务消息结构:rossrv show service_demo/AddTwoInts

 查看服务对应消息类型:rosservice type /add_two_ints

七、机器人领域的典型应用场景

服务通信在实际机器人开发中应用广泛,典型场景包括:

  1. 功能指令触发:控制机械臂执行一次抓取、触发机器人导航到指定目标点、启停传感器数据采集。
  2. 参数查询与配置:查询机器人电池电量、修改运动控制参数、设置传感器检测阈值。
  3. 单次计算服务:调用逆运动学求解、坐标空间转换、单次路径规划查询。
  4. 系统管理操作:保存 / 加载参数配置、触发系统自检、重启指定功能节点。

八、常见问题与注意事项

  1. 服务名全局唯一:同一个服务名称只能有一个服务端,重复注册会导致原有服务被覆盖。
  2. 同步阻塞特性:客户端调用call()后会进入阻塞状态,若服务端响应慢会导致客户端卡顿,复杂场景可使用异步调用接口。
  3. 消息类型严格匹配:客户端与服务端必须使用完全一致的 srv 消息定义,否则无法建立连接。
  4. 服务端启动时序:客户端调用服务前必须确保服务端已启动,否则call()会直接返回 false。

总结

服务通信是 ROS 机器人开发的核心通信手段之一,它以同步请求 - 响应的模式,完美适配了机器人系统中 “指令下发 - 结果反馈” 的交互需求。结合话题通信、参数服务器,开发者可以构建完整的机器人节点通信架构,支撑从环境感知、决策规划到运动执行的全流程数据交互。

掌握服务通信的原理与开发方法,是入门机器人技术、实现复杂机器人功能调度的必备基础。

Logo

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

更多推荐