DDS针对数据的分布式架构,可以完全去中心化,一个Participant对应一个domainid,每个进程直接通过Participant进行PDP阶段的服务发现,EDP是完成通讯握手,每个Participant的多个Publisher、Subscriber允许共用一个通讯端点,亦可以是多个,典型的是fastdds(高吞吐、低延迟且内存占用低),支持多种QOS,支持单播、组播、共享内存,由于高性能、生态强、ROS2、开源免费等特点,大量运用于机器人、物联网、汽车、医疗等行业
阅读指引
| 你想了解... | 建议阅读章节 |
|---|---|
| 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 API (fastdds/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 分层架构图
说明:
- 应用只调用 DDS API;发现与组包由 RTPS 层完成。
- 默认内置传输通常为 UDP + SHM(同机优先走共享内存)。
- 元流量(发现)与用户数据可共用或拆分传输配置。
1.4 实体关系图(单个 Domain 内)
发布方与订阅方 无需事先知道对方地址 ;在 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 与类型)
类型与 CDR 代码来源:
- 在官方
hello_world示例中,由HelloWorld.idl经 fastddsgen 生成HelloWorld.hpp、HelloWorldPubSubTypes.*、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(端点)报文。
2.2 Discovery Server 模式
集中式发现:Client 只与 Discovery Server 通信,由 Server 转发发现信息。适用于大规模部署或不宜使用多播的网络。
2.3 静态端点发现(Static EDP)
当所有远端 Writer/Reader 的 Topic、QoS、实体 ID 事先已知 时,可关闭动态 SEDP,改用 XML 静态表配置端点,减少发现报文。见第 6.4 节。
3. 通讯与握手时序
3.1 应用层:从匹配到收数
初学者注意:
- 应先等待 matched (或检查匹配状态),再
write,否则可能没有订阅方接收。 - 订阅方在
on_data_available中调用take_next_sample或read,才能拿到数据。
3.2 RTPS 层:RELIABLE 可靠传输
当 Reliability 为 RELIABLE 时,Writer 与 Reader 之间通过 RTPS 子消息确认送达,必要时重传。
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 |
默认 | 默认 | 默认 | 仅 SHM (SharedMemTransportDescriptor) |
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 Profile :
FASTDDS_DEFAULT_PROFILES_FILE指向如hello_world_profile.xml(见 §6.2)。 - 静态发现表 :
HelloWorld_static_disc.xml等(见 §6.5)。
5. 关键接口说明
以下接口均属于 Fast DDS DDS API (namespace 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 指向的应用样本编码进 payload。data_representation 指定 CDR 变体:XCDR_DATA_REPRESENTATION(XCDRv1 / PLAIN_CDR)、XCDR2_DATA_REPRESENTATION(XCDRv2 / DELIMIT_CDR2)。实现须正确设置 payload.length(序列化后字节长度)及 payload.encapsulation(端序等封装头)。write() 发送前会先调用 calculate_serialized_size 再 serialize。返回 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_md5 为 true 时强制走 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;
}
自定义类型时注意:
- 必须实现 §5.3.1 中全部纯虚函数;可选接口按需求重写。
serialize务必设置payload.length;长度错误会导致 RTPS 层截断或越界。- 类型名
set_name与 Topic、发现报文一致。 - 有
@key字段时正确实现compute_key并设is_compute_key_provided = true。 - 优先用 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)、CLIENT、SERVER(Discovery Server)。 |
discovery_config.m_DiscoveryServers |
CLIENT 模式下 Discovery Server 的 Locator 列表。 |
transport().use_builtin_transports |
为 true 时使用内置 UDP+SHM;为 false 时需自行 user_transports 添加描述符。 |
transport().user_transports |
自定义传输列表,如仅 SharedMemTransportDescriptor、UDPv4TransportDescriptor。 |
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_TCPv4 与 setLogicalPort。
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_world、delivery_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 官方发布为准。