3. ROS2服务通信基础

任务目标

本案例将创建两个ROS2节点:一个服务端节点,提供整数加法服务;一个客户端节点,向服务端发送两个整数并接收求和结果。通过此案例掌握ROS2服务通信的完整流程:服务接口定义、服务端实现、客户端调用以及编译运行。

环境依赖

  • 已安装ROS2(Humble或更高版本),并完成环境配置(source /opt/ros/humble/setup.bash)
  • 已创建ROS2工作空间(如~/ros2_ws/src)

实现步骤

1. 创建功能包并定义服务接口

在终端执行以下命令创建功能包和自定义服务接口(使用example_interfaces/AddTwoInts也可,但为展示自定义接口流程,此处新建):

cd ~/ros2_ws/src
ros2 pkg create --build-type ament_cmake service_demo
mkdir -p service_demo/srv

创建service_demo/srv/AddTwoInts.srv文件,内容如下:

int64 a
int64 b
---
int64 sum

编辑service_demo/CMakeLists.txt,在find_package部分添加rosidl_default_generators,并在文件末尾添加:

rosidl_generate_interfaces(${PROJECT_NAME}
  "srv/AddTwoInts.srv"
)

编辑service_demo/package.xml,添加以下依赖:

<build_depend>rosidl_default_generators</build_depend>
<exec_depend>rosidl_default_runtime</exec_depend>
<member_of_group>rosidl_interface_packages</member_of_group>

2. 服务端节点代码

在service_demo/src/目录下创建server.cpp:

#include <rclcpp/rclcpp.hpp>
#include <service_demo/srv/add_two_ints.hpp>

using AddTwoInts = service_demo::srv::AddTwoInts;

int main(int argc, char** argv) {
    rclcpp::init(argc, argv);
    auto node = std::make_shared<rclcpp::Node>("add_two_ints_server");
    
    auto server = node->create_service<AddTwoInts>(
        "add_two_ints",
        [](const std::shared_ptr<AddTwoInts::Request> req,
           std::shared_ptr<AddTwoInts::Response> res) {
            res->sum = req->a + req->b;
            RCLCPP_INFO(rclcpp::get_logger("server"), 
                       "Received: %ld + %ld = %ld", req->a, req->b, res->sum);
        });
    
    RCLCPP_INFO(node->get_logger(), "Service server ready.");
    rclcpp::spin(node);
    rclcpp::shutdown();
    return 0;
}

3. 客户端节点代码

在service_demo/src/目录下创建client.cpp:

#include <rclcpp/rclcpp.hpp>
#include <service_demo/srv/add_two_ints.hpp>
#include <chrono>

using AddTwoInts = service_demo::srv::AddTwoInts;
using namespace std::chrono_literals;

int main(int argc, char** argv) {
    rclcpp::init(argc, argv);
    auto node = std::make_shared<rclcpp::Node>("add_two_ints_client");
    
    auto client = node->create_client<AddTwoInts>("add_two_ints");
    
    // 等待服务上线
    while (!client->wait_for_service(1s)) {
        if (!rclcpp::ok()) {
            RCLCPP_ERROR(node->get_logger(), "Interrupted while waiting for service.");
            return 1;
        }
        RCLCPP_INFO(node->get_logger(), "Waiting for service...");
    }
    
    // 构造请求
    auto request = std::make_shared<AddTwoInts::Request>();
    request->a = 15;
    request->b = 27;
    
    // 发送请求并等待响应
    auto future = client->async_send_request(request);
    if (rclcpp::spin_until_future_complete(node, future) == 
        rclcpp::FutureReturnCode::SUCCESS) {
        auto response = future.get();
        RCLCPP_INFO(node->get_logger(), "Result: %ld + %ld = %ld",
                   request->a, request->b, response->sum);
    } else {
        RCLCPP_ERROR(node->get_logger(), "Failed to call service.");
        return 1;
    }
    
    rclcpp::shutdown();
    return 0;
}

4. 编译配置

编辑service_demo/CMakeLists.txt,在rosidl_generate_interfaces之后添加:

add_executable(server src/server.cpp)
ament_target_dependencies(server rclcpp)
target_include_directories(server PUBLIC
  $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
  $<INSTALL_INTERFACE:include>)

add_executable(client src/client.cpp)
ament_target_dependencies(client rclcpp)
target_include_directories(client PUBLIC
  $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
  $<INSTALL_INTERFACE:include>)

install(TARGETS server client
  DESTINATION lib/${PROJECT_NAME})

编译与运行

cd ~/ros2_ws
colcon build --packages-select service_demo
source install/setup.bash

# 终端1:运行服务端
ros2 run service_demo server

# 终端2:运行客户端
ros2 run service_demo client

验证方法

在客户端终端观察输出:

[INFO] Result: 15 + 27 = 42

服务端终端应显示:

[INFO] Received: 15 + 27 = 42

同时可在第三个终端使用ros2 service list查看服务列表,应包含/add_two_ints;使用ros2 service type /add_two_ints应返回service_demo/srv/AddTwoInts。

常见问题

  • 找不到服务接口:确认已执行source install/setup.bash,且功能包编译成功。
  • 客户端等待超时:确认服务端已启动且服务名称一致(均为add_two_ints)。
  • 编译错误:检查CMakeLists.txt中rosidl_generate_interfaces是否在add_executable之前调用。