03:让 Node 真正通信——Topic、Parameter、日志与 roslaunch
摘要:这一章把上一章“已经编译好的两个 ELF”真正放进 ROS1 运行时。你会先手工启动
roscore、Publisher 和 Subscriber,再用rosnode、rostopic、rosparam观察系统,最后把多终端操作收敛成一个hello.launch。
@[toc]
本章目标
完成本章后,你应该能够:
- 理解
roscore、ROS Master、Parameter Server、rosout的关系; - 会使用
rosrun启动单个 Node; - 会使用
rosnode list/info查看 Node; - 会使用
rostopic list/info/echo/hz查看 Topic; - 理解 Topic 数据为什么不是由 Master 转发;
- 理解 private parameter
~publish_rate; - 会使用
roslaunch一次启动多个 Node; - 会区分终端日志、
/rosout和~/.ros/log。
为什么先手工开多个终端
roslaunch 很方便,但如果一开始就只会:
1 | roslaunch ros1_hello hello.launch |
就很容易把下面这些进程关系当成一个黑盒:
1 | ROS Master |
所以本章先用多个终端把系统拆开,确认每一层都能工作,再使用 launch 文件把步骤自动化。
Step 1:准备多个 Container 终端
在 Host 可以重复执行:
1 | docker compose exec ros1-dev bash |
或者在 VS Code Remote-SSH 窗口中选择:
1 | Terminal |
准备至少四个终端:
1 | Terminal A:roscore |
每个新 shell 都应该能够看到:
1 | echo "$ROS_DISTRO" |
输出:
1 | noetic |
如果 ros1_hello 刚刚构建完成,但新终端找不到它,可以手工:
1 | source /workspace/ros_ws/devel/setup.bash |
Step 2:启动 roscore
Terminal A:
1 | roscore |
不要关闭这个终端。
另开 Terminal D:
1 | rosnode list |
通常能看到:
1 | /rosout |
roscore 到底启动了什么
可以先把它理解为一个“基础运行时集合”。最重要的组件包括:
1 | ROS Master |
ROS Master 最关键的作用是:
1 | 注册与发现 |
它不是所有 Topic 数据的中央转发服务器。
Step 3:启动 Publisher hello_node
Terminal B:
1 | rosrun ros1_hello hello_node |
应该持续看到:
1 | [INFO] ... hello ros1 from docker: 0 |
rosrun 的两个参数
1 | rosrun ros1_hello hello_node |
可以读成:
1 | package: ros1_hello |
rosrun 会根据 ROS package 环境找到 package,再找到它构建出的 executable。
验证 Node 已经注册
Terminal D:
1 | rosnode list |
应该出现:
1 | /hello_node |
再:
1 | rosnode info /hello_node |
你应该能看到它发布的 Topic 信息。
Step 4:查看 /chatter
Terminal D:
1 | rostopic list |
应该出现:
1 | /chatter |
查看 Topic 类型:
1 | rostopic type /chatter |
应该是:
1 | std_msgs/String |
直接看消息:
1 | rostopic echo /chatter |
典型输出:
1 | data: "hello ros1 from docker: 3" |
查看频率:
1 | rostopic hz /chatter |
默认 publish_rate=1.0 时,频率应接近:
1 | 1 Hz |
这里发生了什么
Publisher 中:
1 | publisher.publish(msg); |
不是把每条数据先送给 ROS Master,再由 Master 转发。
简化流程更接近:
1 | Publisher 向 Master 注册: |
所以 Master 主要负责控制面和发现,真正消息 payload 通常是 Node 之间直接传输。
Step 5:启动 Subscriber hello_listener
Terminal C:
1 | rosrun ros1_hello hello_listener |
应该开始看到:
1 | [INFO] ... received: hello ros1 from docker: 10 |
Subscriber 的关键代码
1 | ros::Subscriber subscriber = |
表示:
1 | 订阅 Topic:chatter |
然后:
1 | ros::spin(); |
持续处理 callback queue。
如果没有 ros::spin() 或其它等价的 callback 处理机制,消息即使已经到达底层连接,用户回调也不会被正常持续执行。
看 Topic 的 Publisher/Subscriber
Terminal D:
1 | rostopic info /chatter |
应该看到:
1 | Publishers: |
这比只看终端输出更有价值,因为它直接证明 ROS 图中的连接关系。
Step 6:理解 rosnode 和 rostopic 的边界
初学时很容易把命令混在一起。
看 Node
1 | rosnode list |
关注的是:
1 | 谁在运行? |
看 Topic
1 | rostopic list |
关注的是:
1 | 有哪些数据通道? |
这两个维度应该同时会看。
Step 7:理解 private parameter ~publish_rate
Publisher 中:
1 | ros::NodeHandle pnh("~"); |
"~" 表示 Node 私有命名空间。
Node 名为:
1 | /hello_node |
所以:
1 | ~publish_rate |
解析后通常是:
1 | /hello_node/publish_rate |
在当前手工 rosrun 场景里,如果没有设置参数:
1 | pnh.param(..., 1.0); |
会使用默认值 1.0。
手工设置参数
Terminal D:
1 | rosparam set /hello_node/publish_rate 5.0 |
但要注意:当前示例只在启动时读取一次参数:
1 | pnh.param(...) |
它不会自动持续监听参数变化。
因此如果 Node 已经启动,再 set 参数,当前 ros::Rate 不会自动变成 5 Hz。
停止并重新启动 Publisher 后再观察:
1 | rostopic hz /chatter |
Step 8:用 launch 文件把多个进程组织起来
手工终端已经证明通信链路正确,现在再打开:
1 | ros_ws/src/ros1_hello/launch/hello.launch |
内容:
1 | <launch> |
先看最外层:
1 | <launch> |
它表示一个 launch 描述文件。
<arg>
1 | <arg name="publish_rate" default="1.0"/> |
是 launch 参数。
启动时可以覆盖:
1 | roslaunch ros1_hello hello.launch publish_rate:=5.0 |
<node>
1 | <node pkg="ros1_hello" |
可以按字段读:
1 | pkg |
<param>
1 | <param name="publish_rate" value="$(arg publish_rate)"/> |
因为它写在 hello_node 的 <node> 内,所以这个参数位于 Node private namespace:
1 | /hello_node/publish_rate |
正好对应 C++ 中:
1 | ros::NodeHandle pnh("~"); |
Step 9:让 roslaunch 启动整个示例
先把之前手工启动的 hello_node、hello_listener、roscore 都 Ctrl+C 停掉。
然后只执行:
1 | roslaunch ros1_hello hello.launch |
如果当前没有 ROS Master,roslaunch 会处理启动所需的 Master。
另开终端:
1 | rosnode list |
应该能看到:
1 | /hello_listener |
再:
1 | rostopic hz /chatter |
然后用不同频率:
1 | roslaunch ros1_hello hello.launch publish_rate:=5.0 |
再次:
1 | rostopic hz /chatter |
应接近 5 Hz。
这一步把完整路径串起来了:
1 | 命令行 publish_rate:=5.0 |
Step 10:理解 ROS 日志的三层位置
代码中:
1 | ROS_INFO("%s", msg.data.c_str()); |
最直观的是终端输出。
但 ROS1 日志还会进入 ROS logging 系统。
/rosout
1 | rostopic echo /rosout |
可以看到日志消息流。
/rosout_agg
rosout Node 会对日志进行汇聚发布,常见 Topic:
1 | /rosout_agg |
~/.ros/log
查看:
1 | ls -l ~/.ros/log |
通常能看到本次运行生成的 log 目录以及 latest 指向最近一次运行。
在 Container 中,这里的 ~ 对 dev 用户通常是:
1 | /home/dev |
所以日志位于 Container 用户 home,不是 Host 仓库中的源码文件。
Step 11:认识 XML-RPC 和 TCPROS 的职责差异
ROS1 常见通信机制可以先分成两层:
1 | 控制/发现层 |
例如 Node 注册 Publisher:
1 | Node → Master |
常见是 XML-RPC 调用。
Subscriber 查询 Publisher 地址:
1 | Subscriber → Master |
也属于发现/协商。
而真正 /chatter 的 std_msgs/String payload 通常通过 TCPROS 在 Node 之间传输。
不要把“ROS1 用 XML-RPC”误解成“所有消息正文都用 XML-RPC 传”。
Step 12:形成一组最小运行时命令
Node:
1 | rosnode list |
Topic:
1 | rostopic list |
Parameter:
1 | rosparam list |
单个 executable:
1 | rosrun ros1_hello hello_node |
多 Node 编排:
1 | roslaunch ros1_hello hello.launch |
这些命令是后面排查 ROS1 系统时最常用的基本工具。
本章常见问题
rosrun 提示找不到 package
先:
1 | source /workspace/ros_ws/devel/setup.bash |
如果 rospack find 也失败,回到:
1 | catkin build ros1_hello |
确认构建成功。
Node 启动了,但 rostopic echo /chatter 没数据
按顺序看:
1 | rosnode list |
不要只盯着 C++ 代码。
如果 /hello_node 都不存在,先处理进程启动问题;如果 Node 存在但 /chatter 没 Publisher,再处理 Publisher 创建问题。
Subscriber 没有回调
看:
1 | rostopic info /chatter |
确认 /hello_listener 出现在 Subscribers。
然后检查代码是否持续执行:
1 | ros::spin(); |
或其它 callback queue 处理方式。
改参数后频率没立刻改变
当前示例参数只在 Node 启动时读取一次。
需要动态变化时,要在程序中设计重新读取、dynamic_reconfigure 或其它机制;不是 rosparam set 自动修改已经构造好的 ros::Rate 对象。
本章练习
- 把 launch 默认频率改成
2.0,重新运行并用rostopic hz验证; - 不运行
hello_listener,观察rostopic info /chatter中 Subscribers 的变化; - 不运行
hello_node,只启动 listener,观察它是否仍能存活; - 用
rosnode info比较 Publisher 和 Subscriber 的 Publications/Subscriptions; - 用
rostopic type和rosmsg show std_msgs/String查看消息定义。
下一章开始进入 VS Code。重点不是“安装几个插件”,而是明确:
1 | 哪些动作发生在 Host? |









