Fast DDS 到底怎么用?一文理清发现、传输与 QoS 配置

DDS针对数据的分布式架构,可以完全去中心化,一个Participant对应一个domainid,每个进程直接通过Participant进行PDP阶段的服务发现,EDP是完成通讯握手,每个Participant的多个Publisher、Subscriber允许共用一个通讯端点,亦可以是多个,典型的是fastdds(高吞吐、低延迟且内存占用低),支持多种QOS,支持单播、组播、共享内存,由于高性能、生态强、ROS2、开源免费等特点,大量运用于机器人、物联网、汽车、医疗等行业

官方文档:Fast DDS Documentation

阅读指引

你想了解... 建议阅读章节
DDS 是什么、有哪些实体 第 1 章
发现与匹配如何发生 第 2、3 章
发一条数据经过哪些步骤 第 3 章
QoS 怎么选、怎么配 第 4 章
该调用哪些 API(含 TopicDataType) 第 5 章
官方示例怎么写(发现/UDP/TCP/SHM/QoS) 第 6 章

1. 通讯架构

1.1 DDS 与 Fast DDS 的关系

DDS(Data Distribution Service) 是 OMG 定义的以数据为中心的中间件标准:应用不直接连接对方 IP,而是通过 主题(Topic) 发布/订阅 同一数据类型 的样本。

Fast DDS 是 eProsima 对 DDS 标准的 C++ 实现,对外提供 DDS APIfastdds/dds),内部用 RTPS 协议fastdds/rtps)在网络上交换元数据与业务数据。

1.2 核心概念(初学者必记)

概念 含义
Domain 逻辑隔离边界,相同 domain_id 的参与者才能互相发现;不同 Domain 互不可见。
DomainParticipant 加入某个 Domain 的容器,管理本进程内所有 DDS 实体。
Topic 数据通道的逻辑名称,由「主题名 + 类型名」标识。
Publisher / Subscriber 发布组 / 订阅组,负责创建 DataWriter / DataReader。
DataWriter / DataReader 真正发送 / 接收数据的端点。
TypeSupport 把应用数据类型注册到 DDS,内部完成 CDR 序列化,所有的用于通讯的数据类型均要继承与eprosima::fastdds::dds::TopicDataType并注册到DDS,这一步通常使用fastddsgen生成对应代码文件完成,用户也可以自己手动完成

1.3 分层架构图

flowchart TB subgraph Application[应用代码] PubApp[发布方应用] SubApp[订阅方应用] end subgraph DDSLayer[DDS API 层 - fastdds/dds] Factory[DomainParticipantFactory] DP[DomainParticipant] Topic[Topic + TypeSupport] Pub[Publisher] DW[DataWriter] Sub[Subscriber] DR[DataReader] end subgraph RTPSLayer[RTPS 层 - fastdds/rtps] PDP[参与者发现 PDP SPDP / Discovery Server] EDP[端点发现 EDP SEDP] W[RTPS Writer] R[RTPS Reader] end subgraph Transport[传输层] UDP[UDP 元流量与数据] SHM[共享内存 SHM] TCP[TCP 可选] end PubApp --> Pub PubApp --> DW SubApp --> Sub SubApp --> DR Factory --> DP DP --> Topic DP --> Pub DP --> Sub Pub --> DW Sub --> DR DW --> W DR --> R DP --> PDP DP --> EDP PDP --> UDP EDP --> UDP W --> UDP R --> UDP W --> SHM R --> SHM

说明:

  • 应用只调用 DDS API;发现与组包由 RTPS 层完成。
  • 默认内置传输通常为 UDP + SHM(同机优先走共享内存)。
  • 元流量(发现)与用户数据可共用或拆分传输配置。

1.4 实体关系图(单个 Domain 内)

flowchart LR subgraph Domain[Domain ID = 0] DP1[Participant 发布进程] DP2[Participant 订阅进程] DP1 --> P1[Publisher] P1 --> DW1[DataWriter] DW1 --> T[Topic hello_world_topic Type HelloWorld] DP2 --> S1[Subscriber] S1 --> DR1[DataReader] DR1 --> T end

发布方与订阅方 无需事先知道对方地址 ;在 Topic 名、类型名、QoS 兼容 的前提下,中间件自动匹配并建立数据通路。

1.5 常见场景与官方示例对照

下表仅映射 Fast DDS 官方示例 (位于官方源码 examples/cpp/ 下),便于初学者按场景查找参考实现:

学习场景 官方示例目录 / 可执行文件 发现方式 传输方式
最简 Pub/Sub hello_world 默认 SIMPLE(SPDP/SEDP) 内置 UDP + SHM
XML 配置 QoS hello_world + hello_world_profile.xml SIMPLE 内置 UDP + SHM
共享内存 SHM delivery_mechanisms-m SHM SIMPLE 仅 SHM
多种传输对比 delivery_mechanisms(UDP/TCP/Large Data 等) SIMPLE 命令行可选
Discovery Server discovery_server(server / publisher / subscriber) SERVER / CLIENT 通常 UDP
静态端点发现 static_edp_discovery Static EDP 内置
DDS Security security SIMPLE(加密元流量与数据) 安全插件 + XML

1.6 数据从对象到网络的路径(CDR 与类型)

flowchart LR A[应用结构体 如 HelloWorld] --> B[DataWriter::write] B --> C[HelloWorldPubSubType::serialize] C --> D[Fast-CDR + CdrAux 生成代码] D --> E[SerializedPayload 字节] E --> F[RTPS DATA 子消息] F --> G[UDP / SHM 发送] G --> H[DataReader 接收] H --> I[deserialize -> 应用对象]

类型与 CDR 代码来源:

  • 在官方 hello_world 示例中,由 HelloWorld.idlfastddsgen 生成 HelloWorld.hppHelloWorldPubSubTypes.*HelloWorldCdrAux.* 等。
  • 应用侧只需 TypeSupport 注册类型并 write();CDR 细节在生成的 TopicDataType 实现中完成。
  • 学习与验证序列化:阅读官方 hello_world 目录下生成文件,或查阅 Fast DDS-Gen 文档。

2. 服务发现时序

服务发现让互相未知的 Participant 与 DataWriter/DataReader 找到彼此。分为两阶段:

阶段 名称 作用
PDP Participant Discovery Protocol 发现域内有哪些 Participant
EDP Endpoint Discovery Protocol 发现 Writer/Reader,判断是否可匹配

2.1 简单发现(SPDP + SEDP)--- 默认

Participant 使用 Simple Discovery:通过 UDP 多播/单播交换 SPDP(参与者)与 SEDP(端点)报文。

sequenceDiagram autonumber participant PubApp as 发布方应用 participant PubDP as 发布方 Participant participant Network as 网络 UDP 元流量 participant SubDP as 订阅方 Participant participant SubApp as 订阅方应用 Note over PubApp,SubApp: 阶段一 PDP --- 参与者发现 PubApp->>PubDP: create_participant(domain_id) PubDP->>Network: SPDP 公告本 Participant SubApp->>SubDP: create_participant(domain_id) SubDP->>Network: SPDP 公告 Network->>PubDP: 获知远端 Participant Network->>SubDP: 获知远端 Participant Note over PubApp,SubApp: 阶段二 EDP --- 端点发现 PubApp->>PubDP: create_datawriter(topic, qos) PubDP->>Network: SEDP 发布 Writer 信息 SubApp->>SubDP: create_datareader(topic, qos) SubDP->>Network: SEDP 发布 Reader 信息 Network->>PubDP: 发现匹配的 Reader Network->>SubDP: 发现匹配的 Writer PubDP-->>PubApp: on_publication_matched SubDP-->>SubApp: on_subscription_matched Note over PubApp,SubApp: 匹配完成,可发送用户数据

2.2 Discovery Server 模式

集中式发现:Client 只与 Discovery Server 通信,由 Server 转发发现信息。适用于大规模部署或不宜使用多播的网络。

sequenceDiagram autonumber participant DS as Discovery Server participant PubDP as Client Participant<br/>(Publisher) participant SubDP as Client Participant<br/>(Subscriber) DS->>DS: 以 SERVER 模式启动<br/>监听固定端口 PubDP->>DS: CLIENT 注册 + 上报 Writer SubDP->>DS: CLIENT 注册 + 上报 Reader DS-->>PubDP: 分发远端 Reader 信息 DS-->>SubDP: 分发远端 Writer 信息 PubDP-->>PubDP: on_publication_matched SubDP-->>SubDP: on_subscription_matched Note over PubDP,SubDP: 用户数据仍按 Participant 传输配置发送<br/>(如 UDP / SHM)

2.3 静态端点发现(Static EDP)

当所有远端 Writer/Reader 的 Topic、QoS、实体 ID 事先已知 时,可关闭动态 SEDP,改用 XML 静态表配置端点,减少发现报文。见第 6.4 节。


3. 通讯与握手时序

3.1 应用层:从匹配到收数

sequenceDiagram autonumber participant Pub as 发布方应用 participant DW as DataWriter participant DR as DataReader participant Sub as 订阅方应用 Note over Pub,Sub: 前提:发现已完成,Topic + Type + QoS 兼容 DR-->>Sub: on_subscription_matched(匹配数 +1) DW-->>Pub: on_publication_matched(匹配数 +1) Pub->>DW: write(sample) DW->>DW: serialize → CDR 字节 DW->>DR: RTPS DATA(经传输层) DR->>DR: deserialize DR-->>Sub: on_data_available Sub->>DR: read / take

初学者注意:

  • 应先等待 matched (或检查匹配状态),再 write,否则可能没有订阅方接收。
  • 订阅方在 on_data_available 中调用 take_next_sampleread,才能拿到数据。

3.2 RTPS 层:RELIABLE 可靠传输

当 Reliability 为 RELIABLE 时,Writer 与 Reader 之间通过 RTPS 子消息确认送达,必要时重传。

sequenceDiagram autonumber participant W as RTPS Writer participant R as RTPS Reader Note over W,R: 端点已匹配,双方已知对方 GUID W->>R: HEARTBEAT(通告可读序列号区间) R->>W: ACKNACK(确认 / 请求重传) W->>R: DATA(携带 CDR 负载) R->>W: ACKNACK(全部收到则确认完成)

BEST_EFFORT 模式不保证重传,无上述可靠确认流程,延迟更低但可能丢包。


4. QoS 配置

QoS(Quality of Service)描述「数据如何传递」。DataWriter 与 DataReader 的 QoS 必须兼容 才能匹配。

4.1 常用 QoS 策略

QoS 典型取值 中文说明
Reliability BEST_EFFORT 尽力发送,不确认、不重传,可能丢包。
RELIABLE 可靠传输,丢包会重传,适合控制指令、状态同步。
Durability VOLATILE 只接收匹配 之后 发布的数据。
TRANSIENT_LOCAL 晚加入的订阅者可收到 Writer 本地缓存 的历史样本。
TRANSIENT / PERSISTENT 需持久化服务,跨进程/重启保留(配置更复杂)。
History KEEP_LAST + depth 只保留最近 N 条样本。
KEEP_ALL 保留全部(受 ResourceLimits 限制)。
Deadline 时间周期 期望在该周期内至少收到一个样本,超期触发回调。
Liveliness AUTOMATIC / MANUAL 参与者存活检测;MANUAL 需应用定期 assert。
Lifespan 持续时间 样本超过寿命后不再投递给新读者。
Ownership SHARED / EXCLUSIVE 多 Writer 时是否共享或独占实例。
Partition 字符串列表 逻辑分区;Partition 不同的同 Topic 互不匹配。

4.2 Reliability 兼容规则

Writer Reader 能否匹配
BEST_EFFORT BEST_EFFORT
BEST_EFFORT RELIABLE
RELIABLE BEST_EFFORT
RELIABLE RELIABLE

4.3 Durability 兼容规则(简化)

Writer Reader 能否匹配
VOLATILE VOLATILE
TRANSIENT_LOCAL TRANSIENT_LOCAL
TRANSIENT_LOCAL VOLATILE 是(Reader 不收历史)
VOLATILE TRANSIENT_LOCAL

4.4 代码中设置 QoS(DataWriter 示例)

cpp 复制代码
#include <fastdds/dds/publisher/qos/DataWriterQos.hpp>

DataWriterQos wqos = DATAWRITER_QOS_DEFAULT;
wqos.reliability().kind = RELIABLE_RELIABILITY_QOS;
wqos.durability().kind = TRANSIENT_LOCAL_DURABILITY_QOS;
wqos.history().kind = KEEP_LAST_HISTORY_QOS;
wqos.history().depth = 10;

DataWriter* writer = publisher->create_datawriter(topic, wqos, listener, StatusMask::all());

DataReader 侧需设置 兼容DataReaderQos(Reliability、Durability、History 等)。

4.5 官方示例典型 QoS 与传输配置

以下为 eProsima 官方示例 中常见的 QoS / 传输组合,便于对照学习(非某一定制工程的配置):

官方示例 Reliability Durability History 传输 / 配置方式
hello_world(默认 Profile) 由 Profile 或默认 QoS 决定 通常 VOLATILE KEEP_LAST 内置 UDP + SHM;代码或 hello_world_profile.xml
hello_world_profile.xml RELIABLE TRANSIENT_LOCAL KEEP_LAST,depth=100 同上;XML 中配置 heartbeat
delivery_mechanisms 默认 示例类型默认 QoS 默认 默认 内置 UDP + SHM
delivery_mechanisms -m SHM 默认 默认 默认 仅 SHMSharedMemTransportDescriptor
discovery_server 客户端 默认 默认 默认 自定义 UDP/TCP + Discovery Server
static_edp_discovery XML 中 RELIABLE XML 中 TRANSIENT_LOCAL 与静态表一致 内置;静态 EDP XML
security XML Profile XML Profile XML Profile 加密传输 + 证书目录

配置入口归纳:

  • 代码内DataWriterQos / DataReaderQos / DomainParticipantQos(见 §4.4、§6.3、§6.4)。
  • XML ProfileFASTDDS_DEFAULT_PROFILES_FILE 指向如 hello_world_profile.xml(见 §6.2)。
  • 静态发现表HelloWorld_static_disc.xml 等(见 §6.5)。

5. 关键接口说明

以下接口均属于 Fast DDS DDS APInamespace eprosima::fastdds::dds)。

5.1 DomainParticipantFactory(参与者工厂)

接口 详细说明
get_instance() 获取全局单例工厂。整个进程通常共用此工厂创建 Participant。
create_participant(domain_id, qos, listener, mask) 创建参与者并加入指定 Domain。domain_id 相同的参与者才可能互相发现。
create_participant_with_default_profile(listener, mask) 从默认 XML Profile 创建 Participant(需事先 load_profiles_file 或设置环境变量 FASTDDS_DEFAULT_PROFILES_FILE)。
load_profiles_file(path) 加载 XML 配置文件,供 Profile 名称引用。
delete_participant(participant) 销毁参与者及其下属 Publisher、Subscriber、Topic 等实体。

5.2 TypeSupport 与 Topic(类型与主题)

接口 / 类 详细说明
TypeSupport 继承自 shared_ptr<TopicDataType>,内部持有 TopicDataType 实现(通常为 fastddsgen 生成的 XxxPubSubType)。应用通过 TypeSupport 注册类型;序列化等细节见 §5.3 TopicDataType
register_type(participant) 在参与者上注册类型名(如 "HelloWorld"),之后 Topic 与 Writer/Reader 才能使用该类型。
get_type_name() 返回类型字符串,创建 Topic 时必须与注册名一致。
create_topic(name, type_name, qos) 创建逻辑主题。发布方与订阅方 主题名 + 类型名 必须一致。

5.3 TopicDataType(类型序列化与样本管理)

头文件: fastdds/dds/topic/TopicDataType.hpp

命名空间: eprosima::fastdds::dds::TopicDataType

定位: TopicDataType 是 Fast DDS 中 应用数据类型与 RTPS 层之间的桥梁 。DDS 中间件在 write()take() 等路径上并不直接理解你的 struct / class,而是通过 TopicDataType 子类完成:

  • 将应用对象 序列化 为 RTPS SerializedPayload_t(CDR / XCDR 字节流);
  • 将收到的字节流 反序列化 回应用对象;
  • 分配 / 释放 样本内存;
  • 在带 @key 的类型上计算 InstanceHandle(区分同一 Topic 下的不同实例);
  • 向类型系统注册 TypeObject(XTypes 发现与兼容性检查)。

应用侧 一般不直接调用 这些方法,而是由 DataWriter / DataReader 在内部回调;自定义类型时需继承并实现本类(或由 fastddsgen 生成 XxxPubSubType)。

与 fastddsgen 的关系:

cpp 复制代码
// fastddsgen 由 HelloWorld.idl 生成
class HelloWorldPubSubType : public eprosima::fastdds::dds::TopicDataType
{
    // 构造函数中:set_name("HelloWorld"); 设置 max_serialized_type_size 等
};

HelloWorldPubSubType type;
eprosima::fastdds::dds::TypeSupport ts(&type);  // TypeSupport 包装 TopicDataType
ts.register_type(participant);

在数据路径中的位置:

latex 复制代码
应用 write(HelloWorld&)
  → DataWriter 内部调用 TopicDataType::serialize()
  → SerializedPayload_t(CDR 字节 + encapsulation)
  → RTPS 发送
  → 对端 RTPS 接收
  → TopicDataType::deserialize()
  → take_next_sample(HelloWorld&)

5.3.1 必须实现的纯虚接口

以下方法在自定义类型时 必须 在子类中实现(fastddsgen 生成的代码已包含实现)。

接口 详细功能说明
serialize(data, payload, data_representation) data 指向的应用样本编码进 payloaddata_representation 指定 CDR 变体:XCDR_DATA_REPRESENTATION(XCDRv1 / PLAIN_CDR)、XCDR2_DATA_REPRESENTATION(XCDRv2 / DELIMIT_CDR2)。实现须正确设置 payload.length(序列化后字节长度)及 payload.encapsulation(端序等封装头)。write() 发送前会先调用 calculate_serialized_sizeserialize。返回 false 表示序列化失败,样本不会被发送。
deserialize(payload, data) payload.data / payload.length 解析字节流,填充到 data 指向的已分配样本。订阅方 take / read 收到 RTPS 数据后由中间件调用。解析失败应返回 false,该样本视为无效。
calculate_serialized_size(data, data_representation) 计算 单条样本 在给定 data_representation 下序列化后的字节数(通常含 encapsulation 对齐与 4 字节封装头)。用于预分配 SerializedPayload_t 缓冲区、资源限制与 Data Sharing 等优化路径,避免先序列化再扩容。
create_data() 分配一条 空样本 并返回 void*(如 new HelloWorld())。中间件在 Reader 缓存、Loan 样本、部分内部路径上需要独立样本对象时使用。
delete_data(data) 释放 create_data() 创建的样本(如 delete 对应指针)。与 create_data 成对,防止内存泄漏。
compute_key(payload, ihandle, force_md5) 已序列化的 payload 中提取 DDS 实例键 ,写入 InstanceHandle_t。仅当 IDL 类型含 @key 字段且实现有效键逻辑时返回 true;无键类型(如默认 HelloWorld)通常返回 false,且构造函数中设 is_compute_key_provided = false
compute_key(data, ihandle, force_md5) 内存中的应用对象 计算实例键,语义同上。带键类型用于 INSTANCE 相关 QoS、同 Topic 多实例区分。force_md5true 时强制走 MD5 生成键(用于键过长等场景)。

5.3.2 可选重写与带 Context 的扩展接口

Fast DDS 提供 TopicDataType::Context 抽象结构体,用于在序列化/反序列化时携带 类型相关的上下文 (例如字符串/序列的上界、动态类型约束)。带 _ctx 后缀的方法默认 转发 到无 Context 版本;仅在需要按上下文定制行为时重写。

接口 详细功能说明
serialize_ctx** / **deserialize_ctx 带上下文的序列化/反序列化。默认实现忽略 context 并调用 serialize / deserialize。动态类型或需按运行期约束编码时可重写。
calculate_serialized_size_ctx 带上下文的大小计算,默认同 calculate_serialized_size
create_data_ctx** / **delete_data_ctx 带上下文的样本创建/销毁,默认同 create_data / delete_data
compute_key_ctx 带上下文的键计算,默认同 compute_key
is_bounded() 返回类型是否 有界 (所有字段长度在编译期可确定上限,无无界 string/sequence)。有界类型可启用更多零拷贝与静态缓冲区优化。默认 false;有界 IDL 类型生成代码中常返回 true
is_plain(data_representation) 返回在该 CDR 表示下类型是否为 plain (内存布局与 CDR 编码一致、可不做逐字段拷贝的固定布局类型)。true 时中间件可走 Data Sharing 等高效路径。默认 false
construct_sample(memory) 已分配的内存地址 memory 上构造样本(placement new),避免额外 new。返回 true 表示支持就地构造。默认 false(使用 create_data 堆分配)。
register_type_object_representation() 向 Fast DDS TypeObjectRegistry 注册本类型的 XTypes TypeObject 描述(类型 ID、字段布局等),供发现阶段类型兼容性检查与动态类型场景使用。生成代码中通常调用 register_Xxx_type_identifier(type_identifiers_)
register_type_object_representation_ctx 带上下文的 TypeObject 注册,默认同上。
get_max_serialized_size_ctx(context) 返回类型 最大 序列化字节数(有界类型的上界)。默认返回成员 max_serialized_type_size。无界类型应将该成员设为 0

5.3.3 类型名与元数据成员

接口 / 成员 详细功能说明
set_name** / **get_name() 设置/获取 类型名 字符串(如 "HelloWorld")。须与 create_topic(..., type_name, ...)register_type 使用的名称一致;也是 RTPS 发现报文中宣告的 topicDataTypeName
type_identifiers() 返回 xtypes::TypeIdentifierPair,包含 XTypes 类型标识(与 TypeObject 注册结果关联),用于端点发现时的类型匹配。
max_serialized_type_size 公共成员:该类型 单样本最大 序列化大小(字节)。有界类型在构造函数中根据 IDL 计算并写入;无界类型为 0。影响 payload 池与缓存上限估算。
is_compute_key_provided 公共成员:是否提供了有效的 compute_key 实现。无 @key 类型为 false;有键类型为 true,中间件据此启用实例相关逻辑。

5.3.4 DataRepresentationId_t 与序列化示例

Writer/Reader 通过 QoS DataRepresentationQosPolicy 协商 XCDR 版本;serialize / calculate_serialized_size 收到的 data_representation 须与协商结果一致:

枚举值 含义
XCDR_DATA_REPRESENTATION XCDRv1,封装常用 PLAIN_CDR
XCDR2_DATA_REPRESENTATION XCDRv2,封装常用 DELIMIT_CDR2
XML_DATA_REPRESENTATION XML 表示(Fast DDS 不支持)

官方生成代码中的典型实现( HelloWorldPubSubType::serialize** 摘要):**

cpp 复制代码
bool HelloWorldPubSubType::serialize(
        const void* const data,
        SerializedPayload_t& payload,
        DataRepresentationId_t data_representation)
{
    const HelloWorld* p_type = static_cast<const HelloWorld*>(data);
    eprosima::fastcdr::FastBuffer fastbuffer(
            reinterpret_cast<char*>(payload.data), payload.max_size);
    eprosima::fastcdr::Cdr ser(fastbuffer, eprosima::fastcdr::Cdr::DEFAULT_ENDIAN,
            data_representation == DataRepresentationId_t::XCDR_DATA_REPRESENTATION
                    ? eprosima::fastcdr::CdrVersion::XCDRv1
                    : eprosima::fastcdr::CdrVersion::XCDRv2);
    ser.serialize_encapsulation();
    ser << *p_type;
    payload.length = static_cast<uint32_t>(ser.get_serialized_data_length());
    return true;
}

自定义类型时注意:

  1. 必须实现 §5.3.1 中全部纯虚函数;可选接口按需求重写。
  2. serialize 务必设置 payload.length;长度错误会导致 RTPS 层截断或越界。
  3. 类型名 set_name 与 Topic、发现报文一致。
  4. @key 字段时正确实现 compute_key 并设 is_compute_key_provided = true
  5. 优先用 fastddsgen 从 IDL 生成,再按需手工扩展 TopicDataType 子类。

5.3.5 fasddsgen 扩展

通过fasddsgen与idl文件自动生成与TopicDataType关联的数据结构代码在绝大多数场景是可用,而且是非常适用的,但是如果遇到二次封装、大量通讯信号时,可能过多的导出关联文件会产生相应的不便,为此可以设想一些扩展方案,将cdr序列化用元编程加特化的方式二次封装cdr序列化,并采用模板的方式继承TopicDataType,这样直接序列化模板类型即可,没有必要过多的冗余文件

5.4 Publisher 与 DataWriter(发布端)

接口 详细说明
create_publisher(qos, listener, mask) 创建发布者实体,是 DataWriter 的容器。
create_datawriter(topic, qos, listener, mask) 创建数据写入端点。qos 决定可靠性、耐久性等;listener 接收匹配、发送失败等事件。
create_datawriter_with_profile(topic, profile_name, listener, mask) 按 XML 中 data_writer profile_name 创建 Writer。
write(const void* data) 将一条样本交给中间件:内部序列化(CDR)并经 RTPS 发送。
DataWriterListener::on_publication_matched 当有订阅端点匹配或断开时回调;current_count 表示当前匹配数量。

5.5 Subscriber 与 DataReader(订阅端)

接口 详细说明
create_subscriber(qos, listener, mask) 创建订阅者实体。
create_datareader(topic, qos, listener, mask) 创建数据读取端点。
create_datareader_with_profile(topic, profile_name, listener, mask) 按 XML Profile 创建 Reader。
DataReaderListener::on_subscription_matched 发布端点匹配/断开时回调。
DataReaderListener::on_data_available 有新数据到达时回调;应在此调用 take/read 取数。
take_next_sample(sample, info) 取走并移除一条样本;info.valid_data 为真时表示有效用户数据。
read_next_sample(sample, info) 读取但不从 Reader 缓存移除(仍可被再次 read)。

5.6 发现与传输(ParticipantQos)

配置项 详细说明
wire_protocol().builtin.discovery_config.discoveryProtocol 发现协议:SIMPLE(默认 SPDP)、CLIENTSERVER(Discovery Server)。
discovery_config.m_DiscoveryServers CLIENT 模式下 Discovery Server 的 Locator 列表。
transport().use_builtin_transports true 时使用内置 UDP+SHM;为 false 时需自行 user_transports 添加描述符。
transport().user_transports 自定义传输列表,如仅 SharedMemTransportDescriptorUDPv4TransportDescriptor
DomainParticipantListener::on_participant_discovery 远端 Participant 上线/下线/QoS 变更时通知应用。

5.7 典型调用顺序

发布方:

latex 复制代码
DomainParticipantFactory::get_instance()
  → create_participant() 或 create_participant_with_default_profile()
  → TypeSupport::register_type()
  → create_topic()
  → create_publisher()
  → create_datawriter() + Listener
  → [等待 on_publication_matched]
  → write()
  → delete_participant()

订阅方:

latex 复制代码
DomainParticipantFactory::get_instance()
  → create_participant()
  → TypeSupport::register_type()
  → create_topic()
  → create_subscriber()
  → create_datareader() + Listener
  → [on_subscription_matched / on_data_available]
  → take_next_sample() 或 read_next_sample()
  → delete_participant()

6. 官方示例:代码与配置

本章按 服务发现、UDP、TCP、SHM、QoS、基础通讯 等维度,摘录 Fast DDS 官方源码树examples/cpp/ 下的典型 代码配置XML 配置 。编译时启用 COMPILE_EXAMPLES=ON,详见 Getting Started

6.0 维度总览

维度 官方示例 运行示例 配置入口
基础 Pub/Sub hello_world ./hello_world publisher / subscriber 默认 QoS + 默认传输
简单服务发现 hello_world 同上 默认 SIMPLE(SPDP/SEDP)
Discovery Server discovery_server server / publisher / subscriber DiscoveryProtocol + Locator
静态端点发现 static_edp_discovery ./static_edp_discovery publisher 静态 XML + Participant QoS
UDP 用户数据 delivery_mechanisms -m UDPv4 UDPv4TransportDescriptor
TCP 用户数据 delivery_mechanisms -m TCPv4 TCPv4TransportDescriptor + Locator
SHM 用户数据 delivery_mechanisms -m SHM SharedMemTransportDescriptor
QoS(代码) hello_world / delivery_mechanisms --- DataWriterQos / DataReaderQos
QoS(XML) hello_world_profile.xml FASTDDS_DEFAULT_PROFILES_FILE XML Profile

6.1 基础通讯

官方示例: examples/cpp/hello_world

作用: 演示最简 Publisher / Subscriber,创建 Participant → Topic → Writer/Reader → write / take

运行:

bash 复制代码
./hello_world subscriber
./hello_world publisher

代码示例(发布方,摘自官方 PublisherApp.cpp):

cpp 复制代码
#include <fastdds/dds/domain/DomainParticipantFactory.hpp>
#include <fastdds/dds/publisher/DataWriter.hpp>

// 1. 创建 Participant(可从默认 XML Profile 加载 QoS)
auto factory = DomainParticipantFactory::get_instance();
participant_ = factory->create_participant_with_default_profile(nullptr, StatusMask::none());

// 2. 注册数据类型
type_.register_type(participant_);

// 3. 创建 Publisher、Topic、DataWriter
publisher_ = participant_->create_publisher(pub_qos, nullptr, StatusMask::none());
topic_ = participant_->create_topic(topic_name, type_.get_type_name(), topic_qos);
writer_ = publisher_->create_datawriter(topic_, writer_qos, this, StatusMask::all());

// 4. 匹配后发送
writer_->write(&hello_);

配置说明:

  • 未显式修改传输时,使用 内置传输(通常 UDP + SHM,同机优先 SHM)。
  • 未显式修改发现时,使用 SIMPLE 发现(SPDP + SEDP)。
  • 类型 HelloWorld 由同目录下 HelloWorld.idl 经 fastddsgen 生成。

6.2 服务发现

6.2.1 简单发现(SPDP + SEDP)--- 默认

官方示例: hello_world(无需额外配置)

作用: Participant 通过 UDP 多播/单播自动交换参与者与端点信息,应用 零配置 即可互联(同一 Domain、Topic、兼容 QoS)。

代码要点: 使用 create_participant()create_participant_with_default_profile() 即可,不必 设置 discoveryProtocol

cpp 复制代码
// 默认即为 SIMPLE 发现,无需下列代码;仅作说明:
DomainParticipantQos pqos = PARTICIPANT_QOS_DEFAULT;
// pqos.wire_protocol().builtin.discovery_config.discoveryProtocol
//     默认为 SIMPLE(SPDP/SEDP)
participant_ = factory->create_participant(0, pqos, nullptr, StatusMask::none());

6.2.2 Discovery Server(集中式发现)

官方示例: examples/cpp/discovery_server

作用: 元流量经中央 Server 转发,适合不便多播或需集中管理发现的场景。

运行(三个终端):

bash 复制代码
./discovery_server server
./discovery_server subscriber
./discovery_server publisher

Server 端代码(摘自 ServerApp.cpp):

cpp 复制代码
DomainParticipantQos pqos;
pqos.transport().use_builtin_transports = false;
pqos.transport().user_transports.push_back(descriptor);  // 如 UDPv4TransportDescriptor

// 声明为 Discovery Server
pqos.wire_protocol().builtin.discovery_config.discoveryProtocol =
        eprosima::fastdds::rtps::DiscoveryProtocol::SERVER;

// Server 监听地址(PDP 元流量)
Locator listening_locator;
listening_locator.kind = LOCATOR_KIND_UDPv4;
IPLocator::setIPv4(listening_locator, "127.0.0.1");
IPLocator::setPhysicalPort(listening_locator, 16166);
pqos.wire_protocol().builtin.metatrafficUnicastLocatorList.push_back(listening_locator);

participant_ = DomainParticipantFactory::get_instance()->create_participant(0, pqos, listener);

Client 端代码(摘自 ClientPublisherApp.cpp):

cpp 复制代码
DomainParticipantQos pqos;
pqos.transport().use_builtin_transports = false;

// 用户数据传输(示例常用 UDPv4)
auto udp = std::make_shared<UDPv4TransportDescriptor>();
pqos.transport().user_transports.push_back(udp);

// Discovery Server 地址
Locator server_locator;
server_locator.kind = LOCATOR_KIND_UDPv4;
IPLocator::setIPv4(server_locator, "127.0.0.1");
IPLocator::setPhysicalPort(server_locator, 16166);

// 声明为 Client,并指向 Server
pqos.wire_protocol().builtin.discovery_config.discoveryProtocol =
        eprosima::fastdds::rtps::DiscoveryProtocol::CLIENT;
pqos.wire_protocol().builtin.discovery_config.m_DiscoveryServers.push_back(server_locator);

participant_ = DomainParticipantFactory::get_instance()->create_participant(0, pqos, nullptr);

配置要点:

配置项 说明
discoveryProtocol::SERVER 本进程作为发现服务器
discoveryProtocol::CLIENT 本进程向 Server 注册并获取远端信息
m_DiscoveryServers Client 侧 Server 的 Locator 列表(IP + 端口须一致)
metatrafficUnicastLocatorList Server 侧 PDP 监听地址
user_transports 发现与用户数据所用传输(Client/Server 均需配置)

6.2.3 静态端点发现(Static EDP)

官方示例: examples/cpp/static_edp_discovery

作用: 关闭动态 SEDP,通过 XML 预先声明 所有远端 Writer/Reader,减少发现报文。

运行:

bash 复制代码
./static_edp_discovery subscriber
./static_edp_discovery publisher

代码示例(摘自 PublisherApp.cpp):

cpp 复制代码
DomainParticipantQos pqos = PARTICIPANT_QOS_DEFAULT;
pqos.name("static_edp_delivery_pub_participant");

// 关闭 SIMPLE EDP,启用静态 EDP
pqos.wire_protocol().builtin.discovery_config.use_SIMPLE_EndpointDiscoveryProtocol = false;
pqos.wire_protocol().builtin.discovery_config.use_STATIC_EndpointDiscoveryProtocol = true;
pqos.wire_protocol().builtin.discovery_config.static_edp_xml_config("HelloWorld_static_disc.xml");

auto factory = DomainParticipantFactory::get_instance();
factory->check_xml_static_discovery("HelloWorld_static_disc.xml");
participant_ = factory->create_participant(0, pqos, nullptr, StatusMask::none());

XML 配置( HelloWorld_static_disc.xml):

xml 复制代码
<staticdiscovery>
    <participant>
        <name>static_edp_delivery_pub_participant</name>

        <writer>
            <entityID>1</entityID>

            <userId>2</userId>

            <topicName>hello_world_topic</topicName>

            <topicDataType>HelloWorld</topicDataType>

            <reliabilityQos>RELIABLE_RELIABILITY_QOS</reliabilityQos>

            <durabilityQos>TRANSIENT_LOCAL_DURABILITY_QOS</durabilityQos>

        </writer>

    </participant>

    <participant>
        <name>static_edp_delivery_sub_participant</name>

        <reader>
            <entityID>3</entityID>

            <userId>4</userId>

            <topicName>hello_world_topic</topicName>

            <topicDataType>HelloWorld</topicDataType>

            <reliabilityQos>RELIABLE_RELIABILITY_QOS</reliabilityQos>

            <durabilityQos>TRANSIENT_LOCAL_DURABILITY_QOS</durabilityQos>

        </reader>

    </participant>

</staticdiscovery>

6.3 UDP 通讯

官方示例: examples/cpp/delivery_mechanisms

作用: 仅使用 UDPv4 作为用户数据传输(仍可配合 SIMPLE 发现)。

运行:

bash 复制代码
./delivery_mechanisms subscriber -m UDPv4
./delivery_mechanisms publisher -m UDPv4

代码示例(摘自 PublisherApp.cpp):

cpp 复制代码
#include <fastdds/rtps/transport/UDPv4TransportDescriptor.hpp>

DomainParticipantQos pqos = PARTICIPANT_QOS_DEFAULT;
pqos.name("DeliveryMechanisms_pub_participant");

// 关闭内置传输,仅添加 UDPv4
pqos.transport().use_builtin_transports = false;
pqos.transport().user_transports.push_back(
        std::make_shared<UDPv4TransportDescriptor>());

participant_ = factory->create_participant(domain, pqos, nullptr, StatusMask::none());

配置要点:

说明
use_builtin_transports = false 必须关闭,否则仍加载默认 UDP+SHM
UDPv4TransportDescriptor 创建 UDP 传输描述符并加入 user_transports
可选 interfaceWhiteList 限制网卡;metatrafficMulticastLocatorList 定制多播

与默认内置 UDP 的区别: hello_world 默认 use_builtin_transports=true 时同时启用 UDP 与 SHM;此处 显式仅 UDP,便于对比与测试。

6.4 TCP 通讯

官方示例: examples/cpp/delivery_mechanisms-m TCPv4

作用: 使用 TCPv4 传输用户数据;需配置 Locator监听端口

运行:

bash 复制代码
./delivery_mechanisms subscriber -m TCPv4
./delivery_mechanisms publisher -m TCPv4
# 可选指定地址:-t 192.168.1.10

代码示例(摘自 PublisherApp.cpp):

cpp 复制代码
#include <fastdds/rtps/transport/TCPv4TransportDescriptor.hpp>

DomainParticipantQos pqos = PARTICIPANT_QOS_DEFAULT;
pqos.transport().use_builtin_transports = false;

// 发现租约(TCP 场景官方示例中的典型设置)
pqos.wire_protocol().builtin.discovery_config.leaseDuration = c_TimeInfinite;
pqos.wire_protocol().builtin.discovery_config.leaseDuration_announcementperiod = Duration_t(5, 0);

auto tcp = std::make_shared<TCPv4TransportDescriptor>();
tcp->sendBufferSize = 0;
tcp->receiveBufferSize = 0;

// 本机监听与通告地址
Locator tcp_locator;
tcp_locator.kind = LOCATOR_KIND_TCPv4;
IPLocator::setIPv4(tcp_locator, "127.0.0.1");
IPLocator::setPhysicalPort(tcp_locator, 5100);

pqos.wire_protocol().builtin.metatrafficUnicastLocatorList.push_back(tcp_locator);
pqos.wire_protocol().default_unicast_locator_list.push_back(tcp_locator);

tcp->set_WAN_address("127.0.0.1");
tcp->add_listener_port(5100);
pqos.transport().user_transports.push_back(tcp);

participant_ = factory->create_participant(domain, pqos, nullptr, StatusMask::none());

配置要点:

说明
TCPv4TransportDescriptor TCP 传输描述符
add_listener_port 本进程 TCP 监听端口(示例为 5100)
default_unicast_locator_list 对外通告的用户数据地址
metatrafficUnicastLocatorList 元流量(发现)TCP 地址
set_WAN_address 跨网/WAN 场景下的对外地址

Discovery Server + TCP: discovery_server 示例的 Client 也可选用 TCPv4TransportDescriptor 连接 Server,Locator 使用 LOCATOR_KIND_TCPv4setLogicalPort

6.5 SHM 共享内存通讯

官方示例: examples/cpp/delivery_mechanisms-m SHM

作用: 同机进程间通过 共享内存 传递用户数据,延迟低、拷贝少。

运行:

bash 复制代码
./delivery_mechanisms subscriber -m SHM
./delivery_mechanisms publisher -m SHM

代码示例(摘自 PublisherApp.cpp):

cpp 复制代码
#include <fastdds/rtps/transport/shared_mem/SharedMemTransportDescriptor.hpp>

DomainParticipantQos pqos = PARTICIPANT_QOS_DEFAULT;
pqos.transport().use_builtin_transports = false;

auto shm = std::make_shared<SharedMemTransportDescriptor>();
// 共享内存段大小:与最大消息、样本数相关
shm->segment_size(shm->max_message_size() * max_samples);
pqos.transport().user_transports.push_back(shm);

participant_ = factory->create_participant(domain, pqos, nullptr, StatusMask::none());

配置要点:

说明
SharedMemTransportDescriptor SHM 传输描述符
segment_size 共享内存段大小,需容纳预期并发样本
同机要求 发布/订阅进程须在同一台机器
默认内置 SHM use_builtin_transports=true 时,Fast DDS 已内置 SHM;显式 -m SHM 用于 仅 SHM、不走 UDP 的测试

相关机制: 同示例还支持 -m DATA_SHARING(数据共享投递)、-m LARGE_DATA(大消息组合传输),见官方 delivery_mechanisms README。

6.6 QoS 配置

6.6.1 代码中配置 QoS

官方示例: hello_worlddelivery_mechanisms

作用: 在创建 DataWriter / DataReader 时直接设置可靠性、耐久性、历史缓存等。

代码示例(Writer + Reader):

cpp 复制代码
// DataWriter
DataWriterQos wqos = DATAWRITER_QOS_DEFAULT;
wqos.reliability().kind = RELIABLE_RELIABILITY_QOS;
wqos.durability().kind = TRANSIENT_LOCAL_DURABILITY_QOS;
wqos.history().kind = KEEP_LAST_HISTORY_QOS;
wqos.history().depth = 10;

DataWriter* writer = publisher->create_datawriter(topic, wqos, listener, StatusMask::all());

// DataReader(须与 Writer 兼容)
DataReaderQos rqos = DATAREADER_QOS_DEFAULT;
rqos.reliability().kind = RELIABLE_RELIABILITY_QOS;
rqos.durability().kind = TRANSIENT_LOCAL_DURABILITY_QOS;
rqos.history().kind = KEEP_LAST_HISTORY_QOS;
rqos.history().depth = 10;

DataReader* reader = subscriber->create_datareader(topic, rqos, listener, StatusMask::all());

官方 hello_world 中 Writer 片段:

cpp 复制代码
DataWriterQos writer_qos = DATAWRITER_QOS_DEFAULT;
writer_qos.history().depth = 5;
publisher_->get_default_datawriter_qos(writer_qos);
writer_ = publisher_->create_datawriter(topic_, writer_qos, this, StatusMask::all());

监听匹配(发布方 Listener):

cpp 复制代码
void on_publication_matched(
        DataWriter*,
        const PublicationMatchedStatus& info) override
{
  if (info.current_count_change == 1)
  {
      // 有订阅方匹配,可以 write
  }
}

6.6.2 XML Profile 配置 QoS

官方示例: examples/cpp/hello_world/hello_world_profile.xml

作用: 将 QoS 从代码剥离到 XML,便于统一部署与修改。

XML 完整示例:

xml 复制代码
<?xml version="1.0" encoding="UTF-8" ?>
<profiles xmlns="http://www.eprosima.com">
    <participant profile_name="hello_world_participant_profile" is_default_profile="true">
        <domainId>0</domainId>

        <rtps>
            <name>hello_world_participant</name>

        </rtps>

    </participant>

    <data_writer profile_name="hello_world_datawriter_profile" is_default_profile="true">
        <qos>
            <durability><kind>TRANSIENT_LOCAL</kind></durability>

            <reliability><kind>RELIABLE</kind></reliability>

        </qos>

        <topic>
            <historyQos>
                <kind>KEEP_LAST</kind>

                <depth>100</depth>

            </historyQos>

            <resourceLimitsQos>
                <max_samples>100</max_samples>

                <max_instances>1</max_instances>

                <max_samples_per_instance>100</max_samples_per_instance>

            </resourceLimitsQos>

        </topic>

        <times>
            <heartbeat_period>
                <sec>0</sec>

                <nanosec>100000000</nanosec>

            </heartbeat_period>

        </times>

    </data_writer>

    <data_reader profile_name="hello_world_datareader_profile" is_default_profile="true">
        <qos>
            <durability><kind>TRANSIENT_LOCAL</kind></durability>

            <reliability><kind>RELIABLE</kind></reliability>

        </qos>

        <topic>
            <historyQos>
                <kind>KEEP_LAST</kind>

                <depth>100</depth>

            </historyQos>

            <resourceLimitsQos>
                <max_samples>100</max_samples>

                <max_instances>1</max_instances>

                <max_samples_per_instance>100</max_samples_per_instance>

            </resourceLimitsQos>

        </topic>

    </data_reader>

</profiles>

启用 XML 的方式:

bash 复制代码
# Linux / macOS
export FASTDDS_DEFAULT_PROFILES_FILE=/path/to/hello_world_profile.xml

# Windows PowerShell
$env:FASTDDS_DEFAULT_PROFILES_FILE = "C:\path\to\hello_world_profile.xml"

代码中按 Profile 名创建:

cpp 复制代码
factory->load_profiles_file("hello_world_profile.xml");

participant_ = factory->create_participant_with_profile(
        0, "hello_world_participant_profile", nullptr, StatusMask::none());

writer_ = publisher_->create_datawriter_with_profile(
        topic_, "hello_world_datawriter_profile", listener, StatusMask::all());

同类 XML: examples/cpp/configuration/configuration_profile.xml 提供 Participant + Writer/Reader 的 Profile 组合,用法相同。

6.7 综合对照表

维度 官方路径 关键代码 / 配置 典型命令
基础通讯 hello_world/ create_participant_with_default_profile + write ./hello_world publisher
简单发现 hello_world/ 默认,无需改 discoveryProtocol 同上
Discovery Server discovery_server/ SERVER / CLIENT + m_DiscoveryServers ./discovery_server server
静态 EDP static_edp_discovery/ use_STATIC_EndpointDiscoveryProtocol + XML ./static_edp_discovery publisher
UDP delivery_mechanisms/ UDPv4TransportDescriptor -m UDPv4
TCP delivery_mechanisms/ TCPv4TransportDescriptor + Locator -m TCPv4
SHM delivery_mechanisms/ SharedMemTransportDescriptor -m SHM
QoS 代码 hello_world/ DataWriterQos / DataReaderQos ---
QoS XML hello_world_profile.xml FASTDDS_DEFAULT_PROFILES_FILE 设环境变量后运行 hello_world
安全通讯 security/ XML + certs/ ./security publisher

7.报文解释

报文 分为PDP阶段、EDP阶段与数据传输阶段

PDP阶段

EDP阶段

数据传输

对于TCP的dds,很多情况下wireshark是不能解析RTPS报文的,因为tcp的dds报文是以rtcp开头不是以rtps开头所以不能只能识别,不过通过tcp协议包也可以获取具体通讯情况,但是这样分析相对较复杂

8.进一步学习

主题 官方文档
快速入门 Getting Started
DDS 层 API DDS Layer
发现机制 Discovery
QoS QoS Policies
XML 配置 XML profiles
fastddsgen Fast DDS-Gen

文档版本:适用于 Fast DDS 3.x;示例名称与路径以 eProsima 官方发布为准。

相关推荐
Rocktech_ruixun2 小时前
机器人视觉SLAM对主控硬件有哪些要求?瑞迅科技RK3588/3576/3568分级方案解析
人工智能·嵌入式硬件·机器人
xiaoduo AI2 小时前
抖音小店客服机器人哪个好?选择AI客服主要看哪些功能?
大数据·人工智能·机器人
茶栀(*´I`*)3 小时前
Python网络机器人入门:从Robots协议、网页结构到Requests库的基础实践
网络·python·机器人
xwz小王子3 小时前
Science Robotics | 从模仿到创造:BeyondMimic如何让机器人“学到”人类的敏捷与多才多艺
人工智能·机器人
2601_967659893 小时前
2026停车场巡检机器人排行:地下停车场夜间巡逻怎么选
人工智能·机器人
workflower3 小时前
TF-IDF 的基本思想
人工智能·机器学习·机器人·云计算·无人机
大唐荣华4 小时前
模型训练显卡选型深度指南:价格、数量、性能与自建租赁抉择
人工智能·机器人·模型训练·具身智能·avida·navida
TaoMetrix5 小时前
Microduck 爆火之后:TaoMetrix 如何帮助品牌抓住 AI 陪伴机器人新机会
人工智能·机器人
CoreTK_EMC12 小时前
机器人信号线缆屏蔽:解决伺服干扰、通讯异常的工程落地思路
网络·机器人·芯通康·机器人信号线缆屏蔽·工业机器人线缆 emc·机器人接地屏蔽·ethercat 通讯干扰