名词解释
| 缩略词 | 全称 | 详细解释 | 应用场景 |
|---|---|---|---|
| WLP | Writer Liveliness Protocol | 用于检测发布者(Writer)存活状态的协议,通过定期发送心跳信号实现 | 故障检测、QoS中的LIVELINESS策略配置 |
| CDR | Common Data Representation | DDS标准定义的数据编码格式,支持跨平台数据序列化(大端序/小端序自适应) | 网络传输、持久化存储、跨语言(C++/Java等)数据交换 |
| RTPS | Real-Time Publish-Subscribe | DDS底层通信协议,基于UDP实现实时发布订阅模式 | 设备发现、数据路由 |
| QoS | Quality of Service | 服务质量策略集合(如可靠性、延迟约束等) | 配置数据传输的可靠性(RELIABLE/BEST_EFFORT)、持久性(DURABILITY)等 |
| GUID | Global Unique Identifier | 全局唯一标识符,格式:Prefix[12字节] + EntityId[4字节] | 唯一标识DDS域中的参与者和终结点 |
| DCPS | Data-Centric Publish-Subscribe | DDS核心API层,提供主题(Topic)、数据写入(DataWriter)/读取(DataReader)等抽象 | 应用程序开发接口 |
| DDSI-RTPS | DDS Interoperability Wire Protocol | 保证不同DDS实现间互操作的网络协议规范 | 多厂商DDS系统集成(如RTI Connext与Fast DDS通信) |
| IDL | Interface Definition Language | 一种用于定义接口、数据类型和常量的语言无关描述语言,常用于分布式系统(如CORBA)或生成C++、Java、Python等语言的代码 |
DDS 是什么
Data Distribution Service (DDS) 是一种由 Object Management Group (OMG) 制定的用于实时系统的数据分发标准,是一种中间件协议和 API,旨在实现高效、可靠、可扩展的机器对机器(M2M)通信。它基于发布-订阅(Publish-Subscribe) 模型,特别适用于需要实时、高性能、分布式数据交换的场景。
出现背景
DDS 的开发始于 2001 年,OMG 在 2004 年发布了 DDS 1.0 版本,随后在 2005 年、2007 年和 2015 年分别发布了 1.1、1.2 和 1.4 版本。DDS 的出现是为了应对传统网络编程在复杂分布式系统中的挑战,尤其是在以下领域:
实时性要求高:如航空航天、国防、自动驾驶、医疗设备、机器人和智能电网管理。
分布式系统复杂性:传统网络通信需要手动管理连接、地址配置和数据格式,开发复杂且易出错。
高可靠性与可扩展性:传统中间件(如消息队列或基于 TCP 的通信)在高负载或动态网络环境中难以满足低延迟和高可靠性的需求。
解决的问题
DDS 通过以下特性解决了分布式实时系统的核心问题:
- 解耦发布者和订阅者:应用程序无需知道彼此的存在或位置,DDS 中间件自动处理消息传递,降低开发复杂性。
- 实时性与低延迟:基于 UDP 的 RTPS(Real-Time Publish-Subscribe)协议,延迟可低至数百微秒,适合实时应用。
- 可扩展性:支持从嵌入式设备到云端的大规模系统,动态发现新节点,适应网络变化。
灵活的 QoS(服务质量):通过丰富的 QoS 策略,满足不同场景对可靠性、优先级、延迟等的特定需求。 - 安全性:支持认证、加密和访问控制,适用于安全敏感场景。
- 数据中心化:DDS 关注数据本身,允许定义数据结构和类型,支持内容过滤,减少不必要的数据传输。
DDS 广泛应用于国防(雷达、导航系统)、航空(空中交通控制)、工业 IoT(风力发电场)、机器人(ROS 2 默认中间件)以及自动驾驶等领域。例如,NASA 在肯尼迪航天中心使用 DDS 进行发射控制,波音和空客将其用于航空系统。
架构模型

DDS 的架构基于数据中心发布-订阅(Data-Centric Publish-Subscribe, DCPS)模型,核心理念是将数据组织在一个虚拟的全局数据空间(Global Data Space, GDS) 中,应用程序通过发布和订阅“主题(Topic)”交换数据。以下是关键概念:
Domain(域)
Domain 是 DDS 的逻辑通信范围,每个 Domain 由一个唯一的 Domain ID(整数) 标识。属于同一个 Domain 的参与者(Domain Participant)可以发现并通信,不同 Domain 之间的数据完全隔离。
作用:
- 用于划分不同的通信场景或系统实例,例如测试环境和生产环境可以用不同的 Domain ID。
- Domain 是 DDS 中的“虚拟总线”,所有发布和订阅行为都发生在特定 Domain 内。
特点:
- 一个应用程序可以加入多个 Domain。
- 同一个 Domain ID 的所有参与者共享发现信息和主题。
- 不同 Domain 之间天然隔离,提升安全性和模块化。
Participation(参与者)
Domain Participant(域参与者):DDS 中的顶级实体,表示一个应用程序参与 DDS 域(Domain)的实例。每个域由唯一的域 ID 标识,域是逻辑隔离的通信空间,域内参与者共享数据,域间数据不互通。
作用:
- 域参与者负责创建发布者(Publisher)、订阅者(Subscriber)、主题(Topic)等,是应用程序与 DDS 中间件的接口。
特点:
- 支持动态发现,自动识别域内其他参与者,无需手动配置 IP 地址或端口。
端口(Port)
DDS 通常基于 RTPS(Real-Time Publish-Subscribe) 协议,使用 UDP 传输。端口在 DDS 中主要用于:
- 动态发现:通过特定的多播地址和端口(如默认 7400 端口)进行参与者和端点的发现。
- 数据传输:每个参与者分配一组端口用于发送和接收数据,端口号由域 ID 和参与者 ID 计算,遵循 RTPS 规范。
- 端口分配:RTPS 协议定义了端口映射规则,确保不同参与者、主题的数据通信不冲突。例如,参与者使用多播端口(如 239.255.0.1)进行发现,单播端口用于数据传输。
端口计算方式
基端口号(PB):默认7400。
域增益(DG):通常为250。
参与者增益(PG):通常为2。
偏移(d0, d1, d2, d3):
多播发现(d0:0)
单播发现(d1:10)
多播用户数据(d2:1)
单播用户数据(d3:11)。
对于Domain ID = 0,Participant ID = 0:
发现多播端口:PB + DG * Domain_ID + d0 = 7400 + 250 * 0 + 0 = 7400
发现单播端口:PB + DG * Domain_ID + d1 + PG * Participant_ID = 7400 + 250 * 0 + 10 + 2 * 0 = 7410
用户数据多播端口:PB + DG * Domain_ID + d2 = 7400 + 250 * 0 + 1 = 7401
用户数据单播端口:PB + DG * Domain_ID + d3 + PG * Participant_ID = 7400 + 250 * 0 + 11 + 2 * 0 = 7411
socket 配置
- Discovery traffic type - multicast - unicast - User traffic type - multicast - unicast
mvbs中配置了三个socket:(不同的配置,对应不同的逻辑,比如组播socket只接收) 发现组播socket: 发送/接收组播 发现单播socket: 接收发现单播 数据单播socket: 发送/接收数据单播, 发送发现单播
内置 Topic(Built-in Topics)
定义:DDS 提供内置主题,用于管理元数据和系统状态,如参与者、发布者、订阅者的信息。
作用:
- 内置主题由 DDS 中间件自动发布和订阅,用于动态发现和管理。
例如,Participant 主题记录域内所有参与者的信息,Topic 描述主题信息。
作用:
- 支持动态发现、监控系统状态(如新节点加入或退出),确保系统无需手动配置即可运行。
服务发现
分布式系统中的参与者(Participants)通过特定的机制发现彼此的存在、Topic、DataWriter 和 DataReader,以便建立数据通信。DDS 使用 RTPS(Real-Time Publish-Subscribe)协议 来实现发现过程,主要包括两种发现机制:Participant Discovery(参与者发现) 和 Endpoint Discovery(端点发现)
DDS 发现机制概述
DDS 的发现过程确保系统中的所有参与者能够动态地找到彼此并匹配发布者和订阅者的需求,而无需手动配置网络地址。发现过程分为两个阶段:
- Participant Discovery Phase (PDP):
- 目的:发现网络中的其他 DomainParticipant(域参与者)。
- 机制:每个 DomainParticipant 周期性地广播自己的信息(包括唯一标识 GUID、IP 地址、端口等)。
- 数据:通过 SPDP(Simple Participant Discovery Protocol) 发送 SPDPdiscoveredParticipantData 数据,包含参与者的元信息。
- 方式:通常使用多播(Multicast)或单播(Unicast)发送到预定义的地址(如 RTPS 默认的多播地址 239.255.0.1)。
- Endpoint Discovery Phase (EDP):
- 目的:发现其他参与者的 DataWriter 和 DataReader(统称为端点)。
- 机制:在 PDP 完成后,参与者交换其 DataWriter 和 DataReader 的信息(如 Topic 名称、数据类型、QoS 设置等)。
- 数据:通过 SEDP(Simple Endpoint Discovery Protocol) 发送端点信息,例如 DiscoveredWriterData 和 DiscoveredReaderData。
- 匹配:DataWriter 和 DataReader 根据 Topic 名称、数据类型和 QoS 兼容性进行匹配。
发送 Discovery 的过程
在 DDS 中,发送 Discovery 数据的具体流程如下:
- 启动 DomainParticipant:
- 当一个应用启动并创建一个 DomainParticipant 时,它会加入一个特定的 Domain(域 ID)。
- 参与者会自动初始化内置的 DataWriter 和 DataReader,用于发送和接收发现数据。
- 发送 SPDP 数据(参与者发现):
- 参与者通过内置的 SPDP DataWriter 周期性地发送 SPDPdiscoveredParticipantData。
- 数据内容包括:
- 参与者的 GUID(全局唯一标识符)。
- 网络地址(IP 和端口)。
- QoS 设置(如租约持续时间 LeaseDuration)。
- 发送方式:
- 默认使用多播地址(239.255.0.1,端口由 Domain ID 和 RTPS 规则确定)。
- 如果多播不可用,可以配置为单播。
- 频率:根据 QoS 中的 LeaseDuration 设置,周期性发送以保持“存活”状态。
- 发送 SEDP 数据(端点发现):
- 一旦两个参与者通过 SPDP 发现彼此,它们会通过内置的 SEDP DataWriter 发送各自的端点信息。
- 数据内容包括:
- DataWriter 的信息:Topic 名称、数据类型、QoS、是否带 Key(例如 Writer with Key 的 entityKind 为 0x02 或 0xc2)。
- DataReader 的信息:类似的元数据。
- 匹配过程:接收方根据 Topic、数据类型和 QoS 检查是否匹配,匹配成功后建立通信。
- 持续更新:
- 发现数据会定期发送,以处理动态加入或离开的参与者。
- 如果某个参与者停止发送发现数据(超过 LeaseDuration),其他参与者会认为它已离线。
QoS(Quality of Service)
QoS(服务质量) 是 DDS 的核心特性,允许开发者通过配置参数控制数据通信的行为。DDS 定义了约 23 种 QoS 策略,适用于域参与者、主题、发布者、订阅者、DataWriter 和 DataReader。以下是主要 QoS 策略:
可靠性(Reliability):
- RELIABLE:保证数据可靠送达,类似 TCP,适用于关键数据。
- BEST_EFFORT:尽力而为,类似 UDP,适用于对延迟敏感的场景。
持久性(Durability):
- VOLATILE:数据不存储,适用于瞬时数据。
- TRANSIENT/PERSISTENT:支持晚加入的订阅者获取历史数据。
延迟预算(Latency Budget):定义数据传输的期望延迟。
优先级(Transport Priority):为重要数据分配更高优先级。
寿命(Lifespan):设置数据有效期,过期数据自动丢弃。
历史(History):控制保留的数据样本数量(如 KEEP_LAST 或 KEEP_ALL)。
安全性(Security):支持认证、加密和访问控制。
QoS 提供细粒度的控制,开发者可根据应用需求(如实时性、可靠性、资源限制)优化通信行为。例如,自动驾驶系统可能要求低延迟和高可靠性,而日志系统可能优先持久性。
IDL
DDS 的数据格式基于接口定义语言(IDL),用于定义主题的数据结构, 有如下特点:
- 类型安全:通过 IDL 定义强类型数据(如结构体、枚举、数组),确保发布者和订阅者使用一致的格式。
- 可扩展性:DDS-XTypes 支持动态类型,允许运行时修改数据结构。
- 内容过滤:订阅者可通过 QoS 设置内容过滤器,仅接收感兴趣的数据,减少带宽浪费。
- 序列化格式:使用 CDR 标准,支持跨平台传输,兼容 C++、Java、Python 等语言。
示例 IDL:
struct SensorData {
long id;
float temperature;
float humidity;
};
此 IDL 定义了一个主题 SensorData,包含传感器 ID、温度和湿度字段,发布者将其序列化为字节流,订阅者反序列化后使用。
Serialization
为什么需要序列化
序列化是将数据对象转换为字节流以便在网络上传输的过程。DDS 使用序列化来:
- 统一数据表示:DDS 支持跨平台(不同操作系统、编程语言、硬件架构)通信,序列化确保数据以标准格式传输。
- 高效传输:序列化后的字节流紧凑,减少网络带宽占用。
- 数据类型支持:DDS 使用接口定义语言(IDL)定义数据结构,序列化确保复杂数据类型(如结构体、数组)正确传递。
如果不使用序列化,可能面临以下问题:
- 跨平台不兼容:不同系统的内存布局(如字节序)不同,数据无法正确解析。
- 传输效率低:未序列化的数据可能包含冗余信息,增加带宽消耗。
- 数据完整性问题:没有标准格式,可能导致数据丢失或错误解析。
- 开发复杂性:开发者需手动处理数据格式转换,增加编码和调试难度。
DDS 通过 CDR(Common Data Representation) 标准进行序列化,支持高效、跨平台的传输。DDS-XTypes 扩展进一步定义了动态类型系统,允许运行时调整数据结构。
序列化与压缩算法
todo
RTPS
entityKind
| Kind of Entity | User-defined Entity | Built-in Entity |
|---|---|---|
| unknown | 0x00 | 0xc0 |
| Participant | N/A | 0xc1 |
| Writer (with Key) | 0x02 | 0xc2 |
| Writer (no Key) | 0x03 | 0xc3 |
| Reader (no Key) | 0x04 | 0xc4 |
| Reader (with Key) | 0x07 | 0xc7 |
| Writer Group | 0x08 | 0xc8 |
| Reader Group | 0x09 | 0xc9 |
Predefined EntityIds
| Entity | Corresponding value for entityId_t (NAME = value) |
|---|---|
| participant | ENTITYID_PARTICIPANT = {{00,00,01},c1} |
| SEDPbuiltinTopicWriter | ENTITYID_SEDP_BUILTIN_TOPICS_ANNOUNCER ={{00,00,02},c2} |
| SEDPbuiltinTopicReader | ENTITYID_SEDP_BUILTIN_TOPICS_DETECTOR ={{00,00,02},c7} |
| SEDPbuiltinPublicationsWriter | ENTITYID_SEDP_BUILTIN_PUBLICATIONS_ANNOUNCER ={{00,00,03},c2} |
| SEDPbuiltinPublicationsReader | ENTITYID_SEDP_BUILTIN_PUBLICATIONS_DETECTOR ={{00,00,03},c7} |
| SEDPbuiltinSubscriptionsWriter | ENTITYID_SEDP_BUILTIN_SUBSCRIPTIONS_ANNOUNCER ={{00,00,04},c2} |
| SEDPbuiltinSubscriptionsReader | ENTITYID_SEDP_BUILTIN_SUBSCRIPTIONS_DETECTOR ={{00,00,04},c7} |
| SPDPbuiltinParticipantWriter | ENTITYID_SPDP_BUILTIN_PARTICIPANT_ANNOUNCER ={{00,01,00},c2} |
| SPDPbuiltinParticipantReader | ENTITYID_SPDP_BUILTIN_PARTICIPANT_DETECTOR ={{00,01,00},c7} |
| BuiltinParticipantMessageWriter | ENTITYID_P2P_BUILTIN_PARTICIPANT_MESSAGE_WRITER ={{00,02,00},c2} |
| BuiltinParticipantMessageReader | ENTITYID_P2P_BUILTIN_PARTICIPANT_MESSAGE_READER = {{00,02,00},c7} |
SubmessageId
| Type | val | function |
|---|---|---|
| RTPS_HE | 0x00 | HeaderExtension |
| PAD | 0x01 | Pad |
| ACKNACK | 0x06 | AckNack |
| HEARTBEAT | 0x07 | Heartbeat |
| GAP | 0x08 | Gap |
| INFO_TS | 0x09 | InfoTimestamp |
| INFO_SRC | 0x0c | InfoSource |
| INFO_REPLY_IP4 | 0x0d | InfoReplyIp4 |
| INFO_DST | 0x0e | InfoDestination |
| INFO_REPLY | 0x0f | InfoReply |
| NACK_FRAG | 0x12 | NackFrag |
| HEARTBEAT_FRAG | 0x13 | HeartbeatFrag |
| DATA | 0x15 | Data |
| DATA_FRAG | 0x16 | DataFrag |
ParameterID
| Name | ID | Type |
|---|---|---|
| PID_PAD | 0x0000 | N/A |
| PID_SENTINEL | 0x0001 | N/A |
| PID_USER_DATA | 0x002c | UserDataQosPolicy |
| PID_TOPIC_NAME | 0x0005 | string<256> |
| PID_TYPE_NAME | 0x0007 | string<256> |
| PID_GROUP_DATA | 0x002d | GroupDataQosPolicy |
| PID_TOPIC_DATA | 0x002e | TopicDataQosPolicy |
| PID_DURABILITY | 0x001d | DurabilityQosPolicy |
| PID_DURABILITY_SERVICE | 0x001e | DurabilityServiceQosPolicy |
| PID_DEADLINE | 0x0023 | DeadlineQosPolicy |
| PID_LATENCY_BUDGET | 0x0027 | LatencyBudgetQosPolicy |
| PID_LIVELINESS | 0x001b | LivelinessQosPolicy |
| PID_RELIABILITY | 0x001a | ReliabilityQosPolicy3 |
| PID_LIFESPAN | 0x002b | LifespanQosPolicy |
| PID_DESTINATION_ORDER | 0x0025 | DestinationOrderQosPolicy |
| PID_HISTORY | 0x0040 | HistoryQosPolicy |
| PID_RESOURCE_LIMITS | 0x0041 | ResourceLimitsQosPolicy |
| PID_OWNERSHIP | 0x001f | OwnershipQosPolicy |
| PID_OWNERSHIP_STRENGTH | 0x0006 | OwnershipStrengthQosPolicy |
| PID_PRESENTATION | 0x0021 | PresentationQosPolicy |
| PID_PARTITION | 0x0029 | PartitionQosPolicy |
| PID_TIME_BASED_FILTER | 0x0004 | TimeBasedFilterQosPolicy |
| PID_TRANSPORT_PRIORITY | 0x0049 | TransportPriorityQoSPolicy |
| PID__DOMAIN_ID | 0x000f | DomainId_t |
| PID_DOMAIN_TAG | 0x4014 | string<256> |
| PID_PROTOCOL_VERSION | 0x0015 | ProtocolVersion_t |
| PID_VENDORID | 0x0016 | VendorId_t |
| PID_UNICAST_LOCATOR | 0x002f | Locator_t |
| PID_MULTICAST_LOCATOR | 0x0030 | Locator_t |
| PID_DEFAULT_UNICAST_LOCATOR | 0x0031 | Locator_t |
| PID_DEFAULT_MULTICAST_LOCATOR | 0x0048 | Locator_t |
| PID_METATRAFFIC_UNICAST_LOCATOR | 0x0032 | Locator_t |
| PID_METATRAFFIC_MULTICAST_LOCATOR | 0x0033 | Locator_t |
| PID_EXPECTS_INLINE_QOS | 0x0043 | boolean |
| PID_PARTICIPANT_MANUAL_LIVELINESS_COUNT | 0x0034 | Count_t |
| PID_PARTICIPANT_LEASE_DURATION | 0x0002 | Duration_t |
| PID_CONTENT_FILTER_PROPERTY | 0x0035 | ContentFilterProperty_t |
| PID_PARTICIPANT_GUID | 0x0050 | GUID_t |
| PID_GROUP_GUID | 0x0052 | GUID_t |
| PID_GROUP_ENTITY_ID | 0x0053 | EntityId_t |
| PID_BUILTIN_ENDPOINT_SET | 0x0058 | BuiltinEndpointSet_t |
| PID_BUILTIN_ENDPOINT_QOS | 0x0077 | BuiltinEndpointQos_t |
| PID_PROPERTY_LIST | 0x0059 | sequence |
| PID_TYPE_MAX_SIZE_SERIALIZED | 0x0060 | long |
| PID_ENTITY_NAME | 0x0062 | EntityName_t |
| PID_ENDPOINT_GUID | 0x005a | GUID_t |
网络报文
tcp头部

数据偏移:数据部分往后偏移,这是TCP的头部长度字段,代表有多少个32bit,即1代表32bit,2代表64bit。
一共4位最大为15,15×32=480b=60B。所以头部最大为60个字节,最小为20字节。TCP头部的长度可以变化,因此可以承载更多的数据。
注意这里必须使用32位的数据,用来快速定位数据部分的位置,所以没有那么多数据的话需要在后面进行填充
udp头部

- UDP最小长度:即无数据的情况下,仅首部长度8字节。
- 数据部分最小长度:即无数据的情况下,0字节。
- UDP最大长度:因长度字段16位限制,所以最大长度为2(16)-1=65535字节。
- 数据部分最大长度:即UDP最大长度-IP最小头部-UDP头部,2(16)-1-20-8=65507字节
ip头部


总长度: 指整个数据报的长度(以字节为单位)。占16bit,最大长度为65535字节。
协议:标识了上层所使用的协议
01 ICMP 02 IGMP 06 TCP 17 UDP 88 IGRP 89 OSPF
数据链路层头部

最小帧:64字节,这是根据CSMA/CD的标准推导出。 帧头部:14字节,6字节目的地址+6字节源地址+2字节长度/类型=14字节。 帧中最小数据:64字节-6字节目的地址-6字节源地址-2字节长度/类型-4字节FCS字段(CRC)=**46字节**。 最大数据:即MTU=1500字节。 最大帧:MTU+帧头部14字节+FCS4字节=1518字节。 以上均未考虑前导码。
有效数据长度

链路层传输字段的最小长度是64,链路协议占了18,64-18=46,即为最小数据长度
MTU的长度:
TCP最大数据长度:1500-20-20 = 1460(ipv4) 1440(ipv6)
UDP最大数据长度:1500-20-8 = 1472(ipv4) 1452(ipv6)
参考资料
cmake -G “Unix Makefiles” -DBUILD_EXAMPLES=ON ..
车载消息中间件FastDDS 源码解析(一)FastDDS 介绍和使用 c++版本源码分析

评论