ROS2 入门第二章

机器人

Posted by LXG on May 23, 2026

ROS2 教程

编写简单的服务和客户端

创建新的包


lxg@lxg:~/code/ROS2/workspace/demo/src$ ros2 pkg create --build-type ament_cmake cpp_srvcli --dependencies rclcpp example_interfaces
going to create a new package
package name: cpp_srvcli
destination directory: /home/lxg/code/ROS2/workspace/demo/src
package format: 3
version: 0.0.0
description: TODO: Package description
maintainer: ['lxg <xiaogang.li>']
licenses: ['TODO: License declaration']
build type: ament_cmake
dependencies: ['rclcpp', 'example_interfaces']
creating folder ./cpp_srvcli
creating ./cpp_srvcli/package.xml
creating source and include folder
creating folder ./cpp_srvcli/src
creating folder ./cpp_srvcli/include/cpp_srvcli
creating ./cpp_srvcli/CMakeLists.txt

lxg@lxg:~/code/ROS2/workspace/demo/src$ ls /opt/ros/humble/include/example_interfaces/example_interfaces/srv/
add_two_ints.h  add_two_ints.hpp  detail  set_bool.h  set_bool.hpp  trigger.h  trigger.hpp

编写服务端代码


#include "rclcpp/rclcpp.hpp"
#include "example_interfaces/srv/add_two_ints.hpp"

#include <memory>

// 文件说明:
// 这是一个最小的 ROS2 服务端示例,提供名为 `add_two_ints` 的服务。
// 服务类型为 `example_interfaces::srv::AddTwoInts`,接收两个整数 `a` 和 `b`,
// 返回它们的和 `sum`。示例展示了如何定义服务回调、创建服务并进入事件循环。

// 服务回调函数:处理来自客户端的请求并填充响应
// 参数:
// - request: 指向请求消息的共享指针,包含请求字段 `a` 和 `b`(要相加的两个整数)
// - response: 指向响应消息的共享指针,回调应设置 `response->sum` 字段
void add(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;

  // 打印接收到的请求到日志,便于调试
  // 注意:使用 `%ld` 打印 long int,取决于平台上整数类型的大小
  RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "Incoming request\na: %ld" " b: %ld",
              request->a, request->b);

  // 打印返回的响应值
  RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "sending back response: [%ld]", (long int)response->sum);
}

int main(int argc, char **argv)
{
  // 初始化 ROS 2 客户端库(必须在程序开始时调用)
  rclcpp::init(argc, argv);

  // 创建一个名为 "add_two_ints_server" 的节点实例
  // `make_shared` 返回节点的共享指针,便于与 `spin` 一起使用
  std::shared_ptr<rclcpp::Node> node = rclcpp::Node::make_shared("add_two_ints_server");

  // 创建服务:
  // - 模板参数:服务的类型 `example_interfaces::srv::AddTwoInts`
  // - 第一个参数:服务名 "add_two_ints",客户端将使用该名称进行调用
  // - 第二个参数:回调函数指针 `&add`,用于处理每次到来的请求
  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);

  // 清理并关闭 ROS 2 客户端库
  rclcpp::shutdown();
}

写入客户端节点


#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);
  // 参数检查:期望两个整数参数 X 和 Y
  if (argc != 3) {
    RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "usage: add_two_ints_client X Y");
    return 1;
  }

  // 创建客户端节点
  // 节点名为 "add_two_ints_client"
  std::shared_ptr<rclcpp::Node> node = rclcpp::Node::make_shared("add_two_ints_client");

  // 创建服务客户端:
  // - 模板参数为服务类型 `example_interfaces::srv::AddTwoInts`
  // - 参数为服务名 "add_two_ints",应与服务端提供的名称一致
  rclcpp::Client<example_interfaces::srv::AddTwoInts>::SharedPtr client =
    node->create_client<example_interfaces::srv::AddTwoInts>("add_two_ints");

  // 构造请求消息并填充请求字段 a 和 b
  auto request = std::make_shared<example_interfaces::srv::AddTwoInts::Request>();
  // 将命令行参数转换为 long(使用 atoll)并赋值
  request->a = atoll(argv[1]);
  request->b = atoll(argv[2]);

  // 等待服务可用,超时为 1 秒,每次超时后会再次检查
  // 如果用户按下 Ctrl+C 或 ROS 被中断(rclcpp::ok() 变为 false),程序将退出
  while (!client->wait_for_service(1s)) {
    if (!rclcpp::ok()) {
      RCLCPP_ERROR(rclcpp::get_logger("rclcpp"), "Interrupted while waiting for the service. Exiting.");
      return 0;
    }
    RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "service not available, waiting again...");
  }

  // 异步发送请求,返回一个 future 对象用于获取响应
  auto result = client->async_send_request(request);

  // 等待 future 完成(即等待服务端响应)并处理结果
  if (rclcpp::spin_until_future_complete(node, result) ==
    rclcpp::FutureReturnCode::SUCCESS)
  {
    // 如果成功,打印响应中的 sum 字段
    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");
  }

  // 关闭 ROS 2 客户端库并退出
  rclcpp::shutdown();
  return 0;
}

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 dependencies
find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(example_interfaces REQUIRED)

if(BUILD_TESTING)
  find_package(ament_lint_auto REQUIRED)
  # the following line skips the linter which checks for copyrights
  # comment the line when a copyright and license is added to all source files
  set(ament_cmake_copyright_FOUND TRUE)
  # the following line skips cpplint (only works in a git repo)
  # comment the line when this package is in a git repo and when
  # a copyright and license is added to all source files
  set(ament_cmake_cpplint_FOUND TRUE)
  ament_lint_auto_find_test_dependencies()
endif()

add_executable(server src/add_two_ints_server.cpp)
ament_target_dependencies(server
rclcpp example_interfaces)

add_executable(client src/add_two_ints_client.cpp)
ament_target_dependencies(client
  rclcpp example_interfaces)

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

ament_package()

编译


lxg@lxg:~/code/ROS2/workspace/demo$ colcon build --packages-select cpp_srvcli
Starting >>> cpp_srvcli
Finished <<< cpp_srvcli [3.22s]                     

Summary: 1 package finished [3.43s]

运行


# 服务端
lxg@lxg:~/code/ROS2/workspace/demo$ ros2 run cpp_srvcli server
[INFO] [1779517707.444431515] [rclcpp]: Ready to add two ints.
[INFO] [1779517722.915908316] [rclcpp]: Incoming request
a: 2 b: 3
[INFO] [1779517722.915932832] [rclcpp]: sending back response: [5]

# 客户端
lxg@lxg:~/code/ROS2/workspace/demo$ ros2 run cpp_srvcli client 2 3
[INFO] [1779517722.916145334] [rclcpp]: Sum: 5

创建自定义ROS2 msg 和 srv 文件

创建新包tutorial_interfaces


lxg@lxg:~/code/ROS2/workspace/demo/src$ ros2 pkg create --build-type ament_cmake tutorial_interfaces
going to create a new package
package name: tutorial_interfaces
destination directory: /home/lxg/code/ROS2/workspace/demo/src
package format: 3
version: 0.0.0
description: TODO: Package description
maintainer: ['lxg <xiaogang.li>']
licenses: ['TODO: License declaration']
build type: ament_cmake
dependencies: []
creating folder ./tutorial_interfaces
creating ./tutorial_interfaces/package.xml
creating source and include folder
creating folder ./tutorial_interfaces/src
creating folder ./tutorial_interfaces/include/tutorial_interfaces
creating ./tutorial_interfaces/CMakeLists.txt

编写msg 和 srv


lxg@lxg:~/code/ROS2/workspace/demo/src/tutorial_interfaces$ tree  -L 2
.
├── build
│   ├── COLCON_IGNORE
│   └── tutorial_interfaces
├── CMakeLists.txt
├── include
│   └── tutorial_interfaces
├── install
│   ├── COLCON_IGNORE
│   ├── local_setup.bash
│   ├── local_setup.ps1
│   ├── local_setup.sh
│   ├── _local_setup_util_ps1.py
│   ├── _local_setup_util_sh.py
│   ├── local_setup.zsh
│   ├── setup.bash
│   ├── setup.ps1
│   ├── setup.sh
│   ├── setup.zsh
│   └── tutorial_interfaces
├── log
│   ├── build_2026-05-23_14-39-29
│   ├── COLCON_IGNORE
│   ├── latest -> latest_build
│   └── latest_build -> build_2026-05-23_14-39-29
├── msg
│   └── Num.msg
├── package.xml
├── src
└── srv
    └── AddThreeInts.srv

13 directories, 17 files

编译自动生成的num.hpp

./build/tutorial_interfaces/rosidl_generator_cpp/tutorial_interfaces/msg/num.hpp

./install/tutorial_interfaces/include/tutorial_interfaces/tutorial_interfaces/msg/num.hpp


// generated from rosidl_generator_cpp/resource/idl.hpp.em
// generated code does not contain a copyright notice

#ifndef TUTORIAL_INTERFACES__MSG__NUM_HPP_
#define TUTORIAL_INTERFACES__MSG__NUM_HPP_

#include "tutorial_interfaces/msg/detail/num__struct.hpp"
#include "tutorial_interfaces/msg/detail/num__builder.hpp"
#include "tutorial_interfaces/msg/detail/num__traits.hpp"
#include "tutorial_interfaces/msg/detail/num__type_support.hpp"

#endif  // TUTORIAL_INTERFACES__MSG__NUM_HPP_

lxg@lxg:~/code/ROS2/workspace/demo/src/tutorial_interfaces$ find . -name “add_three_ints.hpp”

./build/tutorial_interfaces/rosidl_generator_cpp/tutorial_interfaces/srv/add_three_ints.hpp

./install/tutorial_interfaces/include/tutorial_interfaces/tutorial_interfaces/srv/add_three_ints.hpp


// generated from rosidl_generator_cpp/resource/idl.hpp.em
// generated code does not contain a copyright notice

#ifndef TUTORIAL_INTERFACES__SRV__ADD_THREE_INTS_HPP_
#define TUTORIAL_INTERFACES__SRV__ADD_THREE_INTS_HPP_

#include "tutorial_interfaces/srv/detail/add_three_ints__struct.hpp"
#include "tutorial_interfaces/srv/detail/add_three_ints__builder.hpp"
#include "tutorial_interfaces/srv/detail/add_three_ints__traits.hpp"
#include "tutorial_interfaces/srv/detail/add_three_ints__type_support.hpp"

#endif  // TUTORIAL_INTERFACES__SRV__ADD_THREE_INTS_HPP_

在类中使用参数

创建cpp_parameters包


lxg@lxg:~/code/ROS2/workspace/demo$ ros2 pkg create --build-type ament_cmake cpp_parameters --dependencies rclcpp

本章内容


创建节点类
    ↓
声明参数
    ↓
读取参数
    ↓
使用参数
    ↓
通过命令行修改参数
    ↓
通过 launch/YAML 配置参数

开始使用ros2 doctor

快速诊断ROS2系统中的问题


ros2 doctor

ros2 doctor --report

创建和使用插件

pluginlib是一个c + + 库,用于从ROS包中加载和卸载插件。插件是从运行时库加载的动态调用y可加载类 (即共享对象,动态调用y链接库)。使用pluginlib,不必将其应用程序与包含类的库显式链接-相反,pluginlib可以在任何时候打开包含导出类的库,而无需应用程序事先了解该库或包含类定义的头文件。插件可用于扩展/修改应用程序行为,而无需应用程序源文件。

模块 插件类型
Nav2 Planner
Nav2 Controller
Nav2 Recovery
MoveIt2 Planning Plugin
RViz Display Plugin
Gazebo Sensor Plugin
RTAB-Map Backend Plugin

创建基类包


lxg@lxg:~/code/ROS2/workspace/demo/src$ ros2 pkg create --build-type ament_cmake polygon_base --dependencies pluginlib --node-name area_node
going to create a new package
package name: polygon_base
destination directory: /home/lxg/code/ROS2/workspace/demo/src
package format: 3
version: 0.0.0
description: TODO: Package description
maintainer: ['lxg <xiaogang.li>']
licenses: ['TODO: License declaration']
build type: ament_cmake
dependencies: ['pluginlib']
node_name: area_node
creating folder ./polygon_base
creating ./polygon_base/package.xml
creating source and include folder
creating folder ./polygon_base/src
creating folder ./polygon_base/include/polygon_base
creating ./polygon_base/CMakeLists.txt
creating ./polygon_base/src/area_node.cpp

创建插件包


lxg@lxg:~/code/ROS2/workspace/demo/src$ ros2 pkg create --build-type ament_cmake polygon_plugins --dependencies polygon_base pluginlib --library-name polygon_plugins
going to create a new package
package name: polygon_plugins
destination directory: /home/lxg/code/ROS2/workspace/demo/src
package format: 3
version: 0.0.0
description: TODO: Package description
maintainer: ['lxg <xiaogang.li>']
licenses: ['TODO: License declaration']
build type: ament_cmake
dependencies: ['polygon_base', 'pluginlib']
library_name: polygon_plugins
creating folder ./polygon_plugins
creating ./polygon_plugins/package.xml
creating source and include folder
creating folder ./polygon_plugins/src
creating folder ./polygon_plugins/include/polygon_plugins
creating ./polygon_plugins/CMakeLists.txt
creating ./polygon_plugins/include/polygon_plugins/polygon_plugins.hpp
creating ./polygon_plugins/src/polygon_plugins.cpp
creating ./polygon_plugins/include/polygon_plugins/visibility_control.h

一个真实无人车架构


                 ROS2

                 Nav2
                    │
     ┌──────────────┼──────────────┐
     │              │              │
Planner Plugin  Controller Plugin Recovery Plugin
     │              │              │
  A*算法         PurePursuit      Spin
  HybridA*       MPC              Backup
  Smac           TEB              Custom

                    │
               RTAB-Map
                    │
      ┌─────────────┼─────────────┐
      │             │             │
   Camera         IMU          LiDAR
      │             │             │
   驱动插件       驱动插件      驱动插件

所以在无人车开发里,插件最常见的地方是 Nav2(规划/控制)、RViz(显示)、Gazebo(仿真)和 RTAB-Map(SLAM 后端)。如果你的目标是把双目摄像头、IMU、RTAB-Map 跑起来,短期内最先会接触到的是 RViz 插件和 Nav2 插件配置,而不是自己开发插件。等你开始改 Nav2 算法或者写新的传感器模块时,才会真正需要编写 Pluginlib 插件。

动作

ROS2 有三种主要通信方式:

特性 Topic Service Action
通信模式 发布-订阅 请求-应答 请求-反馈-结果
阻塞性 异步 同步 异步
中间反馈 ✅ 流式 ❌ 无 ✅ 实时反馈
任务取消 ❌ 无 ❌ 无 ✅ 支持
执行时间 任意 短暂 长时间
用途 数据流 简单请求 长时间任务

Topic


激光雷达
  ↓
PointCloud2

持续发布

Service


请求:
获取地图

响应:
地图数据

立即返回

Action


请求:
导航到目标点

执行:
机器人开始走

反馈:
当前走到哪里了

结果:
到达目标点

Action 三段式通信


Client                              Server
  │                                   │
  ├─────── 发送 Goal ───────────────→ │
  │                                   │ (开始执行)
  │ ← ─ ─ 实时 Feedback ─ ─ ─ ─ ─ ← │
  │ ← ─ ─ 实时 Feedback ─ ─ ─ ─ ─ ← │
  │ ← ─ ─ 实时 Feedback ─ ─ ─ ─ ─ ← │
  │                                   │
  │ ← ─ ─ ─ ─ Result ─ ─ ─ ─ ─ ─ ─ ← │
  │                                   │
  或者
  ├───────── 取消请求 ─────────────→ │

在单个进程中组合多个节点

如何把多个 ROS2 Node 放进同一个进程运行,而不是每个节点一个进程。

Component Container │ ├── NodeA ├── NodeB └── NodeC

三个节点共享同一个进程。


lxg@lxg:~/code/ROS2/workspace/demo$ ros2 component types | grep compos
composition
  composition::Talker
  composition::Listener
  composition::NodeLikeListener
  composition::Server
  composition::Client

运行时组合使用ROS服务 (1.) 与发布者和订阅者

启动一个容器


source /opt/ros/humble/setup.bash

ros2 run rclcpp_components component_container

加载发布者


lxg@lxg:~$ source /opt/ros/humble/setup.bash
lxg@lxg:~$ ros2 component list
/ComponentManager
lxg@lxg:~$ ros2 component load /ComponentManager composition composition::Talker
Loaded component 1 into '/ComponentManager' container node as '/talker'

加载订阅者


ros2 component load /ComponentManager composition composition::Listener

同一个进程内的显示结果


[INFO] [1781762914.661119732] [listener]: I heard: [Hello World: 202]
[INFO] [1781762915.660997072] [talker]: Publishing: 'Hello World: 203'
[INFO] [1781762915.661185434] [listener]: I heard: [Hello World: 203]
[INFO] [1781762916.661082024] [talker]: Publishing: 'Hello World: 204'
[INFO] [1781762916.661243212] [listener]: I heard: [Hello World: 204]
[INFO] [1781762917.661267446] [talker]: Publishing: 'Hello World: 205'
[INFO] [1781762917.661421941] [listener]: I heard: [Hello World: 205]
[INFO] [1781762918.661299597] [talker]: Publishing: 'Hello World: 206'
[INFO] [1781762918.661472659] [listener]: I heard: [Hello World: 206]
[INFO] [1781762919.661405667] [talker]: Publishing: 'Hello World: 207'
[INFO] [1781762919.661585321] [listener]: I heard: [Hello World: 207]
[INFO] [1781762920.661494024] [talker]: Publishing: 'Hello World: 208'
[INFO] [1781762920.661680472] [listener]: I heard: [Hello World: 208]
[INFO] [1781762921.661590770] [talker]: Publishing: 'Hello World: 209'
[INFO] [1781762921.661790133] [listener]: I heard: [Hello World: 209]
[INFO] [1781762922.661692758] [talker]: Publishing: 'Hello World: 210'
[INFO] [1781762922.661867814] [listener]: I heard: [Hello World: 210]
[INFO] [1781762923.661782706] [talker]: Publishing: 'Hello World: 211'
[INFO] [1781762923.661945427] [listener]: I heard: [Hello World: 211]

卸载组件


lxg@lxg:~$ ros2 component unload /ComponentManager 1 2
Unloaded component 1 from '/ComponentManager' container node
Unloaded component 2 from '/ComponentManager' container node