目录
前言
一、本章核心知识点
1.1 ROS2 工作空间是什么
1.2 ROS2 功能包是什么
1.3 msg、srv、action 分别用来做什么
二、ROS2 工作空间结构
2.1 src、build、install、log 四个目录
2.2 src 源码空间
(1)工作空间中的 src
(2)功能包内部的 src
2.3 build 编译空间
(1)build 的作用
(2)什么时候需要清理 build
2.4 install 安装空间
(1)install 是运行结果所在目录
(2)src、build、install 的关系
2.5 log 日志空间
2.6 为什么修改代码后需要重新编译
(1)C++ 代码不是解释执行
(2)正确编译运行流程
三、ROS2 功能包概念
3.1 什么是功能包
功能包是 ROS2 中组织代码的基本单位。
3.2 功能包和普通文件夹有什么区别
(1)普通文件夹没有 ROS2 工程属性
(2)C++ 功能包基本结构
3.3 C++ 功能包和 Python 功能包的区别
(1)两种常见构建类型
(2)创建 C++ 功能包
(3)创建 Python 功能包
3.4 功能包内部常见目录结构及作用
(1)完整功能包结构
(2)普通 C++ 节点功能包
(3)自定义接口功能包
四、ROS2 自定义接口 msg、srv、action
4.1 为什么需要自定义接口
(1)ROS2 已经提供了很多标准消息
(2)标准消息不够用时就需要自定义接口
4.2 msg 消息文件是什么
(1)msg 用于 Topic 话题通信
(2)msg 文件放在哪里
(3)LimoStatus.msg 示例
(4)std_msgs/Header header 是什么意思
(5)发布消息时如何赋值
(6)msg 文件注意事项
4.3 srv 服务文件是什么
(1)srv 用于 Service 服务通信
(2)srv 文件放在哪里
(3)LimoSrv.srv 示例
(4)Service 适合什么场景
4.4 action 动作文件是什么
(1)action 适合长时间任务
(2)action 文件放在哪里
(3)LimoAction.action 示例
(4)Action 的特点
4.5 msg、srv、action 对比总结
五、本章总结
5.1 ROS2 工作空间通常包含四个核心目录:
5.2 功能包是 ROS2 组织代码的基本单位。
5.3 ROS2 中常见的自定义接口有三种:
前言
前面几篇文章已经分别讲解了 ROS2 常见消息类型、话题通信基础以及常用调试命令。到这里,已经可以初步理解 ROS2 中节点、话题、消息之间的关系。
但是,真正开始写 ROS2 工程代码时,只理解节点和话题还不够。因为 ROS2 并不是简单写一个 .cpp 文件就能直接运行,而是需要放在特定的工程结构中,通过工作空间、功能包、编译目录、安装目录以及接口文件共同组织起来。
很多初学者刚接触 ROS2 工程时,最容易疑惑的问题就是:
本文就围绕这些问题,系统讲解 ROS2 工程结构基础,重点包括:
本文的重点是先把 ROS2 工程结构讲清楚,让大家知道代码应该放在哪里、功能包如何组织、接口文件如何理解。
至于 ROS2 C++ 功能包中非常重要的两个配置文件:package.xml 和 CMakeLists.txt,我已经在下一篇文章中单独详细讲解:
ROS2 功能包配置文件详解:package.xml 与 CMakeLists.txt 一文搞懂-CSDN博客
https://blog.csdn.net/m0_58954356/article/details/161804636?spm=1001.2014.3001.5501除此之外ROS2其他相关学习博客链接如下:
ROS2 常见消息类型保姆级教程-CSDN博客
https://blog.csdn.net/m0_58954356/article/details/161693378?spm=1001.2014.3001.5501ROS2 话题通信实战:消息对象、Publisher 发布器与 Subscriber 订阅器保姆级教程-CSDN博客
https://blog.csdn.net/m0_58954356/article/details/161702288?spm=1001.2014.3001.5501
史上最全 ROS2 常用命令保姆级教程:节点、话题、消息结构、参数、服务和动作一篇讲透-CSDN博客
https://blog.csdn.net/m0_58954356/article/details/161726502?spm=1001.2014.3001.5501
一、本章核心知识点
1.1 ROS2 工作空间是什么

工作空间可以理解成 ROS2 项目的总文件夹。它用来统一存放源代码、编译中间文件、安装后的可执行文件以及日志文件。
一个典型 ROS2 工作空间一般长这样:
agilex_open_class_ws/
├── src
├── build
├── install
└── log
1.2 ROS2 功能包是什么
功能包是 ROS2 中组织代码的基本单位。简单理解,功能包就是用来存放某一类功能源代码的文件夹。
比如:
- 一个功能包可以专门放小车运动控制代码
- 一个功能包可以专门放自定义消息
- 一个功能包可以专门放 launch 启动文件。
ROS2 不是直接管理零散的源码文件,而是通过功能包来管理代码和依赖。

1.3 msg、srv、action 分别用来做什么
ROS2 中常见的自定义通信接口有三类:msg、srv、action。
| 消息 | .msg | 定义 Topic 发布和订阅的数据结构 | 状态上报、传感器数据、速度指令 |
| 服务 | .srv | 定义 Service 请求和响应的数据结构 | 一次性查询、参数设置、触发某个动作 |
| 动作 | .action | 定义 Action 的目标、结果和反馈 | 导航、长时间运动任务、需要过程反馈的任务 |
二、ROS2 工作空间结构
2.1 src、build、install、log 四个目录
ROS2 工作空间中最常见的四个目录如下:
| src | 源码空间 | 存放我们自己写的代码、脚本、launch 文件、功能包源码 |
| build | 编译空间 | 编译过程中产生的中间文件,一般不需要手动修改 |
| install | 安装空间 | 编译安装后的可执行文件、脚本、接口文件会放在这里 |
| log | 日志空间 | 记录编译和运行过程中的日志,出错时可以查看 |
工作空间目录里能看到 build、install、log、src 四个文件夹。这也是 ROS2 使用 colcon build 后最典型的结构。

注意:
工作空间下的 src 不是直接随便放代码的地方,而是用来存放 ROS2 功能包的地方。
例如:
agilex_open_class_ws/
└── src
├── limo_learning
├── limo_msgs
└── other_package
这里的 limo_learning、limo_msgs 才是真正的 ROS2 功能包。
2.2 src 源码空间
src 是源码空间,主要用来存放我们自己创建或下载的 ROS2 功能包。
(1)工作空间中的 src
工作空间下的 src 不是随便放代码的地方,而是用来存放 ROS2 功能包的地方。
例如:
agilex_open_class_ws/
└── src
├── limo_learning
├── limo_msgs
└── other_package
这里的:
limo_learning
limo_msgs
other_package
才是真正的 ROS2 功能包。
(2)功能包内部的 src
在功能包内部,也可能还有一个 src 文件夹。
例如:
limo_learning/
├── src
├── include
├── launch
├── config
├── CMakeLists.txt
└── package.xml
这里要特别区分两个 src:
例如:
agilex_open_class_ws/src/limo_learning/src/limo_topic_cmd.cpp
这里前面的 src 是工作空间源码目录,后面的 src 是功能包内部的 C++ 源码目录。
完整结构如下:
agilex_open_class_ws/
└── src
└── limo_learning
├── src
│ └── limo_topic_cmd.cpp
├── CMakeLists.txt
└── package.xml
2.3 build 编译空间
(1)build 的作用
build 是编译空间。
当我们在工作空间根目录执行:
colcon build
ROS2 会根据每个功能包中的 CMakeLists.txt 和 package.xml 进行编译。
编译过程中产生的中间文件会放到 build 目录中,例如:
build/limo_learning
build/limo_msgs
一般情况下,build 目录不需要我们手动修改。
(2)什么时候需要清理 build
如果遇到编译缓存异常、依赖更新后不生效等问题,可以清理后重新编译:
rm -rf build install log
colcon build
这相当于重新生成整个工作空间的编译结果。
2.4 install 安装空间
(1)install 是运行结果所在目录
install 是 ROS2 中非常重要的目录。
很多初学者以为 ros2 run 运行的是 src 里面的源码,其实不是。
对于 C++ 功能包来说,我们运行的通常是 编译后安装到 install 空间中的可执行文件。
例如运行:
ros2 run limo_learning limo_topic_cmd
系统会在已经 source 的 install 空间中查找 limo_topic_cmd 这个可执行文件。
(2)src、build、install 的关系
可以这样理解 ROS2 的编译运行流程:
src 里面写代码
↓
colcon build 编译
↓
build 里面生成中间文件
↓
install 里面生成运行结果
↓
ros2 run 从 install 空间查找程序
这也是为什么修改 C++ 源代码之后,一定要重新编译:
colcon build
source install/setup.bash
如果只修改了 src 里的源码,但没有重新编译,那么 install 里面还是旧的可执行文件,运行结果自然不会变化。
2.5 log 日志空间
log 是日志空间。
每次执行:
colcon build
- 编译过程中的日志都会保存在 log 目录中。
- 如果某个功能包编译失败,可以去 log 目录查看更详细的错误信息。
不过对于初学阶段来说,大多数时候直接看终端中的报错信息就已经够用了。
但是当项目变大后,log 目录会很有用,尤其是排查复杂编译错误时。
2.6 为什么修改代码后需要重新编译
(1)C++ 代码不是解释执行
这是初学 ROS2 时非常重要的问题。
因为 ROS2 的 C++ 代码不是解释执行的,而是需要先编译成可执行文件。
修改 C++ 文件后:
src/limo_learning/src/limo_topic_cmd.cpp
只是源码发生了变化。
但是运行时用到的可执行文件在:
install/limo_learning/lib/limo_learning/limo_topic_cmd
如果没有重新执行:
colcon build
那么 install 里面的可执行文件不会更新。
(2)正确编译运行流程
正确流程应该是:
如果只修改了某一个功能包,也可以单独编译:
colcon build –packages-select limo_learning
source install/setup.bash
三、ROS2 功能包概念

3.1 什么是功能包
功能包是 ROS2 中组织代码的基本单位。
一个 ROS2 工程不是直接管理零散的源码文件,而是通过一个个功能包来管理代码、接口、配置和依赖。
一个功能包可以包含:
- C++ 节点代码
- Python 节点代码
- 自定义 msg 消息
- 自定义 srv 服务
- 自定义 action 动作
- launch 启动文件
- config 配置文件
- URDF / xacro 机器人模型文件
- RViz 配置文件
例如:
limo_learning
可以用来存放小车的控制节点代码。
limo_msgs
可以专门用来存放小车相关的自定义消息、服务和动作接口。
所以功能包不只是一个普通文件夹,它是 ROS2 用来组织工程的基本单位。
3.2 功能包和普通文件夹有什么区别
(1)普通文件夹没有 ROS2 工程属性
普通文件夹只是一个目录,本身没有 ROS2 工程属性。
而 ROS2 功能包通常至少包含两个关键文件:
CMakeLists.txt
package.xml
其中:
| CMakeLists.txt | 描述如何编译、生成可执行文件、安装文件 |
| package.xml | 描述功能包名称、版本、依赖、构建类型等信息 |
也就是说,一个文件夹想被 ROS2 当作功能包识别,不能只是随便建一个目录,而是需要符合 ROS2 功能包结构。
(2)C++ 功能包基本结构
例如一个 C++ 功能包可能长这样:
limo_learning/
├── src
│ └── limo_topic_cmd.cpp
├── CMakeLists.txt
└── package.xml
其中:
src
用来存放 C++ 源代码。
CMakeLists.txt
用来描述 C++ 编译规则。
package.xml
用来描述功能包信息和依赖关系。
3.3 C++ 功能包和 Python 功能包的区别
(1)两种常见构建类型
ROS2 中常见的功能包构建类型有两种:
它们分别对应不同语言的开发方式。
| ament_cmake | C++ | 需要 CMakeLists.txt 编译生成可执行文件 |
| ament_python | Python | 主要通过 setup.py 安装 Python 节点入口 |
(2)创建 C++ 功能包
如果要创建 C++ 功能包,可以执行:
cd ~/agilex_open_class_ws/src
ros2 pkg create –build-type ament_cmake limo_learning
如果是写 C++ 节点,一般选择:
ament_cmake
(3)创建 Python 功能包
如果要创建 Python 功能包,可以执行:
cd ~/agilex_open_class_ws/src
ros2 pkg create –build-type ament_python limo_learning_python
如果是写 Python 节点,一般选择:
ament_python
在 ROS2 实际开发中:
底层控制、驱动、实时性要求较高的模块,常用 C++。 快速验证、工具脚本、简单节点,也经常使用 Python。
3.4 功能包内部常见目录结构及作用
(1)完整功能包结构
一个比较完整的 ROS2 功能包内部结构可能如下:
limo_learning/
├── src
├── include
├── launch
├── config
├── msg
├── srv
├── action
├── CMakeLists.txt
└── package.xml
各目录作用如下:
| src | 存放 C++ 源代码 |
| include | 存放 C++ 头文件 |
| launch | 存放 launch 启动文件 |
| config | 存放参数配置文件 |
| msg | 存放自定义消息文件 |
| srv | 存放自定义服务文件 |
| action | 存放自定义动作文件 |
| CMakeLists.txt | C++ 编译和安装规则 |
| package.xml | 功能包信息和依赖声明 |
(2)普通 C++ 节点功能包
不过不是每个功能包都必须包含所有目录。
例如普通 C++ 节点功能包可能只需要:
limo_learning/
├── src
├── CMakeLists.txt
└── package.xml
(3)自定义接口功能包
自定义接口功能包可能是:
limo_msgs/
├── msg
├── srv
├── action
├── CMakeLists.txt
└── package.xml
四、ROS2 自定义接口 msg、srv、action

4.1 为什么需要自定义接口
(1)ROS2 已经提供了很多标准消息
ROS2 已经提供了很多常用消息类型,例如:
geometry_msgs/msg/Twist
sensor_msgs/msg/LaserScan
nav_msgs/msg/Odometry
std_msgs/msg/String
这些消息类型可以满足很多基础需求。
例如:
Twist 用来表示速度指令
LaserScan 用来表示激光雷达数据
Odometry 用来表示里程计数据
String 用来表示字符串消息
(2)标准消息不够用时就需要自定义接口
但是实际机器人开发中,经常会遇到系统自带消息不够用的情况。
比如我们想描述小车状态:
这时候就可以自定义一个消息:
LimoStatus.msg
如果想定义一个服务,让客户端发送速度请求,服务端返回是否执行成功,可以定义:
LimoSrv.srv
如果想定义一个长时间执行的任务,例如发送目标、执行过程反馈、最后返回结果,可以定义:
LimoAction.action
这就是自定义接口的意义。
4.2 msg 消息文件是什么
(1)msg 用于 Topic 话题通信
msg 表示 message,也就是消息。
它主要用于 Topic 话题通信。
Topic 通信可以理解为:
结构类似:
Publisher —- topic —-> Subscriber
(2)msg 文件放在哪里
自定义 msg 文件一般放在功能包的 msg 文件夹中。
例如:
limo_msgs/
├── msg
│ └── LimoStatus.msg
(3)LimoStatus.msg 示例
LimoStatus.msg 示例:
std_msgs/Header header
uint8 vehicle_state
uint8 control_mode
float64 battery_voltage
uint16 error_code
uint8 motion_mode
(4)std_msgs/Header header 是什么意思
其中:
std_msgs/Header header
这个消息里包含了一个 标准消息头 header,它不是普通变量,而是 ROS2 里 std_msgs 包提供的一个标准结构。
header = 时间戳 + 坐标系名称
时间戳 stamp
坐标系 frame_id
其余每一行格式一般是:
字段类型 字段名
例如:
float64 battery_voltage
表示定义一个 float64 类型的字段,字段名叫 battery_voltage,可以用来表示电池电压。
(5)发布消息时如何赋值
比如发布时可以这样填:
msg.header.stamp = this->now();
msg.header.frame_id = "base_link";
msg.vehicle_state = 1;
msg.control_mode = 2;
msg.battery_voltage = 24.5;
msg.error_code = 0;
msg.motion_mode = 1;
(6)msg 文件注意事项
需要注意:
msg 文件里面不需要写 C++ 的分号。
错误写法:
float64 battery_voltage;
正确写法:
float64 battery_voltage
如果 msg 文件中使用了其他功能包里的类型,例如:
std_msgs/Header header
那么在 package.xml 和 CMakeLists.txt 中也要声明对应依赖。
4.3 srv 服务文件是什么
(1)srv 用于 Service 服务通信
srv 表示 service,也就是服务。
Service 是请求响应模型。
它的特点是:
结构类似:
Client —- request —-> Server
Client <— response — Server
(2)srv 文件放在哪里
自定义 srv 文件一般放在功能包的 srv 文件夹中。
例如:
limo_msgs/
├── srv
│ └── LimoSrv.srv
(3)LimoSrv.srv 示例
LimoSrv.srv 示例:
float32 x
float32 y
float32 z
—
bool success
其中 — 是分隔符。
— 上面是请求部分:
float32 x
float32 y
float32 z
— 下面是响应部分:
bool success
可以理解为:
(4)Service 适合什么场景
Service 适合一次性请求场景,例如:
查询一次状态
设置一次参数
触发一次动作
请求一次计算结果
4.4 action 动作文件是什么
(1)action 适合长时间任务
action 表示动作。
Action 可以理解为增强版 Service。
Service 更适合短时间完成的“一问一答”任务,而 Action 更适合执行时间较长、需要过程反馈的任务。
例如:
导航到某个目标点
机械臂移动到某个位置
机器人执行一段巡检任务
小车执行一段运动任务
(2)action 文件放在哪里
自定义 action 文件一般放在功能包的 action 文件夹中。
例如:
limo_msgs/
├── action
│ └── LimoAction.action
(3)LimoAction.action 示例
LimoAction.action 示例:
float32 x
float32 y
float32 z
—
bool success
—
uint32 status
Action 文件由三部分组成,中间用两个 — 分隔:
目标 Goal
—
结果 Result
—
反馈 Feedback
对应上面的例子:
第一部分:表示客户端发送的目标数据。
float32 x
float32 y
float32 z
第二部分:表示任务最终执行结果。
bool success
第三部分:表示任务执行过程中的状态反馈。
uint32 status
(4)Action 的特点
所以 Action 的特点是:
发送目标
执行过程中持续反馈
执行结束返回最终结果
4.5 msg、srv、action 对比总结
三种接口的区别可以总结为:
| msg | 发布 / 订阅 | .msg | 状态上报、传感器数据、速度指令 |
| srv | 请求 / 响应 | .srv | 参数设置、状态查询、触发一次动作 |
| action | 目标 / 反馈 / 结果 | .action | 导航、巡检、机械臂运动、长时间任务 |
更简单的记忆方式:
例如:
/cmd_vel 速度控制:适合 Topic
查询机器人是否准备好:适合 Service
导航到目标点:适合 Action
理解这三种通信方式之后,后面写 ROS2 代码时,就能根据任务特点选择合适的通信模型。
五、本章总结
本章主要讲解了 ROS2 工程基础概念。
5.1 ROS2 工作空间通常包含四个核心目录:
src 源码空间,用来存放功能包
build 编译空间,用来存放编译中间文件
install 安装空间,用来存放编译安装后的结果
log 日志空间,用来存放编译和运行日志
其中最关键的是理解:
src 里面写代码
install 里面放运行结果
ros2 run 通常从 install 空间查找程序
所以修改 C++ 源码、接口文件、CMakeLists.txt 或 package.xml 后,通常都需要重新执行:
colcon build
source install/setup.bash
5.2 功能包是 ROS2 组织代码的基本单位。
普通文件夹只是目录,而 ROS2 功能包通常需要包含:
CMakeLists.txt
package.xml
C++ 功能包一般使用:
ament_cmake
Python 功能包一般使用:
ament_python
5.3 ROS2 中常见的自定义接口有三种:
msg 用于 Topic 话题通信
srv 用于 Service 服务通信
action 用于 Action 动作通信
三者可以简单记忆为:
msg:一直发
srv:问一下,回一下
action:发目标,中途反馈,最后返回结果