← 返回博客列表

一、FastDDS 概念介绍

1.1 什么是 FastDDS

FastDDS 是由 eProsima 公司开源的 C++ 实现 DDS(Data Distribution Service)中间件,它完整实现了 OMG(Object Management Group)发布的 DDS-RTPS(Real-Time Publish-Subscribe)线协议。FastDDS 是 ROS 2 的默认通信中间件(rmw_fastrtps),广泛应用于机器人、自动驾驶、工业控制等需要高性能实时通信的领域。

核心定义
FastDDS = DDS 标准实现 + RTPS 线协议 + 发布/订阅模型 + 丰富 QoS 策略

关键标准
· DDS 1.4 标准(OMG 数据分发服务规范)
· DDS-RTPS 2.5 标准(线协议,保证不同厂商 DDS 互操作)
· DDS-XTypes(动态数据类型)
· DDS-Security 1.1(安全模型)

1.2 核心架构层次

FastDDS 采用分层架构设计,从下到上依次为:

1.3 DDS 核心实体模型

DDS 标准定义了一组层次化的实体:

二、RTPS 协议详解

2.1 什么是 RTPS

RTPS(Real-Time Publish-Subscribe Protocol for DDS over Interoperable transports)是 OMG 制定的线协议标准(Wire Protocol),它定义了不同 DDS 实现之间在网络上的数据交互格式与规则。简单来说,DDS 标准定义了"做什么"(API、QoS、数据模型),而 RTPS 协议定义了"怎么传"(报文格式、握手流程、状态机)。

DDS 与 RTPS 的关系
· DDS:面向应用层的高层 API 规范(Publisher/Subscriber/Topic/QoS)
· RTPS:底层线协议规范,将 DDS 的语义映射到网络报文
· 互操作性:只要遵循 RTPS 协议,不同厂商的 DDS(如 FastDDS / Cyclone DDS / RTI Connext)就能互相通信
· 类比:DDS 之于 RTPS,如同 HTTP 之于 TCP;DDS 是应用语义层,RTPS 是传输协议层

2.2 RTPS 核心实体

RTPS 协议在 DDS 实体之下定义了对应的协议层实体:

2.3 RTPS 报文类型(子消息)

子消息类型 方向 作用
DATA Writer → Reader 携带实际数据负载,包含 CacheChange 的序列号与序列化数据
DATA_FRAG Writer → Reader 大数组分片传输,支持流式数据(如图像、点云)
HEARTBEAT Writer → Reader 心跳,告知 Reader 当前可用的 CacheChange 序列号范围
ACKNACK Reader → Writer 确认/否定确认,告知 Writer 哪些数据已收到、哪些需要重传
GAP Writer → Reader 告知 Reader 某些序列号的数据不再可用(已过期或被丢弃)
INFO_DST 双向 告知对方自己的 GuidPrefix,用于实体寻址
INFO_TS 双向 携带时间戳信息
INFO_SRC 双向 告知报文来源的 GuidPrefix

2.4 RTPS 通信三阶段

RTPS 协议将通信过程分为三个核心阶段:

① 发现阶段(Discovery Phase)
· Participant 通过 Simple Discovery Protocol (SDP) 交换 Endpoint 信息
· 分为两个子阶段:
  - SPDP(Simple Participant Discovery Protocol):发现网络中的其他 Participant
  - SEDP(Simple Endpoint Discovery Protocol):交换 Writer/Reader 的 Topic、类型、QoS 信息
· 匹配条件:Topic 名称相同、数据类型相同、QoS 兼容
· 匹配成功后,Writer 与 Reader 建立"关联"(Association)
② 数据传输阶段(Data Exchange Phase)
· Writer 调用 new_change() 创建 CacheChange 并写入 HistoryCache
· StatefulWriter 通过 HEARTBEAT 告知 Reader 可用数据
· Reader 通过 ACKNACK 请求缺失的数据
· Writer 发送 DATA 或 DATA_FRAG 报文传输实际数据
· Reader 接收后写入自己的 HistoryCache,并通知上层 DataReaderListener
· RELIABLE 模式下,Writer 根据 ACKNACK 重传未确认的数据
· BEST_EFFORT 模式下,Writer 直接发送 DATA,不等待确认
③ 关闭阶段(Closing Phase)
· Writer 发送 dispose/unregister 通知数据被删除
· Reader 发送 GAP 清理无效序列号
· Participant 注销时,通过 SPDP 通告离线,其他节点清理关联

2.5 RTPS 可靠传输机制

RTPS 的可靠传输基于 HEARTBEAT-ACKNACK 握手机制,类似 TCP 的 ACK 但更灵活:

注意:QoS 兼容性
RTPS 匹配时检查 QoS 兼容性。如果 Writer 是 RELIABLE 而 Reader 是 BEST_EFFORT,可以匹配(Reader 忽略重传)。但如果 Writer 是 BEST_EFFORT 而 Reader 要求 RELIABLE,则无法匹配。这是 ROS 2 中常见的"话题无数据"问题根源。

三、核心 UML 架构图

3.1 DDS 实体类图

FastDDS DDS 实体类图

3.2 发布订阅通信时序图

FastDDS 发布订阅通信时序图

3.3 RTPS 协议交互图

FastDDS RTPS 协议交互图

四、FastDDS 核心能力

4.1 能力矩阵

能力 说明 关键 API / 机制
发布/订阅通信 解耦的数据分发模型,N-to-N 通信 DataWriter::write() / DataReader::take()
动态发现 自动发现网络中的其他节点,无需中心配置 Simple Discovery Protocol (SDP)
QoS 策略 22 种 QoS 策略,覆盖可靠性、持久性、时限等 ReliabilityQos / DurabilityQos / DeadlineQos
多传输层 UDP / TCP / 共享内存 / 跨厂商互操作 UDPv4TransportDescriptor / SHMTransportDescriptor
内容过滤 按数据内容过滤,订阅者只接收感兴趣的样本 ContentFilteredTopic / SQLFilter
多域隔离 通过 Domain ID 隔离通信,不同域互不可见 DomainParticipantFactory::create_participant(id)
安全机制 DDS-Security 标准:认证、授权、加密 Authentication / AccessControl / Cryptographic
零拷贝传输 基于共享内存的零拷贝数据传输 SHMTransportDescriptor + DataSharingQos
流式数据 支持大文件、视频流分片传输 DATA_FRAG 子消息
持久化 历史数据持久化到磁盘,重启后可恢复 DurabilityQos::TRANSIENT_DURABILITY_QOS

4.2 核心 QoS 策略

ReliabilityQos:BEST_EFFORT(尽力而为,适合传感器流) / RELIABLE(可靠传输,适合命令控制)

DurabilityQos:VOLATILE(不保留历史) / TRANSIENT_LOCAL(保留晚加入者可见的历史) / TRANSIENT(持久化到磁盘)

HistoryQos:KEEP_LAST(N)(保留最近 N 个样本) / KEEP_ALL(保留全部)

DeadlineQos:期望数据更新周期,超时触发回调

LivelinessQos:检测节点存活状态,自动清理死亡节点

五、适用场景

5.1 典型应用场景

5.2 机器人系统中的实际应用

案例:基于 FastDDS 的机器人示教器系统

· 传感器数据流:LiDAR 点云、相机图像使用 BEST_EFFORT + KEEP_LAST(5)
· 控制指令:运动指令使用 RELIABLE + KEEP_LAST(1)
· 状态同步:机器人状态使用 TRANSIENT_LOCAL,晚加入的示教器可获取最后状态
· 告警信息:使用 DEADLINE QoS,超时未收到即触发告警
· 跨网段:通过 TCP 传输跨网段连接远程示教器

六、相比其他中间件的优缺点

6.1 与主流通信中间件对比

维度 FastDDS ZeroMQ gRPC MQTT
通信模型 发布/订阅 (动态发现) 多种模式 (需手动连接) 请求/响应 + 流 发布/订阅 (需 Broker)
动态发现 ✅ 内置 ❌ 需手动配置 ❌ 需服务发现 ❌ 依赖 Broker
QoS 保障 ✅ 22 种策略 ❌ 仅基础 ❌ 仅超时 ❌ 仅 3 个级别
实时性 ✅ 软实时 ✅ 较好 ⚠️ 一般 ❌ 较差
跨语言 C++/Python/Java 多语言 多语言 多语言
Broker 依赖 ❌ 去中心化 ❌ 去中心化 ✅ 需服务端 ✅ 需 Broker
互操作标准 ✅ DDS-RTPS ❌ 无 ✅ Protobuf ✅ MQTT 标准
复杂度 中等 低 中等 低

6.2 FastDDS 的优势

① 去中心化:无需 Broker,避免单点故障,降低部署复杂度

② 动态发现:节点自动发现,热插拔无需重启

③ 丰富 QoS:22 种策略精细化控制通信行为

④ 标准 RTPS:不同厂商 DDS 可互操作

⑤ 零拷贝:共享内存传输,极致性能

⑥ ROS 2 集成:作为默认中间件,生态完善

6.3 FastDDS 的劣势

① 学习曲线:DDS 概念多(Domain/Participant/Topic/QoS),上手较陡

② 配置复杂:QoS 组合多,调优需要经验

③ 资源占用:相比 ZeroMQ,内存与 CPU 占用更高

④ 跨网段需配置:默认多播,跨网段需配置 Initial Peers

⑤ 调试工具少:相比 ROS 1 的 rqt_graph,DDS 层调试工具较少

七、相比其他 DDS 实现的优缺点

7.1 主流 DDS 实现对比

维度 FastDDS Cyclone DDS OpenDDS RTI Connext
开发方 eProsima Eclipse Foundation OCI RTI
开源协议 Apache 2.0 EPL 2.0 BSD-like ❌ 商业
ROS 2 支持 ✅ 默认 ✅ 可选 ⚠️ 社区 ✅ 商业支持
RTPS 互操作 ✅ 原生 ✅ 原生 ⚠️ 需配置 ✅ 原生
性能 ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐ ⭐⭐⭐ ⭐⭐⭐⭐⭐
共享内存 ✅ 内置 ✅ 支持 ⚠️ 需扩展 ✅ 内置
安全模块 ✅ 开源 ✅ 开源 ✅ 开源 ✅ 商业
Python 绑定 ✅ 官方 ⚠️ 社区 ⚠️ 社区 ✅ 商业
文档完善度 ⭐⭐⭐⭐⭐ ⭐⭐⭐ ⭐⭐⭐⭐ ⭐⭐⭐⭐⭐

7.2 FastDDS 相比其他 DDS 的优势

① ROS 2 默认:官方钦定中间件,生态最完善,社区最活跃

② 性能领先:在多次 ROS 2 中间件性能测评中吞吐量与延迟指标领先

③ Apache 2.0 协议:商业友好,无 GPL 传染风险

④ 共享内存零拷贝:内置 SHM 传输,单机性能极致

⑤ 文档详尽:官方文档与示例丰富,上手快

⑥ Python 绑定:官方维护的 Python 绑定,支持快速原型开发

7.3 FastDDS 相比其他 DDS 的劣势

① 商业支持弱:相比 RTI Connext,缺乏企业级技术支持

② 实时性认证:相比 RTI Connext Professional,缺乏硬实时认证

③ 工具链:RTI 提供更完善的监控、记录、回放工具

④ Cyclone DDS 兼容性:部分 ROS 2 场景下 Cyclone DDS 表现更稳定

⑤ 配置复杂度:相比 Cyclone DDS,XML 配置项更多

八、最小 FastDDS 通信全流程示例

8.1 项目结构

fastdds-demo/
├── CMakeLists.txt
├── idl/
│   └── HelloWorld.idl          # 数据类型定义
├── publisher/
│   └── publisher.cpp           # 发布者
├── subscriber/
│   └── subscriber.cpp          # 订阅者
└── scripts/
    └── fastddsgen.sh           # IDL 代码生成脚本

8.2 定义 IDL 数据类型

// idl/HelloWorld.idl
struct HelloWorld {
    unsigned long index;
    string message;
};

8.3 生成类型支持代码

# 使用 fastddsgen 工具生成 C++ 代码
fastddsgen idl/HelloWorld.idl

# 生成文件:
#   HelloWorld.h / HelloWorld.cpp        (数据类型)
#   HelloWorldPubSubTypes.h / .cpp       (序列化/反序列化)
#   HelloWorldTypeSupport.h              (类型支持注册)

7.4 发布者完整实现

// publisher/publisher.cpp
#include "HelloWorldPubSubTypes.h"
#include <fastdds/dds/domain/DomainParticipantFactory.hpp>
#include <fastdds/dds/publisher/Publisher.hpp>
#include <fastdds/dds/publisher/DataWriter.hpp>

using namespace eprosima::fastdds::dds;

int main() {
    // ① 创建 DomainParticipant
    DomainParticipantQos participant_qos;
    participant_qos.name("PublisherParticipant");
    DomainParticipant* participant =
        DomainParticipantFactory::get_instance()->create_participant(0, participant_qos);

    // ② 注册数据类型
    TypeSupport type_support(new HelloWorldPubSubType());
    type_support.register_type(participant);

    // ③ 创建 Topic
    Topic* topic = participant->create_topic(
        "HelloWorldTopic",
        "HelloWorld",
        TOPIC_QOS_DEFAULT);

    // ④ 创建 Publisher
    Publisher* publisher = participant->create_publisher(PUBLISHER_QOS_DEFAULT);

    // ⑤ 创建 DataWriter (配置 QoS)
    DataWriterQos writer_qos;
    writer_qos.reliability().kind = RELIABLE_RELIABILITY_QOS;
    writer_qos.durability().kind = TRANSIENT_LOCAL_DURABILITY_QOS;
    writer_qos.history().kind = KEEP_LAST_HISTORY_QOS;
    writer_qos.history().depth = 10;
    DataWriter* writer = publisher->create_datawriter(topic, writer_qos);

    // ⑥ 发布数据
    HelloWorld data;
    for (unsigned long i = 0; i < 100; ++i) {
        data.index(i);
        data.message("Hello from FastDDS #" + std::to_string(i));
        writer->write(&data);
        std::cout « "Published: " « data.message() « std::endl;
        std::this_thread::sleep_for(std::chrono::milliseconds(500));
    }

    // ⑦ 清理资源
    publisher->delete_datawriter(writer);
    participant->delete_publisher(publisher);
    participant->delete_topic(topic);
    DomainParticipantFactory::get_instance()->delete_participant(participant);
    return 0;
}

7.5 订阅者完整实现

// subscriber/subscriber.cpp
#include "HelloWorldPubSubTypes.h"
#include <fastdds/dds/subscriber/Subscriber.hpp>
#include <fastdds/dds/subscriber/DataReader.hpp>
#include <fastdds/dds/subscriber/SampleInfo.hpp>

using namespace eprosima::fastdds::dds;

// 自定义监听器
class HelloWorldListener : public DataReaderListener {
public:
    void on_data_available(DataReader* reader) override {
        SampleInfo info;
        HelloWorld data;
        while (reader->take_next_sample(&data, &info) == RETCODE_OK) {
            if (info.valid_data) {
                std::cout « "Received: index=" « data.index()
                          « ", message=" « data.message() « std::endl;
            }
        }
    }
};

int main() {
    // ① 创建 DomainParticipant
    DomainParticipant* participant =
        DomainParticipantFactory::get_instance()->create_participant(0, PARTICIPANT_QOS_DEFAULT);

    // ② 注册数据类型
    TypeSupport type_support(new HelloWorldPubSubType());
    type_support.register_type(participant);

    // ③ 创建 Topic
    Topic* topic = participant->create_topic(
        "HelloWorldTopic", "HelloWorld", TOPIC_QOS_DEFAULT);

    // ④ 创建 Subscriber
    Subscriber* subscriber = participant->create_subscriber(SUBSCRIBER_QOS_DEFAULT);

    // ⑤ 创建 DataReader (QoS 必须与 Writer 兼容)
    DataReaderQos reader_qos;
    reader_qos.reliability().kind = RELIABLE_RELIABILITY_QOS;
    reader_qos.durability().kind = TRANSIENT_LOCAL_DURABILITY_QOS;
    reader_qos.history().kind = KEEP_LAST_HISTORY_QOS;
    reader_qos.history().depth = 10;

    HelloWorldListener listener;
    DataReader* reader = subscriber->create_datareader(
        topic, reader_qos, &listener);

    // ⑥ 等待数据 (运行 60 秒后退出)
    std::this_thread::sleep_for(std::chrono::seconds(60));

    // ⑦ 清理资源
    subscriber->delete_datareader(reader);
    participant->delete_subscriber(subscriber);
    participant->delete_topic(topic);
    DomainParticipantFactory::get_instance()->delete_participant(participant);
    return 0;
}

7.6 CMakeLists.txt 构建配置

cmake_minimum_required(VERSION 3.16)
project(FastDDSDemo)

set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# 查找 FastDDS
find_package(fastcdr REQUIRED)
find_package(fastrtps REQUIRED)

# IDL 生成的源文件
set(IDL_SOURCES
    idl/HelloWorld.cxx
    idl/HelloWorldPubSubTypes.cxx
)

# 发布者
add_executable(publisher
    publisher/publisher.cpp
    ${IDL_SOURCES}
)
target_link_libraries(publisher fastrtps fastcdr)

# 订阅者
add_executable(subscriber
    subscriber/subscriber.cpp
    ${IDL_SOURCES}
)
target_link_libraries(subscriber fastrtps fastcdr)

7.7 运行流程

# 终端 1:启动订阅者
./subscriber

# 终端 2:启动发布者
./publisher

# 输出示例:
# Published: Hello from FastDDS #0
# Received: index=0, message=Hello from FastDDS #0
# Published: Hello from FastDDS #1
# Received: index=1, message=Hello from FastDDS #1
通信全流程总结
① 定义 IDL → ② fastddsgen 生成代码 → ③ 创建 Participant → ④ 注册 Type → ⑤ 创建 Topic → ⑥ 创建 Publisher/Subscriber → ⑦ 创建 DataWriter/DataReader(配置 QoS) → ⑧ Writer.write() / Reader Listener 回调 → ⑨ 清理资源

八、总结

8.1 FastDDS 核心优势总结

① 标准实现:完整实现 DDS-RTPS 标准,跨厂商互操作

② 性能卓越:共享内存零拷贝,高吞吐低延迟

③ 动态发现:去中心化,节点自动发现,热插拔

④ 丰富 QoS:22 种策略精细化控制通信行为

⑤ ROS 2 集成:作为默认中间件,生态完善

⑥ 开源友好:Apache 2.0 协议,商业可用

8.2 选型建议

场景 推荐中间件 理由
ROS 2 机器人开发 FastDDS 默认中间件,生态最完善
高性能实时通信 FastDDS 或 RTI Connext QoS 丰富,零拷贝传输
简单消息队列 ZeroMQ / MQTT 轻量级,部署简单
跨语言 RPC 调用 gRPC 强类型 Protobuf,多语言支持好
商业级关键系统 RTI Connext 企业级支持,硬实时认证

8.3 结语

FastDDS 作为 ROS 2 的默认通信中间件,在机器人、自动驾驶、工业控制等领域展现出强大的生命力。其基于 DDS 标准的设计保证了互操作性,丰富的 QoS 策略满足了各种复杂场景的需求,共享内存零拷贝技术提供了极致的性能。对于需要高性能、高可靠、可扩展通信的分布式系统,FastDDS 是一个值得深入学习和使用的优秀中间件。