ROS2第一个程序跑通指南:从环境搭建到话题通信实战
发布时间:2026/10/9 4:16:04 锦皓数字建站

真正把“第一个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装Humble24.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 setuptools58.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.cpppackage.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_publisherstd_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::Publisherstd_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_sharedTalker()); rclcpp::shutdown(); return 0; }拆开讲几个关键点create_publisherstd_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_subscriptionstd_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::Subscriptionstd_msgs::msg::String::SharedPtr subscription_; }; int main(int argc, char **argv) { rclcpp::init(argc, argv); rclcpp::spin(std::make_sharedListener()); 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里确认依赖已声明dependrclcpp/depend dependstd_msgs/dependcreate命令如果带上了--dependencies参数里面会自动写好但要检查有没有漏。提示改完CMakeLists.txt后如果要重新生成编译配置建议先删掉build目录再重新build避免出现缓存残留导致的奇怪问题。4. 编译、运行与可视化调试让程序看得见4.1 colcon build全流程回到工作空间顶层cd ~/dev_ws colcon build执行后你会看到build和install目录生成。如果编译报错先看是不是没装colconsudo 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和/talkertopic list列出所有话题能看到/chattertopic 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.bashCould 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 setuptools58.2.0Multiple packages found环境里多个版本共存用source install/setup.bash --merge但新手建议清理环境构建需要较长时间时可以使用colcon build --packages-select demo_cpp只编译指定功能包能省一半时间。如果只想看警告和错误追加--event-handlers console_direct。5.2 运行时节点找不到或收不到数据节点启动时报Unable to find executable先确认两件事。第一功能包是否成功构建并sourceros2 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开发过程中终端操作频繁几个实用小技巧可以明显提高效率CtrlShiftT在同一个窗口再开一个终端标签多节点调试时比开一堆独立窗口清爽很多每个终端标题栏直接写清楚用途比如”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 从零开始的检查清单如果程序还是跑不通按下面清单逐项过一遍printenv ROS_DISTRO返回humbleros2 pkg list | grep demo_cpp能查到功能包终端都source了系统环境和install环境CMakeLists.txt里目标名和源文件路径有没有错话题名在发布订阅两端是否完全一致防火墙是否阻挡了组播发现是否同一台机器上存在多个ROS2版本这套检查流程是我平时排查自己项目时反复用的帮你把“不知道哪儿错了”变成“找出错的那个环节”。最后再分享一个小经验第一个程序跑通之后你可以顺便试一下改了消息内容后能不能正常编译运行再试一下把发布频率改成2秒一次看看订阅端是否同步变化。这些微小的改动会帮你把话题通信的逻辑内化成直觉。ROS2的整套体系非常庞大但只要是通信机制层面的问题万变不离其宗把发布订阅模型吃透了后面无论做驱动器控制还是多传感器融合你都站得住脚。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。