摘要:RTPS(实时发布订阅协议)作为DDS的核心通信协议,实现了跨平台、跨厂商的无障碍通信。其关键技术包括:1)采用PIM/PSM分离架构,支持从嵌入式到服务器的多平台部署;2)使用CDR编码解决字节序差异;3)高效报文结构支持多子消息封装;4)通过PDP和EDP实现去中心化自动发现。开发验证可通过Python(快速原型)或C++(工业级实现),配合Wireshark进行协议分析。RTPS的三大解耦特性(供应商、网络、时间)使其成为车载通信等实时系统的理想选择。(149字)

如果说 DDS 是上层的"调度大脑",那么 RTPS (Real-Time Publish-Subscribe) 就是它那双"强健的腿"。
RTPS 是由 OMG 组织定义的 线缆协议(Wire Protocol) 。它的核心使命只有一个:确保不同厂家开发的 DDS(如 RTI Connext, eProsima FastDDS, CycloneDDS)能在同一个网络里无障碍地"对话"。
1. 跨平台通信的秘诀:PIM 与 PSM 的分离
RTPS 之所以能跨越 Windows、Linux、QNX 甚至是无系统的嵌入式环境,是因为它在设计上采用了平台无关模型 (PIM) 和 平台特定映射 (PSM) 的分离架构。
-
PIM (Platform Independent Model): 定义了 DDS 的逻辑行为(比如:数据怎么分发、QoS 怎么生效、心跳包怎么发)。这一层不关心你是用 UDP 还是串口。
-
PSM (Platform Specific Mapping): 定义了逻辑包如何变成真正的比特流。目前最主流的映射是 RTPS over UDP/IP。
2. 数据的"统一语言":CDR 序列化
不同计算机存储数据的方式不同(比如大端 与小端 字节序)。为了实现跨平台,RTPS 采用了 CDR (Common Data Representation) 编码。
-
对齐规则: CDR 规定了基本数据类型(int, float 等)在流中的对齐方式。
-
自适应字节序: 在 RTPS 报文头中有一个标志位,告诉接收方:"这包数据是用小端格式写的"。如果接收方是高大端的架构(如某些车载微控制器),它会自动进行字节翻转。这保证了 Intel 芯片的 PC 能听懂 NXP 芯片的 ECU 在说什么。
3. RTPS 报文的"套娃"结构
RTPS 的报文结构非常高效,它将复杂的指令拆解为一个个 子消息 (Submessages)。
-
Global Header (16 字节): 包含协议版本、供应商 ID(如
0x010f代表 eProsima)以及 GUID Prefix(唯一标识一个设备/参与者)。 -
Submessages: 一个 UDP 包里可以塞进多个子消息:
-
DATA: 携带实际的业务数据。
-
INFO_TS: 携带时间戳。
-
HEARTBEAT: 告诉订阅者:"我发完数据了,你收到了吗?"
-
ACKNACK: 订阅者回话:"第 5 号包没收到,请重发。"
-
4. 自动发现机制:PDP 与 EDP
DDS 不需要像 SOME/IP 那样依赖一个中心化的 SD 服务。RTPS 通过两层发现流程实现"自组织网络":
A. PDP (Participant Discovery Protocol)
-
动作: 节点启动时,向预定义的组播地址发送 DATA(p) 报文。
-
目的: "打招呼"。告诉大家:我的 IP 是多少,我叫什么名字。
B. EDP (Endpoint Discovery Protocol)
-
动作: 互相认识后,节点开始交换具体的 DATA(r) 和 DATA(w)。
-
目的: "对暗号"。告诉对方:我有一个名为
Vehicle/Speed的话题(Topic),数据类型是float,我的可靠性要求是RELIABLE。如果双方的 QoS 匹配,连接就正式建立了。
5. 跨平台调试工具:Wireshark 过滤器
在分析 TBox 或智驾域控制器的 DDS 流量时,你可以使用以下 Wireshark 过滤器:
-
rtps: 过滤所有的 RTPS 报文。 -
rtps.sm.id == 0x15: 只看 DATA 子消息(业务数据)。 -
rtps.sm.id == 0x07: 只看 HEARTBEAT(检查链路是否稳定)。 -
rtps.parameter.topicName == "YourTopicName": 精确查找某个话题的发现报文。
💡 总结
RTPS 的伟大之处在于它实现了"解耦":
-
供应商解耦: A 公司的 DDS 能跟 B 公司的 DDS 通信。
-
网络解耦: 只要有 UDP,不管物理层是 5G 还是以太网,逻辑都一样。
-
时间解耦: 通过心跳和重传机制,即便网络偶尔波动,数据也能可靠送达。
要在 VS Code 中运行 DDS Demo,最简单的方式是使用 Python (适合快速验证逻辑)或 C++(适合高性能车载开发)。
由于 DDS 是"以数据为中心"的,我们需要定义一个数据类型(IDL)。为了简化,我们以车载场景中的"车速(VehicleSpeed)"为例。
1. Python 版:最快上手 (使用 Cyclone DDS)
Python 不需要复杂的编译过程,非常适合在 VS Code 中配合终端直接运行。
第一步:安装"引擎"与插件
-
终端安装库:
pip install cyclonedds -
VS Code 插件: 确保已安装 Python 插件。
第二步:编写代码
创建一个文件 dds_demo.py,它既可以作为发布者,也可以作为订阅者:
Python
from cyclonedds.domain import DomainParticipant
from cyclonedds.topic import Topic
from cyclonedds.pub import DataWriter
from cyclonedds.sub import DataReader
from cyclonedds.idl import IdlStruct
import time
# 1. 定义数据结构 (类似于车载 IDL)
@IdlStruct
class VehicleSpeed(IdlStruct):
speed: float
# 2. 初始化 DDS 参与者
dp = DomainParticipant()
topic = Topic(dp, "VehicleSpeedTopic", VehicleSpeed)
def run_publisher():
dw = DataWriter(dp, topic)
print("发布者已启动,正在发送车速数据...")
s = 0.0
while True:
sample = VehicleSpeed(speed=s)
dw.write(sample)
print(f"发送数据: {s} km/h")
s += 5.5
time.sleep(1)
def run_subscriber():
dr = DataReader(dp, topic)
print("订阅者已启动,等待数据...")
while True:
for sample in dr.take():
print(f"收到数据: {sample.speed} km/h")
time.sleep(0.5)
# 提示:你可以通过修改这里来切换身份
if __name__ == "__main__":
import sys
if len(sys.argv) > 1 and sys.argv == "sub":
run_subscriber()
else:
run_publisher()
第三步:在 VS Code 终端运行
-
按
Ctrl + ~打开终端。 -
启动订阅者: 输入
python dds_demo.py sub。 -
拆分终端 (点击终端右上角
|图标),在新窗口启动发布者: 输入python dds_demo.py。 -
你会看到两个终端开始通过 DDS 进行实时通信。
2. C++ 版:工业级标准 (使用 eProsima Fast DDS)
C++ 的实现更符合车载 TBox 的真实开发环境,但步骤较多,需要用到 CMake。
第一步:环境准备
-
安装 Fast DDS: 在 Linux (WSL/Ubuntu) 下使用
sudo apt install libfastrtps-dev。 -
VS Code 插件: 安装 C/C++ 和 CMake Tools。
第二步:定义 IDL (VehicleSpeed.idl)
DDS 强制要求先定义接口描述文件:
代码段
struct VehicleSpeed {
float speed;
};
在终端运行 fastddsgen VehicleSpeed.idl,它会自动生成一堆 C++ 头文件和源代码。
第三步:核心代码片段 (Publisher)
在 VS Code 中编写 main.cpp,利用生成的代码:
C++
// 简化后的逻辑伪代码
auto participant = DomainParticipantFactory::get_instance()->create_participant(0, PQOS);
auto topic = participant->create_topic("VehicleSpeedTopic", "VehicleSpeed", TOPIC_QOS);
auto publisher = participant->create_publisher(PUBLISHER_QOS);
auto writer = publisher->create_datawriter(topic, DATAWRITER_QOS);
VehicleSpeed my_speed;
my_speed.speed(60.5);
writer->write(&my_speed); // 数据通过 RTPS 协议发出
3. 为什么在 VS Code 里做这个实验很有意义?
-
观察 RTPS 流量: 运行 Demo 时,打开 Wireshark,使用过滤器
rtps,你可以清晰地看到 DATA 包和 HEARTBEAT 包在后台跳动。 -
验证跨语言通信: 你可以用 C++ 写发布者 ,用 Python 写订阅者 。只要它们的
Topic Name和IDL 结构一致,它们就能完美对话。这正是 RTPS 跨平台能力的体现。 -
模拟网络丢包: 尝试关闭发布者,观察订阅者的状态。或者在分布式环境下(两台电脑),你会发现只要在同一个局域网,它们会自动发现对方。
💡 进阶建议
如果你在运行中发现收不到数据,通常是因为 QoS(服务质量) 不匹配。比如发布者要求"可靠传输(RELIABLE)",而订阅者设置为"尽力而为(BEST_EFFORT)",连接就无法建立。