ROS 2 怎么创建发布者和订阅者?零基础入门笔记

5 阅读8分钟

ROS 2 怎么创建发布者和订阅者?把 QoS 一起讲明白的零基础笔记

简介

刚开始学 ROS 2 的时候,很多人会先学发布者和订阅者,但很快又会遇到一个新词:QoS

于是问题就来了:

  • 发布者和订阅者到底怎么创建?
  • create_publisher(..., 10) 里的 10 是什么?
  • 为什么有时候 topic 名和消息类型都对了,还是收不到消息?
  • reliable()best_effort()transient_local() 又是什么意思?

其实这几件事可以放在一起理解。

你可以先记住一句话:

发布者和订阅者负责“发消息”和“收消息”,QoS 负责规定“消息怎么传”。

这篇笔记会用尽量通俗的方式,带你把这几件事一次搞懂。


一、什么是发布者和订阅者?

在 ROS 2 里,不同节点之间经常需要交换数据。

比如:

  • 摄像头节点发布图像
  • 雷达节点发布扫描数据
  • 控制节点发布速度指令
  • 状态节点发布当前状态

这里就有两个角色。

1. 发布者(Publisher)

发布者就是“发消息的人”。

它负责把消息发到某个 topic 上。


2. 订阅者(Subscriber)

订阅者就是“收消息的人”。

它负责监听某个 topic,只要这个 topic 上有新消息,它就会收到。


3. Topic

topic 可以理解成一个“频道”。

发布者把消息发到这个频道。
订阅者订阅这个频道,然后接收消息。


二、什么是 QoS?

QoS 全称是 Quality of Service,中文通常叫“服务质量策略”。

你可以把它理解成:

消息在传输之前,要先约定好一套规则。

比如:

  • 消息能不能丢?
  • 要不要尽量保证送达?
  • 要缓存多少条?
  • 后来才加入的订阅者,能不能收到之前发过的消息?

这些规则合在一起,就是 QoS。

所以你可以这样理解:

  • 发布者 / 订阅者:解决“谁发谁收”
  • QoS:解决“消息按什么规则发和收”

三、创建发布者和订阅者最核心的两个函数

在 ROS 2 C++ 里,发布者和订阅者通常都在节点内部创建。

最核心的两个函数就是:

创建发布者

this->create_publisher<消息类型>("topic名字", qos);

创建订阅者

this->create_subscription<消息类型>(
    "topic名字",
    qos,
    回调函数
);

这里的 qos 可以直接写成一个数字,也可以写成 rclcpp::QoS(...)


四、为什么有人写 10,有人写 rclcpp::QoS(10)

你可能见过这两种写法:

this->create_publisher<std_msgs::msg::String>("topic", 10);
this->create_publisher<std_msgs::msg::String>(
    "topic",
    rclcpp::QoS(10)
);

对于初学者来说,可以先这样理解:

  • 10:相当于给了一个最基本的队列深度
  • rclcpp::QoS(10):表示“我要显式配置 QoS”

而且以后你想继续加规则,比如:

  • reliable()
  • best_effort()
  • transient_local()

就必须从 rclcpp::QoS(...) 这种写法往下接。

例如:

auto qos = rclcpp::QoS(10).reliable();

这比只写一个 10 更清楚。


五、先看一个最简单的发布者(带 QoS)

下面是一个最基础的 C++ 发布者。

它每隔 500ms 发布一条字符串消息,并且使用了 QoS。

#include <chrono>
#include <memory>
#include <string>

#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"

using namespace std::chrono_literals;

class MinimalPublisher : public rclcpp::Node
{
public:
    MinimalPublisher() : Node("minimal_publisher"), count_(0)
    {
        auto qos = rclcpp::QoS(10).reliable();

        publisher_ = this->create_publisher<std_msgs::msg::String>(
            "topic",
            qos
        );

        timer_ = this->create_wall_timer(
            500ms,
            std::bind(&MinimalPublisher::timer_callback, this)
        );
    }

private:
    void timer_callback()
    {
        auto msg = std_msgs::msg::String();
        msg.data = "Hello, ROS 2! " + std::to_string(count_++);
        RCLCPP_INFO(this->get_logger(), "发布消息: '%s'", msg.data.c_str());
        publisher_->publish(msg);
    }

    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<MinimalPublisher>());
    rclcpp::shutdown();
    return 0;
}

六、这段发布者代码该怎么理解?

1. Node("minimal_publisher")

这是节点名字。

也就是说,这个程序启动后,在 ROS 2 里它叫 minimal_publisher


2. auto qos = rclcpp::QoS(10).reliable();

这句就是在配置 QoS。

你可以先把它理解成:

  • 保留最近 10 条消息
  • 并且尽量保证消息可靠送达

其中:

  • 10 表示队列深度
  • reliable() 表示可靠传输

3. create_publisher

publisher_ = this->create_publisher<std_msgs::msg::String>(
    "topic",
    qos
);

这句话的意思是:

  • 创建一个发布者
  • 消息类型是 std_msgs::msg::String
  • topic 名字叫 "topic"
  • 它使用上面设置好的 qos

4. publisher_->publish(msg)

这句表示:

把消息真正发出去。


七、再看一个最简单的订阅者(带 QoS)

#include <memory>

#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"

class MinimalSubscriber : public rclcpp::Node
{
public:
    MinimalSubscriber() : Node("minimal_subscriber")
    {
        auto qos = rclcpp::QoS(10).reliable();

        subscription_ = this->create_subscription<std_msgs::msg::String>(
            "topic",
            qos,
            std::bind(&MinimalSubscriber::topic_callback, this, std::placeholders::_1)
        );
    }

private:
    void topic_callback(const std_msgs::msg::String::SharedPtr msg) const
    {
        RCLCPP_INFO(this->get_logger(), "收到消息: '%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<MinimalSubscriber>());
    rclcpp::shutdown();
    return 0;
}

八、这段订阅者代码在干什么?

1. 创建节点

class MinimalSubscriber : public rclcpp::Node

说明我们写了一个订阅者节点。


2. 配置 QoS

auto qos = rclcpp::QoS(10).reliable();

这里和发布者一样,也用了相同的 QoS。

这很重要。

因为发布者和订阅者要想顺利通信,QoS 最好是兼容的。


3. 创建订阅者

subscription_ = this->create_subscription<std_msgs::msg::String>(
    "topic",
    qos,
    std::bind(&MinimalSubscriber::topic_callback, this, std::placeholders::_1)
);

它的意思是:

  • 订阅 "topic"
  • 消息类型是 std_msgs::msg::String
  • 使用 qos
  • 只要收到消息,就调用 topic_callback

4. 回调函数

void topic_callback(const std_msgs::msg::String::SharedPtr msg) const

这个函数可以理解成:

一旦收到消息,就自动执行这里面的代码。

在这个例子里,就是把收到的消息打印出来。


九、QoS 里最常见的几个配置

零基础先记住下面 3 个就够用了。


1. reliable()

auto qos = rclcpp::QoS(10).reliable();

意思是:

尽量保证消息送达。

适合:

  • 控制命令
  • 重要状态消息
  • 不希望轻易丢失的数据

2. best_effort()

auto qos = rclcpp::QoS(5).best_effort();

意思是:

能送就送,丢一点也没关系。

适合:

  • 激光雷达
  • 相机
  • IMU
  • 高频传感器数据

因为这类数据往往更关心“最新的一帧”,而不是“旧数据必须补回来”。


3. transient_local()

auto qos = rclcpp::QoS(1).transient_local().reliable();

意思是:

发布者会保留历史消息,后来才启动的订阅者也有机会收到之前发过的消息。

适合:

  • 地图
  • 静态配置
  • 初始化信息
  • 一次发布但希望后来者也能看到的数据

十、初学者怎么选 QoS?

如果你刚入门,可以直接照下面记。

场景 1:普通消息

比如:

  • 状态信息
  • 普通控制信息

推荐:

auto qos = rclcpp::QoS(10).reliable();

场景 2:高频传感器

比如:

  • 雷达
  • 相机
  • IMU

推荐:

auto qos = rclcpp::QoS(5).best_effort();

场景 3:静态消息 / 地图 / 初始化数据

推荐:

auto qos = rclcpp::QoS(1).transient_local().reliable();

十一、为什么有时候收不到消息?很可能就是 QoS 问题

很多初学者都会遇到这种情况:

  • topic 名一样
  • 消息类型一样
  • 发布者也启动了
  • 订阅者也启动了
  • 但就是收不到消息

这时候除了检查 topic 和类型,还要检查 QoS。

因为在 ROS 2 里:

发布者和订阅者只有在 QoS 兼容时,才会建立连接。

你可以先简单记住一句话:

如果两边“通信规则”差太多,就连不上。


十二、一个很常见的 QoS 坑

发布者:

auto qos = rclcpp::QoS(5).best_effort();

订阅者:

auto qos = rclcpp::QoS(10).reliable();

这时候就可能有问题。

因为:

  • 发布者说:我只是“尽力发”
  • 订阅者却要求:你必须“可靠发”

这就容易不兼容。

所以初学者最稳妥的做法就是:

发布者和订阅者先用同样的 QoS。


十三、最小记忆模板

发布者模板

auto qos = rclcpp::QoS(10).reliable();

publisher_ = this->create_publisher<std_msgs::msg::String>(
    "topic",
    qos
);

订阅者模板

auto qos = rclcpp::QoS(10).reliable();

subscription_ = this->create_subscription<std_msgs::msg::String>(
    "topic",
    qos,
    std::bind(&MyNode::callback, this, std::placeholders::_1)
);

发布消息

publisher_->publish(msg);

让节点持续运行

rclcpp::spin(node);

十四、怎么创建包、编译和运行?

1. 创建 C++ 包

ros2 pkg create --build-type ament_cmake --license Apache-2.0 cpp_pubsub

2. 编译

cd ~/ros2_ws
colcon build

3. source 环境

source install/setup.bash

4. 运行发布者

ros2 run cpp_pubsub talker

5. 运行订阅者

ros2 run cpp_pubsub listener

十五、初学者最容易踩的坑

坑 1:topic 名不一样

比如一个叫 "topic",另一个叫 "topics",那就通信不了。


坑 2:消息类型不一样

比如一个发 String,另一个却按别的类型接收,也不行。


坑 3:QoS 不兼容

这是 ROS 2 里很常见的问题。

表面看起来都正常,但实际上两边连不上。


坑 4:忘了 spin

如果没有:

rclcpp::spin(...)

节点可能很快就退出了。


坑 5:编译后忘了 source install/setup.bash

这样系统可能找不到你刚刚编译出来的包。


十六、一句话总结

最后用一句最好记的话收尾:

发布者负责发消息,订阅者负责收消息,topic 是交流频道,QoS 决定消息按什么规则传。

如果你是刚入门,建议你先把下面这句背下来:

auto qos = rclcpp::QoS(10).reliable();

然后再记住:

create_publisher(...)
create_subscription(...)

这样你就已经把 ROS 2 发布订阅最核心的部分入门了。