ZeroOne AI
← 返回文章列表

手把手做ROS2机器人系统开发(五):C++实现服务器与客户端

👁 38
分类:机器人自动化

上一讲完成了发布者/订阅者程序,掌握了话题通信模型。本章节继续讲解 ROS2 的第二种核心通信方式——服务通信(Service),即"请求/响应"模型。该模型适用于需要实时返回结果的任务,例如:机器人运动指令下发、参数查询、业务逻辑调用等。本章以"两个整数相加"服务为例,编写一个标准的服务器/客户端

手把手做ROS2机器人系统开发(五):C++实现服务器与客户端

教程背景

上一讲完成了发布者/订阅者程序,掌握了话题通信模型。本章节继续讲解 ROS2 的第二种核心通信方式——服务通信(Service),即"请求/响应"模型。该模型适用于需要实时返回结果的任务,例如:机器人运动指令下发、参数查询、业务逻辑调用等。本章以"两个整数相加"服务为例,编写一个标准的服务器/客户端程序模板。

一、程序编写

1. 创建软件包

继续将软件包创建在 src 目录下,方便统一存放和管理:

ros2 pkg create --build-type ament_cmake cpp_srvcli --dependencies rclcpp example_interfaces

2. 编译软件包

回到工作空间根目录进行编译,注意路径:

colcon build --packages-select cpp_srvcli

3. 软件配置

编程环境配置(VS Code 头文件目录等)参考本系列第三、四讲:

4. 服务器程序编写

代码路径:src/cpp_srvcli/src/add_two_ints_server.cpp

实现两个数相加的服务端:

/**
 * @file add_two_ints_server.cpp
 * @author gmotion (motion_gui@126.com)
 * @brief 发布服务器节点
 * @version 0.1
 * @date 2022-06-10
 * @copyright Copyright (c) 2022
 */

#include <memory>       //内存管理
#include <string>       //字符串

#include <rclcpp/rclcpp.hpp>
//这里使用示例中的接口,后续章节会重点讲解如何自定义专用接口
#include <example_interfaces/srv/add_two_ints.hpp>

//添加服务信息
void add(const std::shared_ptr<example_interfaces::srv::AddTwoInts::Request>reques,
        std::shared_ptr<example_interfaces::srv::AddTwoInts::Response>response)
{
    response->sum = reques->a + reques->b;
    RCLCPP_INFO(rclcpp::get_logger("rclcpp"),"Incoming request\n a:%ld " "b: %ld",
    reques->a,reques->b);
    RCLCPP_INFO(rclcpp::get_logger("rclcpp"),"sending back response: [%ld] ",
    (long int)response->sum);
}

//主运行程序
int main(int argc,char * argv[])
{
    rclcpp::init(argc,argv);
    //创建服务名称节点
    std::shared_ptr<rclcpp::Node>node = rclcpp::Node::make_shared("add_two_ints_server");
    //创建服务函数
    rclcpp::Service<example_interfaces::srv::AddTwoInts>::SharedPtr service=
    node->create_service<example_interfaces::srv::AddTwoInts>("add_two_ints",&add);

    RCLCPP_INFO(rclcpp::get_logger("rclcpp"),"Ready to add two ints.");
    rclcpp::spin(node);
    rclcpp::shutdown();
    return 0;
}

5. 客户端程序编写

代码路径:src/cpp_srvcli/src/add_two_ints_client.cpp

客户端向服务器发起请求并打印计算结果:

/**
 * @file add_two_ints_client.cpp
 * @author gmotion (motion_gui@126.com)
 * @brief 客户端-请求服务器计算
 * @version 0.1
 * @date 2022-06-10
 * @copyright Copyright (c) 2022
 */

#include <chrono>       //处理时间类
#include <cstdlib>      //标准函数库
#include <memory>       //内存管理

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

using namespace std::chrono_literals;

//主程序函数
int main(int argc,char * argv[])
{
    rclcpp::init(argc,argv);

    //参数数量发生错误
    //传入两个计算对应的数值-用于请求服务器及计算
    if(argc !=3)
    {
        RCLCPP_INFO(rclcpp::get_logger("rclcpp"),"usage: add_two_ints_client X Y");
        return 1;
    }

    //建立node节点
    std::shared_ptr<rclcpp::Node>node = rclcpp::Node::make_shared("add_two_ints_client");

    //建立客户端
    rclcpp::Client<example_interfaces::srv::AddTwoInts>::SharedPtr client =
    node->create_client<example_interfaces::srv::AddTwoInts>("add_two_ints");

    //建立请求发送参数
    auto request = std::make_shared<example_interfaces::srv::AddTwoInts::Request>();
    request->a = atoll(argv[1]);
    request->b = atoll(argv[2]);

    //等待服务
    while (!client->wait_for_service(1s))
    {
        if (!rclcpp::ok())
        {
            RCLCPP_INFO(rclcpp::get_logger("rclcpp"),"service no avaiable,wait again...");
        }
    }

    //发送运行请求
    auto result = client->async_send_request(request);
    //等待运行结果
    if(rclcpp::spin_until_future_complete(node,result) == rclcpp::FutureReturnCode::SUCCESS)
    {
        RCLCPP_INFO(rclcpp::get_logger("rclcpp"),"Sum: %ld",result.get()->sum);
    }
    else
    {
        RCLCPP_ERROR(rclcpp::get_logger("rclcpp"),"Failed to call service add_two_ints");
    }

    rclcpp::shutdown();
    return 0;
}

6. 软件包设置

编辑 src/cpp_srvcli/package.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>C++ client server tutorial</description>
  <maintainer email="motion_gui@126.com">gmotion</maintainer>
  <license>Apache License 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>

7. 设置编译选项

编辑 src/cpp_srvcli/CMakeLists.txt:

cmake_minimum_required(VERSION 3.8)
project(cpp_pubsub)

# Default to C99
if(NOT CMAKE_C_STANDARD)
  set(CMAKE_C_STANDARD 99)
endif()

# Default to C++14
if(NOT CMAKE_CXX_STANDARD)
  set(CMAKE_CXX_STANDARD 14)
endif()

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

# find dependencies
find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(std_msgs REQUIRED)

add_executable(talker src/publisher_member_function.cpp)
ament_target_dependencies(talker rclcpp std_msgs)

add_executable(listener src/subscriber_member_function.cpp)
ament_target_dependencies(listener rclcpp std_msgs)

install(TARGETS
  talker
  listener
  DESTINATION lib/${PROJECT_NAME})

if(BUILD_TESTING)
  find_package(ament_lint_auto REQUIRED)
  set(ament_cmake_copyright_FOUND TRUE)
  set(ament_cmake_cpplint_FOUND TRUE)
  ament_lint_auto_find_test_dependencies()
endif()

ament_package()

提示:本示例直接沿用上讲的 CMakeLists 模板,重点在于理解服务通信逻辑。实际项目中请根据 src/cpp_srvcli/src/ 下的文件正确配置 add_executable。

二、程序测试

1. 编译程序

指定单一软件包编译:

colcon build --packages-select cpp_srvcli

2. 开启节点测试运行

服务器终端:

. install/setup.bash
ros2 run cpp_srvcli server

客户端终端(传入 12 和 10 两个参数):

. install/setup.bash
ros2 run cpp_srvcli client 12 10

3. 执行效果展示

客户端输出 Sum: 22,服务器端打印收到请求与返回结果,程序准确执行,达到预期结果。

小结

本章节实现了 ROS2 服务通信的完整示例:服务器负责计算响应,客户端发起请求并等待结果。服务通信与话题通信共同构成 ROS2 应用开发的两大基石。下一章节讲解如何创建用户自定义的 msg 和 srv 接口文件。

SEO关键词: ROS2 C++服务通信, ROS2 Service教程, rclcpp服务器客户端, ROS2请求响应模型, ROS2功能包开发

推荐阅读

更多工业 AI、智能制造与技术实践文章

机器人自动化

TRIO-Basic 从入门到精通(十三):Trio 与三菱 Q 实现 MC 协议通讯

本教程讲解Trio控制器作为主站、三菱Q系列PLC作为从站的MC协议以太网通讯,手工构造MC协议报文帧对D寄存器进行批量读写,支持精确超时、断线重连,为运动控制器与PLC协同控制提供完整范例。

M3 Max 96GB 本地部署 Qwen3.6-35B-A3B:打造 SolidWorks 智能机械设计与企业知识库 AI 工作站

M3 Max 96GB 统一内存适合同时运行本地大模型与设计工具链。本文推荐 MoE 架构的 Qwen3.6-35B-A3B(总参数约 35B、单 Token 激活约 3B)配合 MLX 4bit 量化本地部署,打造 SolidWorks 智能机械设计与企业知识库 AI 工作站:mlx-lm 提供 OpenAI 兼容 API,OpenWebUI 做对话入口,通过 SolidWorks MCP 工具调用实现参数化建模,结合 BGE-M3 + Reranker 构建企业知识库 RAG,并规划多模型架构与“小龙虾”本地 AI Agent 平台的演进路线。

工业互联网

Linux系统篇(二十二)——文件(六):目标文件与 ELF 深度解析:从编译到加载的全景揭秘

从目标文件出发,逐层解剖ELF头部、节头表与程序头表,讲透静态链接重定位、动态链接GOT/PLT以及程序从编译到加载的完整过程。

工业互联网

Linux系统篇(十九)——文件(三):底层存储入门:从机房硬件到 CHS/LBA 磁盘寻址全解析

从机房硬件层级讲到磁盘物理结构,再到 CHS 三维寻址与 LBA 线性寻址的转换原理,完整打通底层存储认知。

工业AI

Conda环境下测试Intel NPU:Python版本如何选择?

在 Intel Core Ultra + OpenVINO + YOLO 工业视觉项目中,Conda 环境测试 Intel NPU 时 Python 版本如何选择?本文结合实际开发经验,推荐 Python 3.10 作为 NPU 测试环境,并给出从环境搭建、设备识别到 NPU 模型编译、性能测试的分层验证流程。

通讯协议

深入理解TCP协议(二):TCP可靠传输与高效通信机制详解

本文深入TCP的七大核心工作机制:确认应答、超时重传、滑动窗口、流量控制、拥塞控制、延迟应答与捎带应答。详解丢包的本质与去重、超时时间如何确定、滑动窗口的发送流程与空间复用、快速重传、零窗口探测、窗口扩大因子、慢启动与拥塞避免,以及各机制如何协同实现TCP既可靠又高效的传输,为理解三次握手与四次挥手打下基础。

评论(0)