DDS DCPS 规范:实体模型与 QoS 策略
DCPS(Data-Centric Publish-Subscribe,以数据为中心的发布/订阅)规范是 DDS 规范族最初也是最核心的规范,在某些场合 DDS 规范指的就是 DCPS。本文整理 DCPS 的两块核心内容:实体模型(应用如何通过一组实体接入 DDS 通信,其中 DomainParticipant 是整个体系的入口)与 QoS 策略(DDS 1.4 定义的 22 种标准策略及其分组)。读完可以对 DDS 编程模型的整体结构建立认识,并掌握实体生命周期与 QoS 配置的常见实践。
DCPS 的内容构成
DCPS 规范的内容分为三大块:
- 模型描述:介绍模型中涉及的概念,即下文的实体模型与数据描述;
- 接口描述:分模块定义类并定义接口形式及接口功能描述;
- QoS 定义:定义 22 种 QoS 的功能及其配置方式。
实体模型
DCPS 实体模型定义了三层实体(参与者层、发布/订阅/主题层、读写者层),层级关系如下:
各实体的职责:
- Domain:代表一个通信平面,由 Domain ID 唯一标识,只有同一个域内的通信实体才可以通信。如果考虑车内通信,可以只划分 1 个 Domain,也可以按照交互规则或其他规则定义多个 Domain。
- DomainParticipant:代表域内通信的应用程序的本地成员身份,可以理解为"某个进程(或应用实例)加入到一个 DDS 通信域后的代表对象"。它是 DDS 实体体系的根节点和入口,负责把应用纳入 DDS 的发现与通信机制中。
- Topic:数据的抽象概念,由 TopicName 标识并关联相应的数据类型(DataType)。把域内所有 Topic 集合在一起,就形成一个虚拟的全局数据空间(Global Data Space),进一步弱化了节点的概念。
- Publisher:发布者,发布主题数据,至少与 1 个 DataWriter 关联,通过调用 DataWriter 的相关函数将数据发出去。
- Subscriber:订阅者,订阅主题数据,至少与 1 个 DataReader 关联。数据到达时,应用程序可能在忙于执行其他操作,也可能正在等待该消息,因此存在同步访问和异步通知两种方式。
- DataWriter:数据写入者,类似缓存,把需要发布的主题数据从应用层写入 DataWriter。
- DataReader:数据读取者,同样可以理解为一种缓存,从订阅者得到主题数据,随之传给应用层。
DomainParticipant:通信的入口
没有 DomainParticipant,应用就无法接入 DDS 网络,无法进行服务发现,也无法收发数据。这一节展开它的定位、使用条件与工程实践。
域与参与者
| 概念 | 定义 | 类比 |
|---|---|---|
| Domain(域) | 一个逻辑隔离的通信空间,由唯一的 domain_id 标识。不同 Domain 之间默认互不发现、互不通信。 | 房间号,不同的房间互不相通。 |
| DomainParticipant | 应用在某个 domain_id 中的具体实例。创建它意味着该进程正式"进入"了该房间。 | 进入房间的人,只有进了房间才能和其他人交流。 |
DomainParticipant 是应用接入 DDS 发现机制与通信机制的唯一凭证。
核心职责
- 身份标识与归属:绑定域 ID,声明"我属于哪个域";携带实现相关的本地信息(如 GUID、QoS 策略),供其他参与者发现和匹配。
- 发现与资源管理的边界:DDS 的自动发现机制通常以 Participant 为粒度进行——对外广播自己的存在并监听其他 Participant 的加入;对内托管其下属所有 DDS 实体(Topic、Publisher、Subscriber、DataWriter、DataReader),可以视为 DDS 在该进程中的运行时上下文。
- 工厂角色:Participant 充当工厂,负责创建 Topic、Publisher 和 Subscriber。
- 生命周期管理:标准 DDS(RTI Connext、Fast DDS 等)要求先调用
delete_contained_entities()(或逐个删除子实体)之后,才能通过DomainParticipantFactory::delete_participant()删除参与者;直接删除仍含有子实体的 Participant 会失败(返回RETCODE_PRECONDITION_NOT_MET)。部分实现的封装接口会代为自动清理,但不是规范行为。
通信建立的必要条件
两个应用想要通过 DDS 成功通信,必须同时满足以下条件,其中第一条直接由 DomainParticipant 决定:
- 相同的
domain_id:如果两个 Participant 不在同一个 Domain ID 下,它们甚至无法互相"看见",更无法建立连接。 - Topic 匹配:Topic 名称相同,数据类型(Type)兼容。
- QoS 策略兼容:可靠性(Reliability)、历史深度(History)、截止时间(Deadline)等策略必须匹配。
- 网络与安全:网络可达(UDP/TCP 端口通畅);若启用 DDS Security,需通过身份认证和权限校验。
排查"收不到数据"的问题时,首先应检查双方是否创建了相同 domain_id 的 Participant。
一个进程需要几个 Participant
绝大多数场景下,一个应用程序进程只需要创建一个 DomainParticipant:
- 资源开销小:发现协议流量、内置端点(Built-in Endpoints)、线程池、Socket 连接等资源通常按 Participant 分配。
- 管理简单:配置集中,逻辑清晰。
仅在确有必要时,才在一个进程中创建多个不同 domain_id 或不同配置的 Participant:
- 多域网关:进程需要同时作为桥梁,连接两个不同的逻辑域(例如
domain_id=0和domain_id=1)。 - 强隔离需求:需要在同一进程内隔离不同的安全身份、完全不同的 QoS 配置或独立的发现组。
代价是资源消耗成倍增加(内存、线程、网络端口),配置复杂度与调试难度上升。除非有明确的多域互通或强隔离需求,坚持一个进程一个 Participant。
常见误区
| 误区 | 正确理解 |
|---|---|
| "Participant 就是发布/订阅者" | 错。Participant 是容器;Publisher/Subscriber 才是具体的发布/订阅角色。 |
| "一个进程只能有一个 Participant" | 错。技术上允许创建多个,但通常不建议,除非为了多域隔离。 |
| "Participant 对应物理机器" | 错。它对应的是 DDS 通信栈的一个实例。一台机器上可以运行多个进程,每个进程都有自己的 Participant。 |
| "它是业务逻辑对象" | 错。它是基础设施对象,类似于网络编程中的 Socket 或 Context,而非业务实体。 |
数据描述:主题、实例与样本
| 概念 | 说明 | 标识 |
|---|---|---|
| 主题 | 最大类,域内唯一的主题名称,并关联 1 个数据类型。 | 主题名+类型 |
| 实例 | 主题数据中 key 成员相同的数据的集合,即使用 key 成员进一步区分主题数据,可以理解成"子主题"。 | InstanceHandle_t |
| 样本 | 每次向 DDS 发送一次数据产生一个数据样本。 | 序列号 |
22 种标准 QoS 策略
QoS(Quality of Service)即服务质量。DDS 规范中定义了 22 种标准 QoS 策略(DDS 1.4 规范 §2.2.3),用于精细控制数据通信的行为,涵盖可靠性、持久性、资源管理、发现机制、数据呈现等多个维度。这些策略可应用于不同的 DDS 实体(如 DomainParticipant、Publisher/Subscriber、DataWriter/DataReader、Topic),并支持匹配协商机制以确保通信双方兼容。
与数据传输行为相关的策略
- Reliability(可靠性):控制是否保证数据送达:
BEST_EFFORT(尽力而为)或RELIABLE(可靠传输)。 - Durability(持久性):控制历史数据对晚加入订阅者的可用性:
VOLATILE、TRANSIENT_LOCAL、TRANSIENT、PERSISTENT。 - History(历史记录):定义每个实例保留多少样本:
KEEP_LAST(保留最近 N 个)或KEEP_ALL(保留全部)。 - Deadline(截止时间):要求数据必须在指定周期内更新,否则触发事件。
- LatencyBudget(延迟预算):允许应用指定可接受的最大端到端传输延迟。
- Ownership(所有权):支持独占或共享数据写入权,避免多个发布者冲突。
- OwnershipStrength(所有权强度):配合 Ownership 使用,数值高的 DataWriter 获得写入优先权。
- Liveliness(活跃性):检测 DataWriter 是否"存活":
AUTOMATIC、MANUAL_BY_PARTICIPANT、MANUAL_BY_TOPIC。 - TimeBasedFilter(基于时间的过滤):限制 DataReader 接收数据的最大频率(如每 100ms 最多一次)。
设置为 BEST_EFFORT 就一定不可靠吗?不一定。DDS 里定义的可靠性 QoS 指的是"是否需要 DDS 做额外的可靠保证",比如传输层使用 TCP 或其他底层协议已经保证了可靠传输,这时配置 BEST_EFFORT 同样可以实现可靠的传输。
设置为 RELIABLE 就一定可靠吗?同样不一定。可靠的实现方式是确认重传:在接收方确认之前,数据需要缓存在队列中以备可能的重传,而队列有长度限制(无论存储在内存还是文件中,都受限于存储空间)。当队列到达上限且需要发送新样本时:
- 替换老的样本数据:被替换的数据丢包后无法重传,造成"丢包",无法实现可靠;
- 阻塞等待队列有额外空间:可以实现"感知上"的可靠。
与资源管理与限制相关的策略
- ResourceLimits(资源限制):限制缓存中最大样本数、实例数、每个实例的样本数等。
- Presentation(表示):控制同一 Publisher/Subscriber 下多个 DataWriter/DataReader 的数据呈现顺序和范围(
INSTANCE、TOPIC、GROUP级别)。 - Partition(分区):在 Domain 内创建逻辑子通道,只有相同 Partition 的发布/订阅才能通信。
- DestinationOrder(目标排序):控制 DataReader 接收样本的顺序:按源时间戳(
BY_SOURCE_TIMESTAMP)或接收顺序(BY_RECEPTION_TIMESTAMP)。
与元数据与用户自定义信息相关的策略
- UserData(用户数据):允许在 DomainParticipant、Publisher 等实体上附加任意二进制元数据。
- TopicData(主题数据):附加到 Topic 的元数据,传播给所有基于该 Topic 的读写器。
- GroupData(组数据):附加到 Publisher/Subscriber 的元数据,传播给其下属所有 DataWriter/DataReader。
与生命周期与发现机制相关的策略
- EntityFactory(实体工厂):控制由工厂创建的实体在创建时是否自动启用(
autoenable,默认启用)。 - WriterDataLifecycle(写者数据生命周期):控制 DataWriter 是否在删除时自动注销其实例。
- ReaderDataLifecycle(读者数据生命周期):控制 DataReader 是否自动清除未更新的实例。
- DurabilityService(持久化服务):配置当 Durability 为
TRANSIENT或PERSISTENT时,外部持久化服务的行为(如清理延迟、历史深度等)。
其余标准策略
- TransportPriority(传输优先级):为数据流设置网络传输优先级(依赖底层传输支持)。
- Lifespan(生命周期):指定数据样本的有效生存时间,超时后自动丢弃。
以上 22 种即 DDS 1.4 DCPS 规范定义的标准 QoS 全集。各扩展规范与具体实现还提供额外策略,如 XTypes 的 TypeConsistencyEnforcement(类型一致性强化)、部分实现中用于避免自收的 IgnoreLocal 配置等,均不计入这 22 种之内。
要点
- DCPS 是 DDS 规范族的核心,内容分为模型描述、接口描述、QoS 定义三大块
- 实体模型分三层:DomainParticipant 是根节点和工厂,Publisher/Subscriber 各自关联 DataWriter/DataReader,Topic 关联数据类型
- 通信建立的四个条件:相同 domain_id、Topic 与类型匹配、QoS 兼容、网络与安全可达——排查收不到数据先查 domain_id
- 一个进程默认一个 Participant;多 Participant 仅用于多域网关与强隔离场景
- 删除 Participant 前必须先
delete_contained_entities(),直接删除会返回RETCODE_PRECONDITION_NOT_MET - 22 种标准 QoS 覆盖传输行为、资源管理、元数据、生命周期四个方向;BEST_EFFORT 与 RELIABLE 的实际可靠性都取决于传输层与队列容量