ROS2(7)高级篇 FastDDS
1.概述
RWM(Robot Middleware Wrapper 机器人中间件包装),默认情况下,ROS 2 使用 DDS (Data Distribution Service) 作为其中间件 。它与多个 DDS 或 RTPS(DDS 有线协议)供应商兼容。目前支持 eProsima 的 Fast DDS、RTI 的 Connext DDS、Eclipse Cyclone DDS 和 GurumNetworks GurumDDS。它还支持非 DDS RMW 实现,例如 Zenoh。
本文只对Fast DDS进行介绍,它是目前humble默认的DDS中间件。
Fast DDS(Fast RTPS)是由西班牙公司eProsima开发的一个开源的实时发布-订阅中间件实现,基于 DDS 标准。对应到ros2中是rmw_fastrtps_cpp。
具有如下优点:
- 高性能:支持低延迟(微秒级)、高吞吐量通信,针对机器人实时控制场景优化;提供共享内存(Shared Memory) 传输,同一设备内节点通信无需网络开销。
- 高可靠性:内置多种 QoS(服务质量)策略,如 “可靠传输”“历史数据缓存”“生命周期管理”,可应对丢包、节点断线等异常场景。
- 强扩展性:支持动态节点发现(无需手动配置地址),可轻松扩展至数百个节点;兼容多平台(Linux、Windows、macOS、嵌入式系统)和多语言(C++、Python、C#)。
- 开源免费:基于 Apache 2.0 许可,无商业使用限制,开发者可直接查看源码或二次开发。

2. FastDDS配置
2.1 全局发布模式
ROS 2 中的 rmw_fastrtps 默认使用同步发布。这可以通过设置环境变量 RMW_FASTRTPS_PUBLICATION_MODE 配置不同的发布模式:
- ASYNCHRONOUS: 异步发布模式。此模式意味着当发布者调用写入作时,数据将复制到队列中,后台线程(异步线程)负责读取队列并将数据进行发送。
- SYNCHRONOUS: 同步发布模式。此模式意味着数据直接在用户线程的上下文中发送。这意味着在写入作期间发生的任何阻塞调用都会阻止用户线程,从而阻止应用程序继续其作。请务必注意,此模式通常会以较低的延迟产生更高的吞吐率,因为线程之间没有通知或上下文切换。
2.2 XML配置
具体xml的各种配置参考https://fast-dds.docs.eprosima.com/en/latest/fastdds/xml_configuration/xml_configuration.html#xml-profiles
2.2.1 配置方式
有下面方式可以放置该XML文件:
- 可行执行文件目录加载 加载位于当前执行路径中的名为
DEFAULT_FASTDDS_PROFILES.xml的 XML 文件。 - 环境变量加载 使用环境变量 FASTDDS_DEFAULT_PROFILES_FILE 定义的xml文件。
一般会使用环境变量加载来控制,当使用这种方式时,还需要搭配RMW_FASTRTPS_USE_QOS_FROM_XML=1来使用,比如:
export RMW_FASTRTPS_USE_QOS_FROM_XML=1
export FASTRTPS_DEFAULT_PROFILES_FILE=path/xxx.xml
RMW_FASTRTPS_USE_QOS_FROM_XML=1 ,作用是强制 FastDDS 忽略节点代码中定义的 QoS(服务质量)配置,转而使用 XML 配置文件中指定的 QoS 策略。
在 ROS 2 中,节点的 QoS(如可靠性、历史记录深度、存活时间等)有两种配置方式:
- 代码内配置:在创建发布者(Publisher)、订阅者(Subscriber)、服务(Service)等对象时,通过代码显式指定(例如 QoSProfile(reliability=ReliabilityPolicy.RELIABLE))。
- XML 配置文件:通过 FastDDS 专用的 XML 配置文件定义 QoS 策略(如 标签),适用于全局统一配置。
默认情况下,FastDDS 会优先使用代码中定义的 QoS,XML 配置文件中的 QoS 仅在代码未指定时生效(或作为补充)。当设置 RMW_FASTRTPS_USE_QOS_FROM_XML=1 后,FastDDS 会强制忽略代码中定义的 QoS,完全遵循 XML 配置文件中声明的 QoS 策略。即使代码中显式设置了 QoS(如可靠性、历史深度),也会被 XML 中的配置覆盖。
2.2.2 配置示例(不同主题的同步异步)
<?xml version="1.0" encoding="UTF-8" ?>
<profiles xmlns="http://www.eprosima.com/XMLSchemas/fastRTPS_Profiles">
<!-- 默认发布者配置 -->
<publisher profile_name="default_publisher" is_default_profile="true">
<historyMemoryPolicy>DYNAMIC</historyMemoryPolicy>
</publisher>
<!-- 默认订阅者配置 -->
<subscriber profile_name="default_subscriber" is_default_profile="true">
<historyMemoryPolicy>DYNAMIC</historyMemoryPolicy>
</subscriber>
<!-- 同步发布者配置 -->
<publisher profile_name="/sync_topic">
<historyMemoryPolicy>DYNAMIC</historyMemoryPolicy>
<qos>
<publishMode>
<kind>SYNCHRONOUS</kind>
</publishMode>
</qos>
</publisher>
<!-- 异步发布者配置 -->
<publisher profile_name="/async_topic">
<historyMemoryPolicy>DYNAMIC</historyMemoryPolicy>
<qos>
<publishMode>
<kind>ASYNCHRONOUS</kind>
</publishMode>
</qos>
</publisher>
</profiles>
下面进行解释。
<publisher profile_name="default_publisher" is_default_profile="true">
<historyMemoryPolicy>DYNAMIC</historyMemoryPolicy>
</publisher>
- profile_name:配置文件的名称(标识用),这里为default_publisher。
- is_default_profile=“true”:全局默认的发布者配置, 其他未指定特定配置的发布者,都会自动使用该配置。
- historyMemoryPolicy>DYNAMIC:定义发布者缓存历史数据时的内存分配策略:
- DYNAMIC(动态分配):内存会根据需要自动扩容(如历史数据量增加时),避免固定内存大小导致的溢出,但可能有轻微的内存分配开销。
- PREALLOCATED(预分配):初始化时就分配固定大小的内存,无动态扩容开销,但可能因数据量超过预设值而丢失数据。
默认订阅者配置(default_subscriber)和上面一样。
下面是指定的发布者/sync_topic:
<publisher profile_name="/sync_topic">
<historyMemoryPolicy>DYNAMIC</historyMemoryPolicy>
<qos>
<publishMode>
<kind>SYNCHRONOUS</kind>
</publishMode>
</qos>
</publisher>
< publishMode >标签描述了同步还是异步。
2.3 Qos配置
2.3.1 可靠性(Reliability)
是否允许丢包。可选值:
- ReliabilityPolicy.RELIABLE(可靠传输):发布者会重传未被确认的数据,直到接收方收到,不丢包但可能增加延迟。适用于控制指令(如机械臂关节角度)、关键状态信息(如机器人故障报警)等;
- ReliabilityPolicy.BEST_EFFORT(最佳努力):不重传,数据可能丢失,延迟低,适用于实时性优先场景。适用于高频传感器数据(如激光雷达点云、摄像头图像),允许偶尔丢包但需低延迟。
注意,订阅者的可靠性级别不能高于发布者(例如:可靠订阅者可接收可靠 / 最佳努力发布者的数据;最佳努力订阅者只能接收最佳努力发布者的数据)。
xml配置:
<qos>
<!-- 可靠传输 -->
<reliability>
<kind>RELIABLE</kind> <!-- 可选 BEST_EFFORT -->
<!-- 可选:重传超时时间(默认1000ms) -->
<max_blocking_time>
<sec>1</sec>
<nanosec>0</nanosec>
</max_blocking_time>
</reliability>
</qos>
2.3.2 耐久性(Durability)
控制新加入的订阅者是否能收到 “历史数据”(订阅前发布的数据)。可选值:
- DurabilityPolicy.VOLATILE(易失性):新订阅者只能收到订阅后发布的新数据,默认值。
- DurabilityPolicy.TRANSIENT_LOCAL(本地暂时缓存):发布者会在本地缓存历史数据,新订阅者加入时可获取缓存的历史数据(适用于 “晚加入” 的节点)。
- DurabilityPolicy.TRANSIENT/PERSISTENT(分布式 / 持久化):更复杂的分布式缓存(ROS 2 中较少用,依赖中间件支持)。
场景: - 易失性:实时流数据(如 IMU 实时读数),新节点无需历史数据。
- 本地暂时缓存:配置数据(如机器人参数、地图元数据),新启动的节点(如导航节点)需要获取初始配置。
xml配置:
<qos>
<!-- -->
<durability>
<kind>TRANSIENT_LOCAL</kind>
</durability>
</qos>
2.3.3 历史记录(History)
控制发布者 / 订阅者缓存的历史数据量(用于耐久性或重传)。可选值:
- HistoryPolicy.KEEP_LAST(保留最新 N 条):只缓存最近的depth条数据(depth为整数,默认 10)。
- HistoryPolicy.KEEP_ALL(保留所有):缓存所有数据(可能占用大量内存,需谨慎使用)。
配合参数:depth(仅KEEP_LAST有效),指定缓存的最大条数。
<qos>
<!-- 保留最新10条数据 -->
<history>
<kind>KEEP_LAST</kind>
<depth>10</depth>
</history>
</qos>
2.3.4 截止时间(Deadline)
定义数据更新的最大间隔(发布者必须在 Deadline 内发送新数据,否则触发回调通知)。
- deadline(如Duration(seconds=1),表示最大 1 秒必须更新一次)。
场景:周期性数据(如传感器状态),确保数据 “新鲜度”。例如:要求激光雷达每 500ms 至少发送一帧数据,超过则触发异常处理(如切换备用传感器)。
<qos>
<!-- 要求每500ms至少发布一次数据,否则触发 Deadline 回调 -->
<deadline>
<period>
<sec>0</sec>
<nanosec>500000000</nanosec> <!-- 500ms -->
</period>
</deadline>
</qos>
2.3.5 活跃度(Liveliness)
监控发布者是否 “存活”,若发布者在指定时间内未发送数据或心跳,订阅者会认为其 “离线”。可选值:
- LivelinessPolicy.AUTOMATIC(自动):中间件自动发送心跳,无需用户干预(默认)。
- LivelinessPolicy.MANUAL_BY_TOPIC:用户需手动调用 API 发送 “存活” 信号(更灵活但需额外代码)。
<qos>
<!-- 自动发送心跳,2秒内无信号则视为离线 -->
<liveliness>
<kind>AUTOMATIC</kind>
<lease_duration>
<sec>2</sec>
<nanosec>0</nanosec>
</lease_duration>
</liveliness>
</qos>
2.3.6 数据生命周期(Lifespan)
定义数据的 “有效期”,超过该时间后,即使数据未被接收,也会被丢弃(不再重传)。配置参数:lifespan(如Duration(seconds=0.5),数据 500ms 后失效)。
场景:时效性极强的数据(如实时定位结果),过期数据无意义(例如:1 秒前的位置数据对当前控制无价值)。
<qos>
<!-- 数据有效期为1秒,超过1秒的旧数据不再传输 -->
<lifespan>
<duration>
<sec>1</sec>
<nanosec>0</nanosec>
</duration>
</lifespan>
</qos>
2.4 查看当前的Qos配置
最简单的方式就是查看某个节点信息时,执行topic info加上--verbose参数:
ros2 topic info /turtle1/cmd_vel --verbose
显示的Qos如下:
QoS profile:
Reliability: RELIABLE
History (Depth): UNKNOWN
Durability: VOLATILE
Lifespan: Infinite
Deadline: Infinite
Liveliness: AUTOMATIC
Liveliness lease duration: Infinite
2.5 代码配置Qos
rclcpp中的qos.hpp包含了许多已经定义好的Qos策略,都是从Qos派生的派生类:
|派生类名称|关键QoS策略|适用场景|C++ 快速使用代码|Python 快速使用代码|
| 派生类名称 | 核心默认参数(关键QoS策略) | 适用场景 |
|---|---|---|
ClockQoS | 历史:KeepLast(1)、可靠性:BestEffort、耐久性:Volatile | 时钟同步话题(如 /clock) |
SensorDataQoS | 历史:KeepLast(5)、可靠性:BestEffort、耐久性:Volatile | 高频传感器数据(激光雷达、IMU、摄像头等) |
ParametersQoS | 历史:KeepLast(1000)、可靠性:Reliable、耐久性:Volatile | 节点与参数服务器交互(参数获取/设置) |
ServicesQoS | 历史:KeepLast(10)、可靠性:Reliable、耐久性:Volatile | 服务通信(请求/响应,如 /add_two_ints) |
ParameterEventsQoS | 历史:KeepLast(1000)、可靠性:Reliable、耐久性:Volatile | 参数变更事件通知(如 /parameter_events) |
RosoutQoS | 历史:KeepLast(1000)、可靠性:Reliable、耐久性:TransientLocal、生命周期:10秒 | ROS 日志输出(如 /rosout) |
SystemDefaultsQoS | 所有策略均为 SystemDefault(遵循底层中间件默认配置) | 兼容中间件默认行为的通用场景 |
代码中可以在创建发布者等时,构造传入:
node->create_publisher<sensor_msgs::msg::PointCloud2>(
"/lidar/points", // 目标话题
rclcpp::SensorDataQoS() // 显式指定 QoS 派生类,仅对该话题生效
);
也可以修改其中的某些配置:
// 给 /camera/image 话题用 SensorDataQoS,且 depth=10(默认5)
auto camera_pub = node->create_publisher<sensor_msgs::msg::Image>(
"/camera/image",
rclcpp::SensorDataQoS().keep_last(10) // 自定义 depth,仅作用于该话题
);
当然也可以完全自定义:
auto qos = rclcpp::QoS(rclcpp::KeepLast(5)) // 历史记录:保留最新5条
.reliable() // 可靠性:可靠传输
.transient_local() // 耐久性
.deadline(std::chrono::seconds(1)); // 截止时间:1秒内必须更新
3. 简单发现与发现服务
3.1 对比

正如上图所示,左侧是简单发现协议,每个节点都需要和其他节点进行通信发现,而右边是集中式的,每个节点只需和服务通信发现即可。
默认是简答发现协议,对比如下:
| 特性 | 简单发现协议(SDP) | 发现服务(SDS) |
|---|---|---|
| 架构 | 去中心化(P2P),所有节点直接广播发现消息 | 中心化,通过独立服务器(一个或多个)集中管理发现信息 |
| 发现方式 | 多播(默认)或单播,节点间直接交换参与者和端点信息(SPDP+SEDP) | 单播,节点向服务器注册,服务器维护全局视图并通知匹配的客户端 |
| 网络流量 | 随节点数增长呈 N² 级爆炸(每个节点需与其他所有节点通信) | 显著降低(服务器聚合流量,v2.0.2版本比SDP减少 93% 流量) |
| 可靠性 | 多播不可靠(可能丢包),单播需手动配置且无法保证送达 | 基于TCP的可靠传输,所有发现消息通过服务器中转,确保可达性 |
| 多播依赖 | 强烈依赖多播(默认配置),跨子网或复杂网络需额外配置多播路由 | 完全不依赖多播,所有通信使用单播,天然支持跨子网、云环境和高延迟网络 |
| 扩展性 | 适合小规模部署(<100节点),节点数增加时性能急剧下降 | 专为大规模设计(支持数千节点),服务器可级联(树状/网状拓扑),支持动态扩展 |
3.2 多播/SPDP/SEDP介绍
多播(Multicast)是一种网络数据传输方式,核心是 “一对多” 的高效通信,一个发送者发送一份数据,网络会自动将数据转发给所有订阅了该多播地址的接收者,而非发送者单独给每个接收者发一份数据。
多播使用专门的 “多播 IP 地址”(IPv4 范围:224.0.0.0 ~ 239.255.255.255),比如 Fast DDS 默认用 239.255.0.1和239.255.0.2,搭配默认端口+Domain ID进行使用。
比如A往多播地址239.255.0.1的某个端口发送,B需要加入多播组(也绑定该多播地址),然后就能收到该多播地址的数据了。
参与者发现(SPDP)和端点发现(SEDP)是 Fast DDS 简单发现协议(SDP)中两个分工明确的子协议,虽然都依赖多播实现,但在核心作用、传输内容、触发时机等方面有本质区别,共同构成了 DDS 节点从 “感知存在” 到 “建立通信” 的完整发现流程。
- SPDP 的触发时机
- 启动时:参与者(节点)启动后立即发送 SPDP 公告(SPDP Hello),向网络宣告 “我已加入”。
周期性心跳:之后每隔一段时间(默认 3 秒)发送一次心跳(SPDP Heartbeat),证明自己 “仍在线”。 - 退出时:参与者关闭前发送SPDP Goodbye,告知网络 “我要离开”。
- 频率:低频率(秒级),因为参与者的生命周期较长(通常与节点进程一致),无需频繁更新。
- SEDP 的触发时机
- 端点创建时:当参与者内部创建新的发布者 / 订阅者(如node->create_publisher(…)),立即发送 SEDP 公告,告知 “新增了一个可通信的端点”。
- 端点销毁时:端点关闭时发送SEDP Goodbye,告知 “该端点已不可用”。
- 端点信息变更时:若端点的 QoS 或话题等关键信息被修改(罕见),会发送更新公告。
- 频率:与端点的动态性相关,若节点频繁创建 / 销毁端点(如动态切换传感器),SEDP 消息会相应增加,但通常仍低于 SPDP 的心跳频率。
两者对应的地址也是不一样的:
- SPDP 多播地址:239.255.0.1,端口:7400+DomainID
- SEDP 多播地址:239.255.0.2,端口:7401+DomainID
3. DomainID
在 Fast DDS 的简单发现协议(SDP)中,Domain ID是实现逻辑通信域隔离的核心机制。
Domain ID 是一个 0-232 的整数(建议 < 200),用于将网络中的节点划分为不同的逻辑通信域。只有Domain ID 相同的节点才能通过 SDP 自动发现并通信,不同 Domain ID 的节点完全隔离。
同一物理网络中可运行多个独立的 DDS 系统(如机器人集群与监控系统),通过不同 Domain ID 避免参与者、主题、端点等资源冲突。
在 ROS 2 中,通过设置环境变量ROS_DOMAIN_ID控制 Fast DDS 的 Domain ID:
export ROS_DOMAIN_ID=1 # 所有节点需设置相同值
该配置会覆盖 Fast DDS 的默认 Domain ID(0),节点启动后会自动加入对应域。
4. 总结
在机器人、移动机器人领域,节点个数少,且基本上集中在某个或某几个板卡上,对DDS的要求并没有那么高。
更多推荐



所有评论(0)