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 节点从 “感知存在” 到 “建立通信” 的完整发现流程。

  1. SPDP 的触发时机
  • 启动时:参与者(节点)启动后立即发送 SPDP 公告(SPDP Hello),向网络宣告 “我已加入”。
    周期性心跳:之后每隔一段时间(默认 3 秒)发送一次心跳(SPDP Heartbeat),证明自己 “仍在线”。
  • 退出时:参与者关闭前发送SPDP Goodbye,告知网络 “我要离开”。
  • 频率:低频率(秒级),因为参与者的生命周期较长(通常与节点进程一致),无需频繁更新。
  1. 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的要求并没有那么高。

Logo

立足具身智能前沿赛道,致力于搭建全球化、开源化、全栈式技术交流与实践共创平台。

更多推荐