[TOC]
ROS Topic 通讯方式
一、Topic 通讯概念
在 ROS 系统中,多个 Node(节点)都需要向 ROS Master 注册。每个 Node 完成各自的功能逻辑,当节点之间需要传递数据时,ROS 提供了基于发布-订阅模式的数据通信机制。

核心角色:
| 角色 |
英文 |
职责 |
| 发布者 |
Publisher |
发送消息的一方 |
| 订阅者 |
Subscriber |
接收消息的一方 |
| 主题 |
Topic |
消息的中转通道 |
与点对点的直接传输不同,ROS 将消息发布到 Topic 中,任何需要该数据的节点都可以从 Topic 中取走消息——可以理解为”聚宝盆”,东西放进去后,不管有多少人来取,都能拿到完整数据。
二、Topic 通讯实现
下面分别用 C++ 和 Python 实现完整的 Publisher 与 Subscriber。
2.1 C++ 实现
Publisher(发布者)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| #include "ros/ros.h" #include <iostream> #include "std_msgs/String.h"
using namespace std;
int main(int argc, char **argv) { string nodeName = "cpppublisher"; string topicName = "cpptopic";
ros::init(argc, argv, nodeName); ros::NodeHandle node;
const ros::Publisher &publisher = node.advertise<std_msgs::String>(topicName, 1000);
int index = 0; ros::Rate rate(1); while (ros::ok()) { std_msgs::String msg; msg.data = "hello pub " + to_string(index); publisher.publish(msg);
cout << msg.data << endl; rate.sleep(); index++; } return 0; }
|
Subscriber(订阅者)
topicCallback 是收到订阅消息时的回调函数。
⚠️ 必须用 ros::Subscriber 变量接收 node.subscribe() 的返回值,否则对象会被系统立即回收。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| #include "ros/ros.h" #include <iostream> #include "std_msgs/String.h"
using namespace std;
void topicCallback(const std_msgs::String::ConstPtr &msg) { cout << (*msg).data << endl; }
int main(int argc, char **argv) { string nodeName = "cppsubscriber"; string topicName = "cpptopic";
ros::init(argc, argv, nodeName); ros::NodeHandle node;
const ros::Subscriber &subscriber = node.subscribe(topicName, 1000, topicCallback);
ros::spin(); return 0; }
|
2.2 Python 实现
Publisher(发布者)
💡 Python 脚本必须包含以下两行:
#!/usr/bin/env python —— 声明为 Python 脚本(不加则默认按 bash 执行)
#coding:utf-8 —— 提供中文支持
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
|
import rospy from std_msgs.msg import String
if __name__ == '__main__': nodeName = "pypublisher" topicName = "pytopic"
rospy.init_node(nodeName)
publisher = rospy.Publisher(topicName, String, queue_size=1000)
rate = rospy.Rate(10) count = 0 while not rospy.is_shutdown(): publisher.publish("hello %d" % count) rate.sleep() count += 1
|
Subscriber(订阅者)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
|
import rospy from std_msgs.msg import String
def topicCallback(msg): print(msg.data)
if __name__ == '__main__': nodeName = "pysubscriber" topicName = "pytopic"
rospy.init_node(nodeName)
rospy.Subscriber(topicName, String, topicCallback)
rospy.spin()
|
2.3 调试工具
| 工具 |
命令 |
说明 |
| Topic 列表与监控 |
rosrun rqt_topic rqt_topic |
图形化查看所有 Topic |
| Publisher 调试 |
rosrun rqt_publisher rqt_publisher |
手动发布消息测试 |
| 命令行查看消息 |
rostopic echo /topic_name |
终端打印消息内容 |
| 命令行发布消息 |
rostopic pub /topic_name std_msgs/String "data: hello" |
手动发布一条消息 |
三、Topic 自定义消息
除了使用 std_msgs 等内置消息类型,我们也可以定义自己的消息格式。
3.1 定义步骤
Step 1:创建 msg 目录
在 package 目录下新建 msg 目录:
1 2
| cd ~/catkin_ws/src/<your_package> mkdir msg
|
Step 2:编写 .msg 文件
创建 Student.msg 文件,每行一个字段(格式:类型 名称):
ROS 提供的基础数据类型远不止 string 和 int64,还包括 float32/64、bool、time、duration 以及各类数组等。完整列表可参考 ROS 官方文档的 msg 类型说明。
Step 3:配置 package.xml
添加消息生成和运行时依赖:
1 2
| <build_depend>message_generation</build_depend> <exec_depend>message_runtime</exec_depend>
|
| 依赖 |
作用 |
message_generation |
编译时生成消息对应的 C++ 头文件 / Python 模块 |
message_runtime |
运行时消息序列化/反序列化支持 |
Step 4:配置 CMakeLists.txt
① find_package 中添加 message_generation:
1 2 3 4 5 6
| find_package(catkin REQUIRED COMPONENTS roscpp rosmsg rospy message_generation )
|
② 添加消息文件声明:
1 2 3 4
| add_message_files( FILES Student.msg )
|
文件名必须与 msg/ 目录下的实际文件名一致,且必须位于 msg/ 目录下,否则编译会报错。
③ 配置消息生成依赖:
1 2 3 4
| generate_messages( DEPENDENCIES std_msgs )
|
默认需添加 std_msgs 依赖,如果消息中引用了其他自定义消息,也需要在此处声明。
④ 配置 catkin_package 导出:
1 2 3 4 5 6
| catkin_package(
CATKIN_DEPENDS roscpp rosmsg rospy message_runtime
)
|
此配置将 message_runtime 依赖导出给依赖当前包的其他包使用。
Step 5:编译并验证
1 2 3
| cd ~/catkin_ws catkin_make
|
验证生成的文件:
| 语言 |
验证路径 |
说明 |
| C++ |
devel/include/<package_name>/ |
应出现 Student.h 头文件 |
| Python |
devel/lib/python2.7/dist-packages/<package_name>/msg.py |
出现对应 py 文件即成功 |
使用 rosmsg 工具校验:
1
| rosmsg show demo_msgs/Student
|
输出结果应与定义的字段一致,说明自定义消息创建成功。
四、使用自定义消息
自定义消息本质上是一个独立的 package。当其他 package 需要使用它时,必须显式添加依赖关系。
4.1 配置 package.xml
1 2 3
| <build_depend>demo_msgs</build_depend> <build_export_depend>demo_msgs</build_export_depend> <exec_depend>demo_msgs</exec_depend>
|
4.2 配置 CMakeLists.txt
在 find_package 中添加自定义消息包:
1 2 3 4 5 6
| find_package(catkin REQUIRED COMPONENTS roscpp rosmsg rospy demo_msgs )
|
4.3 使用示例
C++ 发布自定义消息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| #include "ros/ros.h" #include "demo_msgs/Student.h"
int main(int argc, char **argv) { ros::init(argc, argv, "student_publisher"); ros::NodeHandle node;
ros::Publisher pub = node.advertise<demo_msgs::Student>("student_info", 1000);
ros::Rate rate(1); int count = 0; while (ros::ok()) { demo_msgs::Student msg; msg.name = "张三"; msg.age = 20 + count;
pub.publish(msg); ROS_INFO("Published: %s, %d years old", msg.name.c_str(), msg.age);
rate.sleep(); count++; } return 0; }
|
C++ 订阅自定义消息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| #include "ros/ros.h" #include "demo_msgs/Student.h"
void studentCallback(const demo_msgs::Student::ConstPtr &msg) { ROS_INFO("Received student: %s, age: %d", msg->name.c_str(), msg->age); }
int main(int argc, char **argv) { ros::init(argc, argv, "student_subscriber"); ros::NodeHandle node;
const ros::Subscriber &sub = node.subscribe("student_info", 1000, studentCallback);
ros::spin(); return 0; }
|
Python 发布自定义消息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
|
import rospy from demo_msgs.msg import Student
if __name__ == '__main__': rospy.init_node("student_publisher_py") pub = rospy.Publisher("student_info", Student, queue_size=1000)
rate = rospy.Rate(1) count = 0 while not rospy.is_shutdown(): msg = Student() msg.name = "李四" msg.age = 20 + count
pub.publish(msg) rospy.loginfo("Published: %s, %d years old", msg.name, msg.age)
rate.sleep() count += 1
|
Python 订阅自定义消息
1 2 3 4 5 6 7 8 9 10 11 12 13
|
import rospy from demo_msgs.msg import Student
def student_callback(msg): rospy.loginfo("Received student: %s, age: %d", msg.name, msg.age)
if __name__ == '__main__': rospy.init_node("student_subscriber_py") rospy.Subscriber("student_info", Student, student_callback) rospy.spin()
|
五、总结
| 要点 |
说明 |
| 通信模型 |
发布-订阅(Publish-Subscribe),通过 Topic 解耦 |
| 数据流向 |
Publisher → Topic → Subscriber(一对多) |
| 节点注册 |
所有 Node 启动时向 ROS Master 注册 |
| 自定义消息 |
通过 .msg 文件定义,编译后自动生成各语言绑定 |
| 跨包依赖 |
消费方需同时在 package.xml 和 CMakeLists.txt 中声明对消息包的依赖 |
| 调试利器 |
rqt_topic、rqt_publisher、rostopic echo/pub 是日常开发的好帮手 |