1. 架构拓扑与层级抽象
ROS2 的通信体系构建于 DDS (Data Distribution Service) 标准之上。其整体架构自上而下呈现严格的层级划分,每一层仅与相邻层交互。
| 抽象层级 | 核心组件 | 功能描述 |
|---|---|---|
| 应用层 (Application) | ROS2 节点 (Node), 消息 (Message) | 定义业务逻辑与数据结构 (.msg, .srv)。 |
| 客户端库 (RCL) | rclcpp, rclpy, rcl |
提供跨语言的执行器 (Executor)、回调管理与核心API。 |
| 中间件接口 (RMW) | rmw_fastrtps_cpp, rmw_cyclonedds_cpp |
统一的抽象层,将 ROS2 的发布/订阅概念映射到特定的 DDS 厂商实现。 |
| DDS 实体层 | DomainParticipant, Publisher, Subscriber |
管理通信实体生命周期、QoS 策略与历史缓存 (History Cache)。 |
| RTPS 协议层 | 实时发布订阅协议 (RTPS) | 负责节点发现 (Discovery)、数据序列化 (CDR) 与子消息封装。 |
| 传输层 (Transport) | UDP, TCP, SHM (共享内存) | 底层系统网络栈或进程间通信机制,负责二进制流的物理传输。 |
2. 发现机制 (Discovery) 数据流
在应用层数据传输前,底层必须建立通信链路。DDS 使用 RTPS 规范中的简单发现协议 (SDP, Simple Discovery Protocol),该阶段独立于用户数据流。
2.1 参与者发现 (SPDP)
- 动作: 节点启动时,底层
DomainParticipant通过预定义的组播地址 (通常为239.255.0.1) 周期性广播DATA(p)报文。 - 内容: 报文包含该参与者的全局唯一标识符 (GUID) 及期望的通信端口。
- 结果: 局域网内的所有节点建立参与者拓扑图。
2.2 端点发现 (SEDP)
- 动作: 在 SPDP 建立连接后,参与者之间通过单播 (Unicast) 交换端点信息
DATA(w)和DATA(r)。 - 匹配规则: 仅当 Topic 名称、数据类型 (Type) 且 QoS 策略(如 Reliability, Durability)完全兼容时,
DataWriter(发布端点) 与DataReader(订阅端点) 才完成匹配。
3. 用户数据流转完整生命周期
当通信拓扑建立后,单次数据发布的底层流转经历以下标准化流程:
第一阶段:数据生产与序列化 (应用侧 -> DDS侧)
- 内存分配: 应用层实例化消息对象(如
std_msgs::msg::String)并填充数据。 - 调用边界: 调用
publisher->publish(msg),数据传递至 RMW 层。 - CDR 序列化: RMW 层调用 DDS 底层实现,将 C++/Python 语言特定的内存结构转化为跨平台的 CDR (Common Data Representation) 字节流格式。
第二阶段:进入历史缓存 (Writer History Cache, WHC)
- 写入缓存: 序列化后的 CDR 字节流不直接发送,而是存入
DataWriter的 WHC。 - QoS 介入: 根据 History QoS(如
KEEP_LAST, Depth=10),若缓存已满,最旧的数据会被移出或阻塞当前线程。
第三阶段:RTPS 封装与网络传输
- 报文组装: RTPS 引擎从 WHC 提取数据,封装为 RTPS 标准报文。一个完整报文包含:
Header: 包含协议版本、厂商 ID、DomainParticipant GUID 前缀。Submessage Header: 标识子消息类型(如DATA,HEARTBEAT)。Payload: CDR 序列化后的有效载荷。
- 下发 Socket: RTPS 引擎将报文通过 UDP 套接字(或共享内存段)写入操作系统内核缓冲区。
第四阶段:网络接收与可靠性处理
- 内核接收: 目标机器网卡接收电信号/光信号,操作系统内核将 UDP 报文放入接收缓冲区。
- RTPS 解析: 订阅端的 DDS 守护线程轮询或通过事件机制读取 UDP 报文,剥离 RTPS Header 和 Submessage Header。
- Reader 缓存 (RHC): 解析后的 CDR 数据放入
DataReader的 Reader History Cache (RHC)。- 可靠性判定 (Reliability): 若 QoS 设为
RELIABLE,Reader 会向 Writer 发送ACKNACK子消息确认接收;若发现 Sequence Number 断层,则请求重传。
- 可靠性判定 (Reliability): 若 QoS 设为
第五阶段:反序列化与应用通知
- 事件触发: RHC 状态更新触发底层系统的
WaitSet或Listener。 - RMW 唤醒: RMW 层检测到数据到达,唤醒 RCL 层的 Executor。
- 反序列化: 数据从 CDR 字节流反序列化为目标语言的消息结构体。
- 回调执行: Executor 将该消息体放入队列,最终执行用户定义的 Callback 函数。
4. 理想实验的数据流追踪
场景设定: 自主导航系统中,激光雷达节点 (lidar_node) 以 10Hz 频率发布 /scan 话题,规划节点 (planner_node) 订阅该话题。QoS 配置为 Reliability = BEST_EFFORT, History = KEEP_LAST (Depth = 1)。
底层微观行为解析:(时间戳仅示意)
- [t=0ms]
lidar_node调用 publish()。内存中生成包含 1080 个浮点数的距离数组。 - [t=0.5ms] 数据穿过
rmw_cyclonedds_cpp。1080 个浮点数被严格按照大端/小端规则编码为紧凑的 CDR 字节流。 - [t=1ms] 字节流进入
lidar_node的 Writer History Cache。由于 Depth=1,上一帧未能发送的雷达数据将被直接覆盖。 - [t=1.5ms] RTPS 引擎将这段载荷打包。由于配置为
BEST_EFFORT,引擎无需等待HEARTBEAT确认机制,直接将包含DATA子消息的 RTPS 报文推送至 Linux 内核的 UDP 发送队列。 - [t=2ms] 报文经过以太网接口物理传输。
- [t=2.5ms]
planner_node的 UDP 接收线程获取报文。提取 Sequence Number (假设为#42)。 - [t=3ms] RTPS 验证 GUID 并将有效载荷存入 Reader History Cache。即便检测到漏接了
#41数据报文,因策略为BEST_EFFORT,底层不会发起NACK请求重传,直接触发可用事件。 - [t=3.5ms]
rclcpp::Executor检测到条件变量满足,调度订阅回调函数。激光雷达数据流转生命周期结束。