☰
ROS2第一个程序跑通指南:从环境搭建到话题通信实战
2026/10/9 4:15:52 网站建设 项目流程

真正把“第一个ros2程序”跑通,很多人不是栽在代码上,而是栽在环境上。我自己当年从ROS1转过来的时候,光是安装和source环境就折腾了大半天,后来带学生做项目,又看着他们在同样的地方反复卡壳。所以这篇东西不打算只贴一段代码完事,我会把从选版本、装环境、建工作区、写节点、编译运行到调试排查的完整链路都捋一遍,尤其是那些文档里不会明说、但实操中必踩的坑。不管你之后是做机器人导航、机械臂控制还是多机协同,这套流程都是地基。

1. 动手前的关键准备:ROS2版本怎么选、环境怎么搭

1.1 版本选择不是越新越好,而是看匹配

ROS2的版本命名是按字母顺序来的,Foxy、Galactic、Humble、Iron、Jazzy……每个版本都有对应的Ubuntu系统要求。很多人上来就想装最新版,结果系统版本不匹配,装到一半就开始报依赖错误,然后陷入修依赖的连环坑。

目前最稳的组合是Ubuntu 22.04 + ROS2 Humble。Humble是长期支持版本(LTS),官方维护周期长,社区资料多,遇到问题一搜基本都有答案。最新的Jazzy对应Ubuntu 24.04,虽然新功能多一些,但很多第三方库和工具链适配还没跟上,新手贸然使用容易卡在兼容性上。

安装之前先确认系统版本:

lsb_release -a

如果输出显示的是22.04,装Humble;24.04就装Jazzy。系统版本和ROS2版本必须对齐,这一步不能省。

另外注意,ROS2目前对Ubuntu的支持最完善,其他发行版虽然也能装,但要么是社区维护,要么步骤繁琐,对新手相当不友好。如果你手头是Windows机器,也别折腾WSL2里的GUI转发问题,直接装个Ubuntu双系统或者用实体机,省下来的时间够你多写十个节点。

1.2 安装流程里的那些拦路虎

安装本身其实就三条命令,但真实执行起来,每个人的报错都五花八门。先给标准流程:

sudo apt update && sudo apt install -y curl gnupg lsb-release sudo curl -sSL https://raw.githubusercontent.com/ros/rosdistro/master/ros.key -o /usr/share/keyrings/ros-archive-keyring.gpg echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/ros-archive-keyring.gpg] http://packages.ros.org/ros2/ubuntu $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/ros2.list > /dev/null sudo apt update sudo apt install -y ros-humble-desktop

桌面版大概两三个G,包含rqt、RViz2、Gazebo这些图形化工具,学习阶段装这个就够。如果只想跑命令行程序,可以装ros-humble-ros-base,但后续要看可视化效果的话还是得补装desktop,不如一步到位。

安装过程中最高频的报错就是:

错误:1 http://packages.ros.org/ros2/ubuntu jammy InRelease 由于没有公钥,无法验证下列签名

这个问题的根源是APT不认识ROS2仓库的GPG公钥。解决办法是手动下载并安装密钥:

sudo install -o root -g root -m 644 /usr/share/keyrings/ros-archive-keyring.gpg /etc/apt/trusted.gpg.d/

装完之后别忘了source环境:

source /opt/ros/humble/setup.bash

这个source只对当前终端生效,新开会话就要重新执行。懒人做法是写进shell配置文件:

echo "source /opt/ros/humble/setup.bash" >> ~/.bashrc source ~/.bashrc

另外一个经典坑是Setuptools版本过高导致rosdep报错。如果你在后续执行rosdep相关命令时遇到module 'setuptools' has no attribute 'distutils'之类的提示,多半是系统Python环境里的setuptools太新了,降级一下就好:

pip install setuptools==58.2.0

提示:ROS2安装完成后,可以用printenv ROS_DISTRO检查环境变量是否生效,输出humble就说明一切正常。

2. 搭建工作空间与创建第一个C++功能包

2.1 工作空间结构:为什么是“src”加“install”

ROS2采用工作空间(workspace)来组织代码,最常见的结构是dev_ws/src,所有功能包源码放在src目录下,编译后生成build和install目录。很多新手不理解为什么会有三个目录,其实一句话就能讲清楚:

  • src:你写的源代码、功能包定义
  • build:编译过程产生的中间文件,别手动去动它
  • install:编译产物和可执行文件,运行时靠这个目录

创建方式:

mkdir -p ~/dev_ws/src cd ~/dev_ws

为什么不直接在home下面随便建个文件夹开写?因为ROS2的构建系统(colcon)需要按照特定的路径规则扫描功能包,后续source install/setup.bash才能正确发现节点。规范化操作是为了减少不可控的奇怪报错。

2.2 用命令生成功能包,而不是手搓

ROS2创建C++功能包有两种方式:ament_cmake和ament_python。新手直接选C++,因为你后续读别人源码、写机器人控制逻辑,C++是绝对主力。

标准命令:

cd ~/dev_ws/src ros2 pkg create --build-type ament_cmake --dependencies rclcpp std_msgs --node-name talker demo_cpp

参数拆开理解:

  • --build-type ament_cmake:指定为C++功能包
  • --dependencies rclcpp std_msgs:声明依赖的库。rclcpp是ROS2的C++客户端库,std_msgs提供标准消息类型(比如字符串、整数)
  • --node-name talker:自动生成一个叫talker的节点骨架文件,省得手动建
  • demo_cpp:功能包名,也就是目录名

执行完这条命令,src下会多出一个demo_cpp文件夹,里面包含:

demo_cpp/ ├── CMakeLists.txt ├── include/demo_cpp/ ├── package.xml └── src/talker.cpp

package.xml是功能包的信息清单,CMakeLists.txt是编译规则。这两个文件理解清楚很重要,后面要改不少东西。

注意:ros2 pkg create要求的环境是已经source过ROS2的终端,否则命令找不到。

3. 编写第一个发布者与订阅者节点,理解话题机制

3.1 话题、服务、动作:先把通信模型搞懂

写代码之前,必须先搞明白ROS2程序之间是怎么说话的。拿现实生活类比:话题(Topic)就像广播电台,发布者不在乎谁在听,订阅者也不在乎谁在说,双方只在乎频率对不对、内容格式统一不统一。服务(Service)就像打电话,一来一回,有问必答。动作(Action)则像点外卖,先下单,然后边配送边报告进度,最后送达通知你。

第一个程序做话题通信最合理。它是ROS2最基础、最通用的通信方式,跑通之后你基本就理解了节点(Node)这个概念,后面学服务和动作都是在这个基础上叠加。

3.2 发布者节点代码与解释

src/talker.cpp,删掉自动生成的骨架,替换成下面这段:

#include "rclcpp/rclcpp.hpp" #include "std_msgs/msg/string.hpp" using namespace std::chrono_literals; class Talker : public rclcpp::Node { public: Talker() : Node("talker"), count_(0) { publisher_ = this->create_publisher<std_msgs::msg::String>("chatter", 10); timer_ = this->create_wall_timer(500ms, std::bind(&Talker::timer_callback, this)); } private: void timer_callback() { auto message = std_msgs::msg::String(); message.data = "Hello, ROS2! Count: " + std::to_string(count_++); RCLCPP_INFO(this->get_logger(), "Publishing: '%s'", message.data.c_str()); publisher_->publish(message); } rclcpp::Publisher<std_msgs::msg::String>::SharedPtr publisher_; rclcpp::TimerBase::SharedPtr timer_; size_t count_; }; int main(int argc, char **argv) { rclcpp::init(argc, argv); rclcpp::spin(std::make_shared<Talker>()); rclcpp::shutdown(); return 0; }

拆开讲几个关键点:

create_publisher<std_msgs::msg::String>("chatter", 10)里的"chatter"是话题名,10是消息队列长度。队列长度决定发布频率大于处理速度时,最多积压多少条消息,超过就丢旧的保新的。

create_wall_timer(500ms, ...)是定时器,每500毫秒触发一次回调。这个比ros::Rate加while循环的老写法优雅得多,不需要手动维护循环逻辑,节点在rclcpp::spin里就会自动处理事件。

RCLCPP_INFO是ROS2的日志输出宏,运行程序时终端会显示发布的内容,方便确认节点工作正常。

3.3 订阅者节点代码与解释

继续创建监听端:

cd ~/dev_ws/src/demo_cpp/src

新建listener.cpp:

#include "rclcpp/rclcpp.hpp" #include "std_msgs/msg/string.hpp" class Listener : public rclcpp::Node { public: Listener() : Node("listener") { subscription_ = this->create_subscription<std_msgs::msg::String>( "chatter", 10, std::bind(&Listener::topic_callback, this, std::placeholders::_1)); } private: void topic_callback(const std_msgs::msg::String::SharedPtr msg) { RCLCPP_INFO(this->get_logger(), "I heard: '%s'", msg->data.c_str()); } rclcpp::Subscription<std_msgs::msg::String>::SharedPtr subscription_; }; int main(int argc, char **argv) { rclcpp::init(argc, argv); rclcpp::spin(std::make_shared<Listener>()); rclcpp::shutdown(); return 0; }

注意订阅者里有个std::placeholders::_1,这个占位符是C++标准库的机制,表示回调函数的第一个参数由系统传入。初学C++的人看到这一行容易懵,简单理解就是:ROS2收到消息时,会自动把消息内容填到这个占位位置,从而触发你的回调函数。

订阅者和发布者之间靠话题名"chatter"匹配,两边名字必须完全一致,大小写差一个字符都收不到。

3.4 修改CMakeLists.txt的正确姿势

自动生成的CMakeLists.txt不会自动包含listener的编译规则,必须手动添加。打开文件,找到add_executable相关段落,补上订阅者:

add_executable(talker src/talker.cpp) ament_target_dependencies(talker rclcpp std_msgs) add_executable(listener src/listener.cpp) ament_target_dependencies(listener rclcpp std_msgs) install(TARGETS talker listener DESTINATION lib/${PROJECT_NAME})

写完之后,package.xml里确认依赖已声明:

<depend>rclcpp</depend> <depend>std_msgs</depend>

create命令如果带上了--dependencies参数,里面会自动写好,但要检查有没有漏。

提示:改完CMakeLists.txt后如果要重新生成编译配置,建议先删掉build目录再重新build,避免出现缓存残留导致的奇怪问题。

4. 编译、运行与可视化调试:让程序看得见

4.1 colcon build全流程

回到工作空间顶层:

cd ~/dev_ws colcon build

执行后你会看到build和install目录生成。如果编译报错,先看是不是没装colcon:

sudo apt install -y python3-colcon-common-extensions

编译成功之后,不要急着运行,先source新生成的install目录:

source install/setup.bash

这个source和/opt/ros/humble/setup.bash不一样:后者是系统级的ROS2环境,前者你的工作空间内的功能包。运行节点前必须先执行这个source,否则ros2 run找不到你刚写的包。

运行发布者:

ros2 run demo_cpp talker

终端会每隔500ms输出一行Publishing日志。

新开一个终端,同样source环境,运行订阅者:

cd ~/dev_ws source install/setup.bash ros2 run demo_cpp listener

如果一切顺利,第二个终端会不断显示I heard: Hello, ROS2! Count: N。到这一步,你的第一个ROS2程序就跑通了。

4.2 用命令行工具确认运行状态

程序跑起来后,可以用ROS2自带的CLI工具做体检,让运行状态“显性化”:

ros2 node list ros2 topic list ros2 topic echo /chatter ros2 topic info /chatter ros2 topic hz /chatter

逐个说下用途:

  • node list列出当前所有在线节点,你至少应该看到/listener和/talker
  • topic list列出所有话题,能看到/chatter
  • topic echo /chatter实时打印话题中的数据内容,相当于抓包
  • topic info /chatter查看消息类型、发布者数量、订阅者数量
  • topic hz /chatter统计消息发布频率,可以验证是不是500ms一次大约2Hz

这些命令在你以后调试真实机器人项目时是救命工具。三个节点的程序排查起来复杂得多,先得靠命令行把通信链路一段段切开看。

4.3 RViz2里怎么看节点数据流

RViz2是ROS2的3D可视化工具,虽然第一个程序只有字符串消息,但安装和启动RViz2的流程值得提前跑通,因为你后面做机器人建模、传感器数据可视化时绝对离不开它。

启动方式:

rviz2

启动后界面是空的,需要手动添加显示项。点左下角Add,选择By topic,再选/talker这样的数据话题来渲染。对于字符串类型的数据,RViz2没有默认的显示插件,你可以用Panel的TextView来查看,也可以把它理解成“以后你会在这里显示地图、点云、机器人模型”。第一次启动会出现一个警告说当前配置没有Global Options的固定坐标系,后面做机器人模型时改成map或base_link就行。

RViz2本身只做展示,真正的通信数据还是在节点之间的。它启动后,你可以回到之前的终端执行ros2 node list,会发现多出/rviz2这么一个节点。这说明RViz2本身也是ROS2图中的一个节点,它通过订阅话题的方式获取数据来渲染。

4.4 数据记录与回放:ros2 bag

有了第一个程序后,很多人会忽略ros2 bag这个极其重要的工具。它的作用和飞行记录仪一样,把话题数据记录下来,之后反复回放分析。

sudo apt install -y ros-humble-ros2bag ros-humble-rosbag2-storage-default-plugins ros2 bag record /chatter

执行后,程序运行期间的所有/chatter消息都会被存成bag文件。回放:

ros2 bag play bag文件目录名

回放时,订阅者节点会再次收到这些消息。这在调试导航、规划这类复杂系统时非常实用,可以在车上跑一遍录数据,回到工位上无限次复现问题。

5. 常见问题与排查技巧实录

5.1 编译阶段的高频报错

下面这几个错误出现的概率极高,提前记下解决方案:

报错症状原因解决办法
Package 'demo_cpp' not found没source install目录执行source install/setup.bash
Could not find a package configuration file缺少依赖检查CMakeLists.txt里的ament_target_dependencies,并用colcon build --packages-select demo_cpp单独编译
undefined reference torclcpp::init没链接客户端库确认调用了ament_target_dependencies
编译报PythonSetuptools错误setuptools版本过新pip install setuptools==58.2.0
Multiple packages found环境里多个版本共存用source install/setup.bash --merge,但新手建议清理环境

构建需要较长时间时,可以使用:

colcon build --packages-select demo_cpp

只编译指定功能包,能省一半时间。如果只想看警告和错误,追加--event-handlers console_direct+。

5.2 运行时节点找不到或收不到数据

节点启动时报Unable to find executable,先确认两件事。第一,功能包是否成功构建并source,ros2 pkg list | grep demo_cpp能查到才说明功能包在环境里。第二,可执行文件名和CMakeLists里的add_executable目标名是否一致,不一致就在ros2 run 包名 目标名里用目标名。

话题收不到数据这种问题,优先查名字匹配。发布者的话题名是/chatter,订阅的是chatter,看起来一样,但ROS2在某些接口中会把/前缀处理掉,此时ros2 topic list是唯一标准,看它列出的名字来对齐。

还有一个隐藏较深的问题:防火墙。ROS2的发现机制默认使用UDP端口7400到7500范围的组播通信,在同一台机器上跑没问题,但多台机器联调时就要确保防火墙放行这些端口,以及机器之间在同一个网段。如果出现DDS discovery相关日志超时,优先检查这个。

5.3 清理环境和重复安装的坑

ROS2装完跑了一段时间,免不了要卸载重装。完全卸载最稳的方法是:

sudo apt remove ros-humble-* sudo apt autoremove

同时把/opt/ros/humble删除。注意,.bashrc里的source行也要删掉或注释掉,否则每次开终端都会报找不到路径。

“装了两个版本ROS2”会导致环境混乱到让你崩溃,我见过有人同时source了Foxy和Humble,结果运行节点时五花八门的ABI版本冲突。解决办法是保持.bashrc里只source当前需要的那个版本,不要叠加。

5.4 命令行快捷键和终端技巧

ROS2开发过程中终端操作频繁,几个实用小技巧可以明显提高效率:

  • Ctrl+Shift+T在同一个窗口再开一个终端标签,多节点调试时比开一堆独立窗口清爽很多
  • 每个终端标题栏直接写清楚用途,比如”talker”、”listener”,节点多了之后不容易搞混
  • 用gnome-terminal -- bash -c "source /opt/ros/humble/setup.bash && source ~/dev_ws/install/setup.bash && ros2 run demo_cpp talker; exec bash"一键启动节点,适合做启动脚本

我个人在实际开发中的习惯是:每次修改代码后,先colcon build,再重新source一下install目录。很多人改了代码不重新source,运行的时候还在用旧版本,排查半天发现是缓存问题,这个坑踩过就记得了。

5.5 从零开始的检查清单

如果程序还是跑不通,按下面清单逐项过一遍:

  1. printenv ROS_DISTRO返回humble?
  2. ros2 pkg list | grep demo_cpp能查到功能包?
  3. 终端都source了系统环境和install环境?
  4. CMakeLists.txt里目标名和源文件路径有没有错?
  5. 话题名在发布订阅两端是否完全一致?
  6. 防火墙是否阻挡了组播发现?
  7. 是否同一台机器上存在多个ROS2版本?

这套检查流程是我平时排查自己项目时反复用的,帮你把“不知道哪儿错了”变成“找出错的那个环节”。

最后再分享一个小经验:第一个程序跑通之后,你可以顺便试一下改了消息内容后能不能正常编译运行,再试一下把发布频率改成2秒一次,看看订阅端是否同步变化。这些微小的改动会帮你把话题通信的逻辑内化成直觉。ROS2的整套体系非常庞大,但只要是通信机制层面的问题,万变不离其宗,把发布订阅模型吃透了,后面无论做驱动器控制还是多传感器融合,你都站得住脚。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询