3 · 编写第一个发布者节点
好,到了真正动手的时刻。这一章我们直接写一个能跑的发布者节点——用 C++ 和 Python 各写一遍。你想想看,ROS2 里最核心的动作就是“发消息”。不管你是做导航、机械臂还是感知,本质上都是把数据扔到话题上。我个人习惯先跑通一个最小例子,再慢慢往里填逻辑。
3.1 发布者节点的骨架
说白了,一个发布者节点就干三件事:初始化ROS2、创建发布者、循环发消息。不管用 C++ 还是 Python,流程一模一样。我刚开始学的时候,总以为要写很多配置代码,其实核心就十几行。
核心流程:
rclcpp/rclpy 初始化 → 创建节点 → 创建发布者 → while 循环里 publish() → spin 保持运行
rclcpp/rclpy 初始化 → 创建节点 → 创建发布者 → while 循环里 publish() → spin 保持运行
3.2 C++ 发布者(rclcpp)
先看 C++ 版本。我个人习惯用 std::make_shared 创建节点,这样生命周期好控制。注意看 rclcpp::init 和 spin 的配合——嗯,这里要注意,spin 会阻塞,所以发布循环要放在 spin 之前或者用定时器。
// talker_cpp.cpp
#include <rclcpp/rclcpp.hpp>
#include <std_msgs/msg/string.hpp>
int main(int argc, char * argv[])
{
// 1. 初始化ROS2
rclcpp::init(argc, argv);
// 2. 创建节点
auto node = std::make_shared<rclcpp::Node>("talker");
// 3. 创建发布者(话题名 /chatter,队列大小 10)
auto publisher = node->create_publisher<std_msgs::msg::String>("chatter", 10);
// 4. 循环发布消息
rclcpp::WallRate loop_rate(2); // 2Hz
int count = 0;
while (rclcpp::ok()) {
auto msg = std_msgs::msg::String();
msg.data = "Hello ROS2: " + std::to_string(count++);
RCLCPP_INFO(node->get_logger(), "发布: '%s'", msg.data.c_str());
publisher->publish(msg);
rclcpp::spin_some(node); // 处理回调(如果有)
loop_rate.sleep();
}
rclcpp::shutdown();
return 0;
}
💡 我的小经验: 早期我老忘记加
rclcpp::spin_some,结果订阅者收不到消息。其实 spin_some 不是必须的,但如果你节点里还有服务或定时器,最好加上。纯发布场景下,用 rclcpp::ok() 判断就够了。
3.3 Python 发布者(rclpy)
Python 版本更简洁,但容易掉进一个坑——rclpy.spin 也是阻塞的。我建议用 Rate 对象配合 while rclpy.ok(),这样逻辑清晰。
# talker_py.py
import rclpy
from rclpy.node import Node
from std_msgs.msg import String
class Talker(Node):
def __init__(self):
super().__init__('talker')
self.publisher_ = self.create_publisher(String, 'chatter', 10)
self.timer = self.create_timer(0.5, self.timer_callback) # 2Hz
self.count = 0
def timer_callback(self):
msg = String()
msg.data = f'Hello ROS2: {self.count}'
self.count += 1
self.get_logger().info(f'发布: "{msg.data}"')
self.publisher_.publish(msg)
def main(args=None):
rclpy.init(args=args)
node = Talker()
rclpy.spin(node)
node.destroy_node()
rclpy.shutdown()
if __name__ == '__main__':
main()
⚠️ 曾经踩过的坑: 我在一个项目里用 Python 发布者,忘记调用
rclpy.spin,结果节点启动后直接退出了。记住:spin 保持节点存活,定时器才能触发。如果你用 while 循环自己控制,记得加 rclpy.spin_once。
3.4 代码结构对比
我把两个版本的关键差异整理成表格,方便你对照。其实核心思想一模一样,只是语法糖不同。
| 步骤 | C++ (rclcpp) | Python (rclpy) |
|---|---|---|
| 初始化 | rclcpp::init(argc, argv) | rclpy.init() |
| 创建节点 | make_shared<Node>("name") | Node('name') |
| 创建发布者 | create_publisher<T>("topic", qos) | create_publisher(T, 'topic', qos) |
| 循环发布 | while + WallRate + publish | 定时器回调 + publish |
| 保持运行 | spin_some / spin | spin(node) |
| 关闭 | rclcpp::shutdown() | rclpy.shutdown() |
3.5 编译与运行
C++ 节点需要编译。你记得在 CMakeLists.txt 里添加 add_executable 和 ament_target_dependencies。Python 节点直接 ros2 run 就行。我个人习惯先开一个终端跑发布者,再开另一个终端用 ros2 topic echo /chatter 验证。
快速验证命令:
然后
ros2 run your_package talker_cpp 或 ros2 run your_package talker_py然后
ros2 topic echo /chatter 看消息。
嗯,到这里你已经写完了第一个发布者节点。是不是比想象中简单?其实 ROS2 的发布者就是一个“循环发消息”的工人,你给它话题名和消息类型,它就一直干。