4. DDS通信模型
DDS(Data Distribution Service)的核心价值在于“以数据为中心”的通信抽象。它不要求开发者直接操作套接字、连接或消息队列,而是把通信双方建模为对“主题”感兴趣的发布与订阅角色。理解 DDS 通信模型,关键是分清六个概念:发布者(Publisher)、数据写入者(DataWriter)、订阅者(Subscriber)、数据读取者(DataReader)、主题(Topic)与数据对象(Data Object)。它们不是并列的六个孤立名词,而是从“域参与者”到“具体数据样本”的层级化协作关系。
一、核心角色与层级关系
在一个 DDS 域(Domain)中,每个应用进程通常先创建 DomainParticipant。它是应用与 DDS 中间件交互的门面,负责注册类型、创建主题、创建发布者和订阅者。发布者与订阅者本身并不直接传输用户数据,它们更像“工厂”或“容器”:发布者用于创建和管理 DataWriter,订阅者用于创建和管理 DataReader。
DataWriter 是发布端的类型化写入端点。它绑定到一个 Topic,并知道如何把用户定义的数据类型序列化后交给中间件。调用 write() 时,写入的是一个具体的数据对象实例,而不是一段无类型字节流。DataReader 是订阅端的类型化读取端点,同样绑定到 Topic,负责接收、缓存并按类型反序列化数据对象,供应用通过 take() 或 read() 获取。
Topic 是发布与订阅之间的契约。它由“主题名 + 数据类型”共同定义。只有主题名和数据类型都匹配的 DataWriter 与 DataReader,才可能建立关联并交换数据。数据对象则是该主题下实际传输的样本,例如一条温度读数、一帧雷达目标或一个订单状态。数据对象可以带 Key,从而形成多个实例(Instance),DDS 能按实例维护状态与生命周期。
二、发布与订阅的匹配流程
DDS 的匹配不是“先建连接再发数据”,而是“先声明兴趣,再自动匹配”。发布端创建 DataWriter 时,会向域内声明“我在某个 Topic 上发布数据”;订阅端创建 DataReader 时,会声明“我在某个 Topic 上订阅数据”。中间件根据 Topic 名称、数据类型以及 QoS 策略进行匹配,匹配成功后自动建立通信路径。
- 注册类型:发布端和订阅端都向 DomainParticipant 注册相同的数据类型,确保序列化格式一致。
- 创建 Topic:以“主题名 + 类型名”创建 Topic,作为双方共同的数据契约。
- 创建端点:发布端通过 Publisher 创建 DataWriter,订阅端通过 Subscriber 创建 DataReader。
- QoS 匹配:中间件比较双方的可靠性、持久性、历史深度、截止时间等 QoS,判断是否兼容。
- 数据交换:匹配成功后,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 当成发送者。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 匹配与网络传输,就能快速定位问题。