4. DDS通信模型

DDS(Data Distribution Service)的核心价值在于“以数据为中心”的通信抽象。它不要求开发者直接操作套接字、连接或消息队列,而是把通信双方建模为对“主题”感兴趣的发布与订阅角色。理解 DDS 通信模型,关键是分清六个概念:发布者(Publisher)、数据写入者(DataWriter)、订阅者(Subscriber)、数据读取者(DataReader)、主题(Topic)与数据对象(Data Object)。它们不是并列的六个孤立名词,而是从“域参与者”到“具体数据样本”的层级化协作关系。

DDS 通信模型可以概括为:DomainParticipant 是入口,Publisher/Subscriber 是管理容器,DataWriter/DataReader 是类型化端点,Topic 是数据契约,数据对象是运行时承载的样本。发布端写入的是“某个主题下的数据对象”,订阅端读取到的也是“同一主题下的数据对象”。

一、核心角色与层级关系

在一个 DDS 域(Domain)中,每个应用进程通常先创建 DomainParticipant。它是应用与 DDS 中间件交互的门面,负责注册类型、创建主题、创建发布者和订阅者。发布者与订阅者本身并不直接传输用户数据,它们更像“工厂”或“容器”:发布者用于创建和管理 DataWriter,订阅者用于创建和管理 DataReader。

DataWriter 是发布端的类型化写入端点。它绑定到一个 Topic,并知道如何把用户定义的数据类型序列化后交给中间件。调用 write() 时,写入的是一个具体的数据对象实例,而不是一段无类型字节流。DataReader 是订阅端的类型化读取端点,同样绑定到 Topic,负责接收、缓存并按类型反序列化数据对象,供应用通过 take() 或 read() 获取。

Topic 是发布与订阅之间的契约。它由“主题名 + 数据类型”共同定义。只有主题名和数据类型都匹配的 DataWriter 与 DataReader,才可能建立关联并交换数据。数据对象则是该主题下实际传输的样本,例如一条温度读数、一帧雷达目标或一个订单状态。数据对象可以带 Key,从而形成多个实例(Instance),DDS 能按实例维护状态与生命周期。

DDS 通信模型:角色层级与数据流 DomainParticipant 域入口:注册类型、创建 Topic / Publisher / Subscriber 发布端 Publisher 管理容器:QoS 分组、创建 DataWriter DataWriter 类型化写入端点:write() / dispose() 数据对象(样本) 带 Key 的实例:温度=25.3℃ 序列化 → 中间件发送 订阅端 Subscriber 管理容器:QoS 分组、创建 DataReader DataReader 类型化读取端点:take() / read() 数据对象(样本) 反序列化后的实例:温度=25.3℃ 缓存 → 应用读取 Topic 主题名 + 数据类型 发布/订阅的契约 名称与类型必须匹配 数据对象经中间件传输 发布端角色 订阅端角色 Topic 契约 数据对象 数据流

二、发布与订阅的匹配流程

DDS 的匹配不是“先建连接再发数据”,而是“先声明兴趣,再自动匹配”。发布端创建 DataWriter 时,会向域内声明“我在某个 Topic 上发布数据”;订阅端创建 DataReader 时,会声明“我在某个 Topic 上订阅数据”。中间件根据 Topic 名称、数据类型以及 QoS 策略进行匹配,匹配成功后自动建立通信路径。

  1. 注册类型:发布端和订阅端都向 DomainParticipant 注册相同的数据类型,确保序列化格式一致。
  2. 创建 Topic:以“主题名 + 类型名”创建 Topic,作为双方共同的数据契约。
  3. 创建端点:发布端通过 Publisher 创建 DataWriter,订阅端通过 Subscriber 创建 DataReader。
  4. QoS 匹配:中间件比较双方的可靠性、持久性、历史深度、截止时间等 QoS,判断是否兼容。
  5. 数据交换:匹配成功后,DataWriter 写入的数据对象被传输到 DataReader 的缓存中,应用通过 take/read 获取。

三、关键概念对照表

概念 所在端 核心职责 典型操作
DomainParticipant 两端 域入口,管理类型、Topic、Publisher/Subscriber register_type、create_topic
Publisher 发布端 发布管理容器,组织 QoS 与 DataWriter create_datawriter
DataWriter 发布端 类型化写入端点,发送数据对象 write、dispose、unregister
Subscriber 订阅端 订阅管理容器,组织 QoS 与 DataReader create_datareader
DataReader 订阅端 类型化读取端点,接收并缓存数据对象 take、read、on_data_available
Topic 两端共享 数据契约:主题名 + 数据类型 名称与类型匹配
数据对象 两端 实际传输的样本,可带 Key 形成实例 赋值、写入、读取

四、案例场景:温度监控系统

假设一个温度监控系统:传感器节点发布温度数据,监控中心订阅温度数据。传感器端创建 DomainParticipant,注册 Temperature 类型,创建名为 TempTopic 的 Topic,再通过 Publisher 创建 DataWriter。监控中心端创建同域 Participant,注册相同类型,创建同名 Topic,通过 Subscriber 创建 DataReader。

// 发布端伪代码
participant = DomainParticipant(domain_id=0)
participant.register_type(Temperature)
topic = participant.create_topic("TempTopic", Temperature)
publisher = participant.create_publisher()
writer = publisher.create_datawriter(topic)
writer.write(Temperature(sensor_id="S01", value=25.3))

// 订阅端伪代码
participant = DomainParticipant(domain_id=0)
participant.register_type(Temperature)
topic = participant.create_topic("TempTopic", Temperature)
subscriber = participant.create_subscriber()
reader = subscriber.create_datareader(topic)
samples = reader.take()

当 writer.write() 被调用时,一个温度数据对象被发布。中间件根据 Topic 和 QoS 匹配到订阅端的 DataReader,将其放入接收缓存。监控中心通过 take() 取出样本,得到 sensor_id="S01", value=25.3。整个过程应用层没有出现 IP 地址、端口或连接管理,这正是 DDS 以数据为中心的体现。

理解要点:Publisher/Subscriber 是“管理容器”,DataWriter/DataReader 是“通信端点”,Topic 是“契约”,数据对象是“货物”。容器负责组织与 QoS,端点负责收发,契约负责匹配,货物才是真正被传递的内容。

五、常见误区与复盘

  • 误区一:把 Publisher 当成发送者。Publisher 本身不发送数据,真正发送的是它创建的 DataWriter。Publisher 更像一个分组管理器和 QoS 载体。
  • 误区二:认为 Topic 只是一个字符串。Topic 必须同时包含名称和数据类型,类型不一致时即使名称相同也无法匹配。
  • 误区三:忽略数据对象的实例概念。带 Key 的数据对象会形成多个实例,DDS 会按实例维护生命周期状态,如 ALIVE、NOT_ALIVE_DISPOSED 等。
  • 误区四:认为 DataReader 收到数据就等于应用已处理。DataReader 只是把数据放入缓存,应用必须调用 take/read 才能真正取走并处理。
  • 误区五:QoS 不匹配却误以为通信失败是网络问题。很多“收不到数据”的情况,根源是 Reliability、Durability 或 Deadline 等 QoS 不兼容。

复盘时可以用一句话检查模型是否用对:“我是在正确的 DomainParticipant 下,用正确的 Topic 和类型,通过 DataWriter/DataReader 交换数据对象吗?”如果这四要素都正确,再进一步排查 QoS 匹配与网络传输,就能快速定位问题。