2. DDS基础概念

DDS(Data Distribution Service,数据分发服务)是一套面向实时分布式系统的数据通信中间件标准。它最核心的思想不是“客户端请求服务器”,而是以数据为中心的发布订阅模型:参与者不直接互相调用,而是围绕“主题”这一数据对象进行读写,由中间件负责把数据从写入者高效、可靠地分发到所有感兴趣的读取者。理解发布订阅模型以及数据写入者、数据读取者、主题、域、分区这些术语,是掌握 DDS 工程实践的第一步。

一、发布订阅模型的基本工作原理

在 DDS 中,通信双方被抽象为发布者(Publisher)和订阅者(Subscriber)。发布者通过数据写入者(DataWriter)把样本写入某个主题,订阅者通过数据读取者(DataReader)从该主题获取样本。中间件根据主题名称、数据类型和 QoS(服务质量)策略,自动完成匹配与传输,无需应用层关心对端地址、连接建立或重连逻辑。

这种模型的关键特征是:时间解耦(发布者与订阅者不必同时在线)、空间解耦(双方不需要知道彼此位置)、同步解耦(读写操作异步进行)。因此它非常适合雷达、飞控、车载、工业控制等对实时性和动态拓扑要求高的场景。

发布订阅模型的核心链路可以概括为:
应用写入数据 → DataWriter → Publisher → 域内传输 → Subscriber → DataReader → 应用读取数据

二、核心术语逐项解析

域(Domain)是 DDS 中最大的逻辑隔离单位。每个域由一个域 ID 标识,只有处于同一域内的参与者才能互相发现和通信。不同域之间的数据完全隔离,常用于把不同业务、不同安全等级或不同测试环境分开。

主题(Topic)是数据的逻辑名称,由“主题名 + 数据类型”共同确定。发布者和订阅者必须使用相同的主题名和兼容的数据类型才能匹配。主题是 DDS 中数据流动的“地址”,也是 QoS 策略绑定的对象。

数据写入者(DataWriter)是发布端实际执行写操作的实体。应用调用 write() 把样本交给 DataWriter,DataWriter 再根据 QoS 决定缓存、重传、丢弃或按历史深度保存。一个 Publisher 可以管理多个 DataWriter。

数据读取者(DataReader)是订阅端实际执行读操作的实体。它从中间件接收样本,应用通过 take() 或 read() 获取数据。其中 take() 会移除样本,read() 只读取不移除,这是初学者最容易混淆的一点。

分区(Partition)是域内更细粒度的逻辑分组机制。通过给发布者和订阅者设置相同的分区字符串,可以控制哪些读写者能够互相匹配。分区常用于同一域内按功能、优先级或数据敏感度做二次隔离。

DDS 发布订阅模型与核心术语 域 Domain(域ID = 0) 发布端 Publisher 数据写入者 DataWriter write(sample) 数据写入者 DataWriter write(sample) 分区 Partition = "sensor" 订阅端 Subscriber 数据读取者 DataReader take() / read() 数据读取者 DataReader take() / read() 分区 Partition = "sensor" 主题 Topic 名称 + 数据类型 QoS 策略绑定 自动匹配读写者 同一域内,主题名与数据类型一致、分区匹配的 DataWriter 与 DataReader 自动建立通信 域隔离 · 主题寻址 · 分区过滤 · QoS 约束

三、典型场景:一个温度监控系统

假设一个机房温度监控系统,需要把多个温度传感器的数据实时分发给监控大屏和告警服务。可以这样设计:

  • 域 ID 使用 0,表示生产环境;测试环境使用域 ID 1,互不干扰。
  • 主题定义为 MachineRoom/Temperature,数据类型为 Temperature,包含设备编号、温度值、时间戳。
  • 每个传感器对应一个 DataWriter,分区设置为 "sensor"。
  • 监控大屏和告警服务各创建一个 DataReader,分区同样设置为 "sensor",从而只接收传感器数据。
// 伪代码:创建主题与数据写入者
Topic<Temperature> topic = participant.create_topic("MachineRoom/Temperature");
Publisher publisher = participant.create_publisher();
DataWriter<Temperature> writer = publisher.create_datawriter(topic);

Temperature sample;
sample.device_id = "T-001";
sample.value = 26.5;
writer.write(sample);

// 订阅端
DataReader<Temperature> reader = subscriber.create_datareader(topic);
reader.take(sample);   // 读取并移除

四、常见误区与复盘要点

误区一:把主题当成“队列”。主题不是点对点队列,而是多对多的数据空间。多个 DataReader 可以同时收到同一份数据,发布者不需要知道有多少订阅者。

误区二:忽略数据类型匹配。主题名相同但数据类型不一致时,读写者无法匹配,通信会静默失败。排查时应优先检查类型注册与主题名。

误区三:混淆 read 与 take。read() 只读取不移除,take() 读取后移除。若循环中一直用 read(),可能反复处理同一条样本。

误区四:分区设置不一致。发布者与订阅者分区不匹配时,即使主题相同也无法通信。分区是“与”关系,必须至少有一个共同分区。

五、核心术语速查表

术语 英文 作用 关键点
域 Domain 最大逻辑隔离单位 域 ID 不同则完全隔离
主题 Topic 数据逻辑名称与寻址入口 名称 + 数据类型必须一致
数据写入者 DataWriter 发布端写数据实体 由 Publisher 管理
数据读取者 DataReader 订阅端读数据实体 由 Subscriber 管理
分区 Partition 域内二次逻辑分组 读写者需有共同分区
服务质量 QoS 控制可靠性、历史、时限等 不兼容会导致匹配失败
学习 DDS 基础概念时,建议按“域 → 主题 → 分区 → 读写者 → QoS”的顺序建立心智模型。先确定隔离边界,再确定数据对象,最后确定传输约束。这样在后续配置 QoS 和排查通信问题时,思路会清晰很多。