编写简单的服务和客户端
创建新的包
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