一、环境前置准备(完整安装 ROS2 Jazzy)

1.1 系统编码校验(UTF-8)

bash

运行

locale

输出含 UTF-8 即为正常,若编码异常执行:

bash

运行

sudo apt update && sudo apt install locales
sudo locale-gen en_US en_US.UTF-8
sudo update-locale LC_ALL=en_US.UTF-8 LANG=en_US.UTF-8
export LANG=en_US.UTF-8

1.2 启用 Ubuntu universe 软件源

bash

运行

apt-cache policy | grep universe

输出包含 500 说明源正常,无输出则手动开启 universe。

1.3 添加 ROS2 官方密钥与软件源

bash

运行

# 安装依赖工具
sudo apt update && sudo apt install curl gnupg lsb-release
# 导入ROS密钥
sudo curl -sSL https://raw.githubusercontent.com/ros/rosdistro/master/ros.key -o /usr/share/keyrings/ros-archive-keyring.gpg
# 写入ROS2 Jazzy软件源(注意删除多余分号)
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/ros-archive-keyring.gpg] http://packages.ros.org/ros2/ubuntu $(source /etc/os-release && echo $UBUNTU_CODENAME) main" | sudo tee /etc/apt/sources.list.d/ros2.list > /dev/null

1.4 安装 ROS2 Jazzy 桌面完整版

bash

运行

sudo apt update
sudo apt install ros-jazzy-desktop

1.5 永久配置环境变量

bash

运行

echo "source /opt/ros/jazzy/setup.bash" >> ~/.bashrc
source ~/.bashrc

关闭终端重新打开,输入海龟仿真测试安装是否成功:

bash

运行

ros2 run turtlesim turtlesim_node

弹出海龟图形窗口代表 ROS2 基础环境安装完成。

1.6 编译工具与 Python 冲突根治(核心踩坑点)

ROS2 编译时常因系统多 Python 版本引发 CMake 报错,本教程提供永久解决方案:

  1. 安装编译依赖

bash

运行

sudo apt install python3-colcon-common-extensions
sudo apt update && sudo apt install ros-dev-tools
  1. 校验 Python 环境

bash

运行

which python
python -V
python3 -V
  1. 优先使用系统 python3 写入环境变量

bash

运行

echo 'export PATH="/usr/bin:$PATH"' >> ~/.bashrc
source ~/.bashrc
  1. colcon 全局默认配置,强制指定 Python3 路径

bash

运行

mkdir -p ~/.colcon
gedit ~/.colcon/defaults.yaml

写入配置:

yaml

build:
  cmake-args:
    - -DPython3_EXECUTABLE=/usr/bin/python3

后续所有工程编译自动生效,无需每次手动传参。

二、ROS2 服务通信原理概述

  1. 通信模式:同步请求应答,客户端发送请求,服务端处理后返回结果;
  2. 组件构成
    • Server(服务端):提供服务接口,注册回调函数处理请求;
    • Client(客户端):主动发起请求,阻塞等待服务返回数据;
    • Service 接口:.srv 文件,分为 Request 请求段、Response 应答段;
  3. 内置测试接口:example_interfaces/srv/AddTwoInts 两数相加服务,无需自定义消息,开箱即用。

三、创建服务通信功能包工程

3.1 工作空间创建

bash

运行

# 创建工作空间目录
mkdir -p ~/ws00_service/src
cd ~/ws00_service/src
# 创建C++功能包,依赖rclcpp、内置服务接口
ros2 pkg create --build-type ament_cmake cpp_srvcli --dependencies rclcpp example_interfaces

目录结构:

plaintext

ws00_service/
└── src/
    └── cpp_srvcli/
        ├── src/
        │   ├── server.cpp   # 服务端源码
        │   └── client.cpp   # 客户端源码
        ├── CMakeLists.txt
        └── package.xml

3.2 完整源码文件

3.2.1 package.xml

xml

<?xml version="1.0"?>
<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
<package format="3">
  <name>cpp_srvcli</name>
  <version>0.0.0</version>
  <description>ROS2 Jazzy C++ Service Client Server Demo 两数相加服务通信</description>
  <maintainer email="user@todo.todo">user</maintainer>
  <license>Apache-2.0</license>

  <buildtool_depend>ament_cmake</buildtool_depend>

  <depend>rclcpp</depend>
  <depend>example_interfaces</depend>

  <test_depend>ament_lint_auto</test_depend>
  <test_depend>ament_lint_common</test_depend>
  <export>
    <build_type>ament_cmake</build_type>
  </export>
</package>
3.2.2 CMakeLists.txt

cmake

cmake_minimum_required(VERSION 3.8)
project(cpp_srvcli)

if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
  add_compile_options(-Wall -Wextra -Wpedantic)
endif()

# 依赖查找
find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(example_interfaces REQUIRED)

# 编译服务端可执行文件
add_executable(server src/server.cpp)
ament_target_dependencies(server rclcpp example_interfaces)

# 编译客户端可执行文件
add_executable(client src/client.cpp)
ament_target_dependencies(client rclcpp example_interfaces)

# 安装二进制文件
install(TARGETS
  server
  client
  DESTINATION lib/${PROJECT_NAME}
)

if(BUILD_TESTING)
  find_package(ament_lint_auto REQUIRED)
  ament_lint_auto_find_test_dependencies()
endif()

ament_package()
3.2.3 src/server.cpp 服务端代码

cpp

运行

#include "rclcpp/rclcpp.hpp"
#include "example_interfaces/srv/add_two_ints.hpp"
#include <memory>

// 服务回调函数:接收请求、计算、填充应答
void add_callback(
    const std::shared_ptr<example_interfaces::srv::AddTwoInts::Request> request,
    std::shared_ptr<example_interfaces::srv::AddTwoInts::Response> response)
{
    // 业务逻辑:两数求和
    response->sum = request->a + request->b;
    // 日志打印请求与计算结果
    RCLCPP_INFO(
        rclcpp::get_logger("server_log"),
        "收到客户端请求:a = %ld , b = %ld | 计算结果 sum = %ld",
        request->a, request->b, response->sum
    );
}

int main(int argc, char ** argv)
{
    // 初始化ROS2客户端
    rclcpp::init(argc, argv);
    // 创建服务节点
    auto service_node = rclcpp::Node::make_shared("add_two_ints_server_node");
    // 创建服务,绑定回调函数,服务名称 /add_two_ints
    auto service_server = service_node->create_service<example_interfaces::srv::AddTwoInts>(
        "add_two_ints", add_callback
    );

    RCLCPP_INFO(service_node->get_logger(), "【服务端启动完成】等待客户端发送请求...");
    // 循环处理服务请求
    rclcpp::spin(service_node);
    // 关闭ROS2
    rclcpp::shutdown();
    return 0;
}
3.2.4 src/client.cpp 客户端代码

cpp

运行

#include "rclcpp/rclcpp.hpp"
#include "example_interfaces/srv/add_two_ints.hpp"
#include <chrono>
#include <cstdlib>
#include <memory>
using namespace std::chrono_literals;

int main(int argc, char ** argv)
{
    rclcpp::init(argc, argv);
    // 参数校验:客户端必须传入两个整数
    if (argc != 3)
    {
        RCLCPP_ERROR(
            rclcpp::get_logger("client_log"),
            "参数错误!使用格式:ros2 run cpp_srvcli client 数字1 数字2"
        );
        return 1;
    }
    // 读取命令行传入的两个数字
    int64_t num_a = atoll(argv[1]);
    int64_t num_b = atoll(argv[2]);

    // 创建客户端节点
    auto client_node = rclcpp::Node::make_shared("add_two_ints_client_node");
    // 创建客户端,绑定服务名称
    auto service_client = client_node->create_client<example_interfaces::srv::AddTwoInts>("add_two_ints");

    // 循环等待服务端上线,1秒轮询一次
    while (!service_client->wait_for_service(1s))
    {
        if (!rclcpp::ok())
        {
            RCLCPP_ERROR(rclcpp::get_logger("client_log"), "程序中断,等待服务失败,退出");
            return 0;
        }
        RCLCPP_INFO(rclcpp::get_logger("client_log"), "服务未就绪,持续等待中...");
    }

    // 构造请求数据包
    auto request = std::make_shared<example_interfaces::srv::AddTwoInts::Request>();
    request->a = num_a;
    request->b = num_b;

    // 异步发送请求,阻塞等待应答
    auto future_result = service_client->async_send_request(request);
    if (rclcpp::spin_until_future_complete(client_node, future_result) == rclcpp::FutureReturnCode::SUCCESS)
    {
        RCLCPP_INFO(
            rclcpp::get_logger("client_log"),
            "服务调用成功:%ld + %ld = %ld",
            num_a, num_b, future_result.get()->sum
        );
    }
    else
    {
        RCLCPP_ERROR(rclcpp::get_logger("client_log"), "服务调用失败,无应答返回");
    }

    rclcpp::shutdown();
    return 0;
}

四、兼容 Python 冲突的编译流程

进入工作空间根目录,一键清理 + 编译,规避 Python 多版本 CMake 报错:

bash

运行

cd ~/ws00_service
# 清理旧编译缓存
rm -rf build/ install/ log/
# 指定Python3路径编译(全局配置后可省略--cmake-args参数)
PATH="/usr/bin:$PATH" colcon build --cmake-args -DPython3_EXECUTABLE=/usr/bin/python3
# 刷新环境变量
source install/setup.bash

编译成功无报错代表工程构建完成。

五、分终端运行演示 & 完整模拟输出

5.1 终端 1:启动服务端

bash

运行

source install/setup.bash
ros2 run cpp_srvcli server

服务端标准输出模拟

plaintext

[INFO] [1783642100.123456] [add_two_ints_server_node]: 【服务端启动完成】等待客户端发送请求...
# 客户端发起请求后新增打印
[INFO] [1783642105.789012] [server_log]: 收到客户端请求:a = 10 , b = 25 | 计算结果 sum = 35

5.2 终端 2:启动客户端,传入参数 10 25

bash

运行

source install/setup.bash
ros2 run cpp_srvcli client 10 25

客户端标准输出模拟

plaintext

[INFO] [1783642105.780000] [client_log]: 服务调用成功:10 + 25 = 35

六、异常场景测试(拓展实验内容,提升博客含金量)

场景 1:先启动客户端,未运行服务端

客户端持续循环等待服务上线:

plaintext

[INFO] [1783642200.111111] [client_log]: 服务未就绪,持续等待中...
[INFO] [1783642201.112222] [client_log]: 服务未就绪,持续等待中...

场景 2:客户端参数数量不匹配

执行命令:

bash

运行

ros2 run cpp_srvcli client 10

报错输出:

plaintext

[ERROR] [1783642300.222222] [client_log]: 参数错误!使用格式:ros2 run cpp_srvcli client 数字1 数字2

七、ROS2 命令行工具调试服务(加分拓展内容)

无需运行客户端代码,直接使用命令行调用服务,快速验证服务可用性:

bash

运行

# 查看当前运行的所有服务
ros2 service list
# 查看服务接口类型
ros2 service type /add_two_ints
# 命令行手动发起服务请求 a=7 b=13
ros2 service call /add_two_ints example_interfaces/srv/AddTwoInts "{a: 7, b: 13}"

命令行调用返回模拟结果

plaintext

requester: making request: example_interfaces.srv.AddTwoInts_Request(a=7, b=13)

response:
example_interfaces.srv.AddTwoInts_Response(sum=20)

八、高频问题排错汇总(博客高分核心板块)

  1. colcon 编译 Python 版本冲突 解决:配置~/.colcon/defaults.yaml强制指定/usr/bin/python3,编译时前置 PATH 环境变量。
  2. ros2 run 找不到可执行文件 解决:每次新开终端执行 source install/setup.bash 刷新环境变量。
  3. 客户端一直等待服务无响应 解决:确认服务端正常启动、服务名称完全一致、无命名空间冲突。
  4. 源添加后 apt update 报错 解决:检查 ROS 密钥导入是否成功,删除源文件重新执行添加密钥脚本。
  5. 终端中文乱码 解决:执行开头 UTF-8 编码配置脚本,重启终端。

九、总结

  1. ROS2 服务通信采用同步请求应答模型,适合需要结果反馈的机器人交互场景;
  2. 本文完整覆盖 ROS2 Jazzy 环境安装、全局 Python 编译冲突解决方案、C++ 服务端客户端完整工程;
  3. 提供多场景运行模拟、命令行调试、故障排错,完整适配课程实验、入门学习、项目开发;
  4. 全部命令开箱即用,复制粘贴即可完成整套服务通信实验,无需额外修改。
Logo

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

更多推荐