Skip to content

DDS Publisher 与 DataWriter 的关系:从 DDS 规范到 ROS2 实现

背景

在封装 DDS 通信层时,一个常见的设计疑问是:一个 Node 内的多个 Publisher 是否应该共享同一个 DDS Publisher 实体?这会不会导致 QoS 冲突? 本文从 DDS 规范出发,梳理 PublisherDataWriter 的职责边界,对比 ROS2 (rmw_cyclonedds) 的实际实现,最终说明 dds-wrapper V4 的设计选择。

DDS 规范中的 Publisher 与 DataWriter

职责划分

DDS 规范中,PublisherDataWriter 是两个不同层次的实体:

实体 职责 是否参与数据传输 是否持有 QoS
Publisher DataWriter 的工厂和生命周期容器,对属于它的 DataWriter 进行分组管理 否(仅作为默认 QoS 来源)
DataWriter 绑定到具体 Topic,实际执行数据写入,持有写入端 QoS

关键点:QoS 设在每个 DataWriter 上,不是设在 Publisher 上。 Publisher 可以提供默认 QoS,但每个 DataWriter 创建时可以覆盖。

层级关系

一个 Publisher 下可以挂多个 DataWriter,每个 DataWriter 的 QoS 完全独立。

ROS2 (rmw_cyclonedds) 的实际做法

实体映射

ROS2 中,每个 rclcpp::Node 底层对应:

  • 1 个 dds::domain::DomainParticipant
  • 1 个 dds::pub::Publisher(Node 级容器)
  • 1 个 dds::sub::Subscriber(Node 级容器)
  • N 个 dds::pub::DataWriter<T>(每个 rclcpp::Publisher<T> 创建一个)
  • M 个 dds::sub::DataReader<T>(每个 rclcpp::Subscription<T> 创建一个)
rclcpp::Node
  ├─ dds::DomainParticipant
  ├─ dds::pub::Publisher            ← 一个,Node 级
  │    ├─ DataWriter<TopicA>        ← rclcpp::Publisher<TopicA> 创建
  │    └─ DataWriter<TopicB>        ← rclcpp::Publisher<TopicB> 创建
  └─ dds::sub::Subscriber           ← 一个,Node 级
       └─ DataReader<TopicC>        ← rclcpp::Subscription<TopicC> 创建

一对一映射

每个 rclcpp::Publisher<T> 恰好创建一个 DDS DataWriter<T>。不存在"一个 Publisher 对应多个 DataWriter"的情况。

ROS2 的 intra-process 优化(同进程内的零拷贝通信)走的是 ring buffer 内存直传路径,完全绕过 DDS,不会额外创建 DataWriter。

dds-wrapper V4 设计

与 ROS2 的对齐

dds-wrapper V4 采用与 ROS2 完全一致的结构:

v4::Node
  ├─ Backend::Participant            ← DomainParticipant
  ├─ Backend::Publisher              ← dds::pub::Publisher,一个,Node 级
  │    ├─ unique_ptr<DataWriter<T1>> ← Publisher<T1>(QoS 独立)
  │    └─ unique_ptr<DataWriter<T2>> ← Publisher<T2>(QoS 独立)
  └─ Backend::Subscriber             ← dds::sub::Subscriber,一个,Node 级
       └─ unique_ptr<DataReader<T3>> ← Subscription<T3>(QoS 独立)
cpp
// Node 构造时创建 DDS 级 Publisher/Subscriber 容器
Node::Node(std::string_view name, uint32_t domainId)
    : _participant(Backend::createParticipant(domainId))
    , _ddsPublisher(Backend::createPublisher(_participant))
    , _ddsSubscriber(Backend::createSubscriber(_participant))
{}

// 每个 API Publisher 创建独立的 DataWriter,QoS 各自设定
template <typename T>
std::shared_ptr<Publisher<T>> Node::createPublisher(
    std::string_view topic, const DdsQoS& qos)
{
    auto resolved = resolveTopic(topic);
    auto writer = Backend::createWriter<T>(
        _participant, _ddsPublisher, resolved, qos);  // QoS 传入 DataWriter
    // ...
}

设计决策总结

维度 ROS2 (rmw_cyclonedds) dds-wrapper V4
Node 级 Publisher 容器 1 个 1 个
rclcpp::Publisher → DataWriter 1:1 1:1
QoS 归属 DataWriter 级 DataWriter 级
多 Publisher 共享容器 是(同一个 Node 下) 是(同一个 Node 下)
QoS 是否互相影响

参考

基于 VitePress 构建