核心结论
rmw_cyclonedds 的序列化不是直接使用 Cyclone DDS 的标准序列化,而是采用了一种"桥接式"的设计方案:
- 序列化实现:完全自定义,基于 ROS 类型自译
- 接口规范:严格遵循 Cyclone DDS 的底层接口标准
- 数据格式:生成的 CDR 数据符合标准,可与其他 DDS 实现互通
架构分层
1. 序列化层(自定义实现)
rmw_cyclonedds 实现了一套完整的 CDR 序列化基础设施:
核心组件位于 Serialization.cpp:
CDRCursor 抽象接口(第31-61行)
- 定义了游标的通用操作:offset()、advance()、put_bytes()
- 提供对齐功能 align()
- 虚基类,供具体实现继承
SizeCursor(第63-83行)
- 只计算偏移量,不实际写入数据
- 用于预先计算序列化后的总大小
- ignores_data() 返回 true
DataCursor(第85行起)
- 实际写入数据到内存缓冲区
- 从 SizeCursor 或直接初始化
- ignores_data() 返回 false
这种设计允许先计算大小再分配内存,然后一次性完成序列化,避免了多次内存分配。
2. 集成层(标准接口)
虽然序列化逻辑是自定义的,但数据包装严格遵循 Cyclone DDS 的接口规范:
核心类位于 serdata.hpp:
- serdata_rmw 类(第58行)
- 继承自
ddsi_serdata(Cyclone DDS 的标准接口) - 包含序列化数据缓冲区
m_data - 包含数据大小
m_size
- 继承自
核心操作位于 serdata.cpp:
serialize_into_serdata_rmw()(第67行)
- 将 ROS 消息样本序列化到 serdata 缓冲区
- 支持普通消息和服务请求(带请求头)
- 处理异常并设置错误信息
serdata_rmw_from_ser()(第145行)
- 从网络接收的 fragment chain 创建 serdata 对象
- 处理多片段重组
- 确保数据完整性
serdata_rmw_from_ser_iov()(第176行)
- 从 iovec 数组创建 serdata 对象
- 支持零拷贝接收路径
为什么需要自定义序列化?
直接使用 Cyclone DDS 的标准序列化无法满足 ROS 的特殊需求,自定义实现解决了以下问题:
1. 支持动态类型系统
- 通过 ROS 类型自译获取消息结构
- 无需预编译类型支持代码
- 支持运行时类型发现
2. 处理复杂消息类型
- 嵌套消息结构
- 数组和序列类型
- 字符串(包括 UTF-16)特殊处理
3. 服务通信扩展
- 请求/响应关联
- 服务调用头信息注入
- 序列管理
4. 性能优化
- 基于类型自译的直接访问
- 避免中间转换层
- 支持零拷贝优化路径
数据流示意
发送路径(序列化)
ROS 消息样本
↓
类型自译信息(TypeSupport)
↓
CDR 序列化器(CDRCursor)
↓
serdata_rmw 缓冲区
↓
Cyclone DDS 发送 接收路径(反序列化)
Cyclone DDS 接收
↓
serdata_rmw 缓冲区
↓
CDR 反序列化器
↓
类型自译信息
↓
ROS 消息样本 关键代码位置速查
| 功能 | 文件 | 行号 |
|---|---|---|
| CDR 游标接口 | Serialization.cpp | 31-61 |
| 大小计算游标 | Serialization.cpp | 63-83 |
| 数据写入游标 | Serialization.cpp | 85- |
| serdata 类定义 | serdata.hpp | 58 |
| 序列化入口 | serdata.cpp | 67 |
| 网络数据接收 | serdata.cpp | 145 |
| 共享内存支持 | serdata.cpp | 100-119 |
扩展阅读
如果想深入了解序列化的更多细节,建议按以下顺序学习:
- ROS 类型自省与类型支持生成 - 理解如何从 ROS 类型生成类型支持
- CDR 序列化实现 - 深入了解 SizeCursor 和 DataCursor 的实现
- Serdata 操作 - 学习样本到序列化形式的转换过程
- 基于 Iceoryx 的共享内存支持 - 了解零拷贝通信的序列化优化