CDR--- 数据序列化格式(DDS的底层数据支持)

1. 概述

1.1 CDR定义

CDR (Common Data Representation) 是由OMG (Object Management Group) 制定的二进制数据序列化标准 ,定义了如何将IDL (Interface Definition Language) 描述的数据结构转换为平台无关的字节流,以及如何从字节流恢复原始数据结构

与其他序列化格式不同,CDR从设计之初就专门针对分布式实时系统优化,具有以下核心优势:

  • 跨平台:自动处理不同硬件架构的字节序和对齐差异(大小端)
  • 跨语言互操作:支持C++、Java、Python、C#等所有主流编程语言
  • 高性能:二进制编码,无冗余元数据开销,接近内存拷贝速度
  • 强类型安全:基于IDL静态类型检查,避免运行时类型错误
  • 标准性:唯一的国际标准二进制序列化格式,保证不同厂商产品的互操作性

1.2 标准演进

CDR标准经历了三次重大演进,每次都在性能和灵活性之间取得了更好的平衡:

版本 标准来源 发布时间 核心特性 主要应用
原始CDR OMG formal/02-06-51 (CORBA 3.0) 2002年 基础类型与复合类型支持,对齐规则定义 CORBA,早期DDS
XCDR v1 OMG DDS-XTypes v1.0 2010年 扩展类型支持,参数化CDR (PL_CDR),可选成员 DDS v1.4,ROS2 Humble及更早版本
XCDR v2 OMG DDS-XTypes v1.3 2018年 64位类型4字节对齐,DELIMITED_CDR,PL_CDR2,wchar优化 DDS v1.6,ROS2 Iron及以后版本

1.3 主要应用领域

CDR已经成为以下领域的标准序列化格式:

  • DDS (Data Distribution Service):所有DDS实现的默认序列化格式
  • ROS2 (Robot Operating System 2):机器人领域最流行的通信协议
  • 工业自动化:OPC UA PubSub、工业以太网等实时控制系统
  • 车载通信:AUTOSAR Adaptive平台的SOME/IP与DDS融合
  • 航空航天:高可靠性实时数据交换系统

2. 三大设计原则

2.1 字节序自动处理机制

字节序是指多字节数据在内存中的存储顺序,不同硬件架构使用不同的字节序:

  • 大端序 (Big-Endian):最高有效字节存储在最低地址(网络字节序)
  • 小端序 (Little-Endian):最低有效字节存储在最低地址(x86/x86_64架构)

CDR字节序处理规则

  1. 每个CDR封装流的开头包含一个字节序标志位:0表示大端序,1表示小端序
  2. 发送方可以选择任意字节序进行序列化(通常选择本机字节序以提高性能)
  3. 接收方必须根据标志位自动进行字节序转换
  4. 在RTPS协议中,字节序标志位于DATA子消息的标志位中

示例 :32位整数0x12345678的字节表示

  • 大端序:0x12 0x34 0x56 0x78
  • 小端序:0x78 0x56 0x34 0x12

2.2 自然边界对齐规则

CDR要求所有数据类型必须按其自然边界对齐,这是为了匹配大多数CPU的内存访问模式。如果数据没有对齐,CPU可能需要进行两次内存访问才能读取完整的数据,严重影响性能。

  • 对齐 = 数据的起始地址必须是某个数的整数倍
  • 凑 = 不够就往前面塞空字节(填充),强行凑到要求的位置

1 字节对齐:随便放

2 字节对齐:只能放 2、4、6、8... 号位

4 字节对齐:只能放 4、8、12、16... 号位

8 字节对齐:只能放 8、16、24、32... 号位

原始CDR对齐规则
数据类型 大小(字节) 对齐要求(字节)
char/octet/boolean 1 1
wchar 4 4
short/unsigned short 2 2
long/unsigned long/float 4 4
long long/unsigned long long/double 8 8
long double 16 16
string/sequence 4(长度域) 4
XCDR v2对齐优化

XCDR v2对对齐规则进行了重大修改,这是XCDR v2相比XCDR v1最显著的性能提升:

  • int64/uint64/float64/double 从8字节对齐改为4字节对齐
  • wchar 从4字节(UTF-32)改为2字节(UTF-16)
  • 这一改变平均减少了15-30%的序列化后数据大小,特别是在包含多个64位类型的结构体中

对齐示例

idl 复制代码
struct Example {
    short a;    // 2字节
    long b;     // 4字节
    char c;     // 1字节
    double d;   // 8字节
};
  • 原始CDR总大小:2 + 2(填充) + 4 + 1 + 7(填充) + 8 = 24字节
  • XCDR v2总大小:2 + 2(填充) + 4 + 1 + 3(填充) + 8 = 20字节(注意:double现在4字节对齐,省去了4字节的填充)

2.3 无自描述性设计

CDR是一种无自描述性 的序列化格式,这意味着字节流中不包含任何类型元数据信息通信双方必须预先通过IDL达成类型共识,接收方必须使用与发送方完全相同的类型定义才能正确解析

这种设计的优缺点非常明显:

  • 优点:极致的性能和最小的数据大小,没有任何元数据开销
  • 缺点:灵活性较差,类型变更需要双方同时升级

3. 数据类型系统

CDR支持所有OMG IDL定义的数据类型,这些类型可以分为基本类型、复合类型和特殊类型三大类。

3.1 基本数据类型

基本类型是所有复合类型的基础,它们的序列化格式是固定的:
"序列化格式" 列的大小,是数据本身的大小,不包括任何填充字节

IDL类型 描述 序列化格式
boolean 布尔值 1字节,0=false,1=true
char 8位字符 1字节,ISO 8859-1编码
wchar 宽字符 XCDR v1:4字节(UTF-32),XCDR v2:2字节(UTF-16)
octet 8位无符号整数 1字节
short 16位有符号整数 2字节,按指定字节序
unsigned short 16位无符号整数 2字节,按指定字节序
long 32位有符号整数 4字节,按指定字节序
unsigned long 32位无符号整数 4字节,按指定字节序
long long 64位有符号整数 8字节,按指定字节序
unsigned long long 64位无符号整数 8字节,按指定字节序
float 32位浮点数 4字节,IEEE 754标准
double 64位浮点数 8字节,IEEE 754标准
long double 128位浮点数 16字节,IEEE 754标准

3.2 复合数据类型

复合类型由基本类型或其他复合类型组成,是实际应用中最常用的类型。

3.2.1 结构体 (Struct)
  • 成员按照声明顺序依次序列化
  • 每个成员都必须满足其自身的对齐要求
  • 结构体整体对齐要求等于其最大成员的对齐要求
  • 结构体末尾可能需要填充字节以满足整体对齐
3.2.2 联合 (Union)
  • 首先序列化判别符 (discriminator)(整数、字符或枚举类型)
  • 然后序列化与判别符对应的成员
  • 对于可变联合,还需要序列化成员头信息
3.2.3 枚举 (Enum)
  • 序列化为其底层整数类型
  • 默认底层类型为long(32位)
  • XCDR v2支持指定更小的底层类型以节省空间
3.2.4 数组 (Array)
  • 固定长度数组直接序列化所有元素
  • 元素按照数组顺序依次排列
  • 每个元素都必须满足其自身的对齐要求
3.2.5 序列 (Sequence)
  • 首先序列化一个unsigned long类型的长度域(表示元素个数)
  • 然后序列化指定数量的元素
  • 空序列的长度域为0
3.2.6 字符串 (String)
  • 首先序列化一个unsigned long类型的长度域
  • 然后序列化字符串内容,包括末尾的空字符'\0'
  • 长度域包含空字符的长度,因此空字符串的长度域为1

3.3 特殊数据类型

3.3.1 Any类型
  • 可以存储任意IDL类型的值
  • 序列化格式:类型码(TypeCode) + 实际值
  • 是CDR中唯一包含元数据的类型
3.3.2 值类型 (Value Type)
  • 支持继承和多态
  • 序列化格式:类型标识 + 成员数据
  • 类型标识用于在接收方确定具体的派生类型

4. 序列化格式演进

CDR标准定义了四种主要的序列化格式,分别适用于不同的可扩展性需求

4.1 PLAIN_CDR

  • 最基础的序列化格式
  • 没有额外的头部信息
  • 成员按照声明顺序连续排列
  • 不支持类型扩展和可选成员
  • 序列化效率最高
  • 适用于FINAL类型

4.2 PL_CDR (Parameterized CDR)

  • XCDR v1引入,用于MUTABLE类型
  • 每个成员前面都有一个成员头
  • 成员头包含成员ID和成员序列化长度
  • 成员可以按照任意顺序排列
  • 接收方可以忽略未知成员
  • 支持可选成员

4.3 DELIMITED_CDR

  • XCDR v2引入,用于APPENDABLE类型
  • 在PLAIN_CDR2编码前添加一个DHEADER (Delimiter Header)
  • DHEADER包含字节序标志和后续数据的长度
  • 接收方可以跳过整个对象而不需要解析其内容
  • 支持向后兼容:旧版本可以读取新版本的消息(忽略新增成员)

4.4 PL_CDR2

  • XCDR v2引入,用于MUTABLE类型
  • 在PL_CDR基础上增加了DHEADER
  • 每个成员前面都有一个EMHEADER (Extended Member Header)
  • EMHEADER包含成员ID、必须理解标志和成员长度
  • 必须理解标志用于指示接收方是否必须理解该成员
  • 支持向前和向后兼容
  • 灵活性最高,但序列化效率最低

5. 封装格式详解

CDR字节流的封装格式根据应用场景略有不同,主要有以下三种:

5.1 标准CDR封装

用于CORBA和早期DDS实现:

复制代码
+-----------------+-------------------------+
| 字节序标志(1字节) | 实际数据(按CDR规则编码) |
+-----------------+-------------------------+
  • 实际数据从偏移1开始,所有对齐都相对于偏移1

5.2 DDS CDR封装

DDS使用的封装格式在标准CDR基础上增加了版本信息:

复制代码
+-----------------+-----------------+-------------------------+
| 字节序标志(1位)  | 版本(7位)        | 选项(8位)               |
+-----------------+-----------------+-------------------------+
| 实际数据(按CDR规则编码)                                      |
+-------------------------------------------------------------+
  • 版本字段:0x00=XCDR v1,0x01=XCDR v2
  • 实际数据从偏移2开始,所有对齐都相对于偏移2

5.3 DHEADER格式

用于XCDR v2中的DELIMITED_CDR和PL_CDR2:

复制代码
+-----------------+-------------------------------------------+
| 字节序标志(1位)  | 数据长度(31位)                            |
+-----------------+-------------------------------------------+
  • 数据长度表示后续数据的字节数,不包括DHEADER本身

6. 可扩展机制

XCDR最强大的特性之一就是其完善的可扩展性机制,它允许类型在不破坏现有通信的情况下进行演进。

6.1 三种可扩展性类型

XCDR定义了三种可扩展性类型,通过IDL注解指定

6.1.1 @final
  • 类型不能被扩展
  • 不能添加、删除或修改任何成员
  • 使用PLAIN_CDR或PLAIN_CDR2编码
  • 序列化效率最高
  • 适用于稳定不变的基础类型
6.1.2 @appendable
  • 只能在类型末尾添加新成员
  • 不能修改或删除现有成员
  • XCDR v1使用PLAIN_CDR编码
  • XCDR v2使用DELIMITED_CDR编码
  • 支持向后兼容:旧版本可以读取新版本的消息
  • 适用于大多数业务类型
6.1.3 @mutable
  • 可以添加、删除和重排序成员
  • 可以修改成员的可选性
  • 不能修改现有成员的类型
  • 使用PL_CDR或PL_CDR2编码
  • 支持向前和向后兼容
  • 灵活性最高,但序列化效率最低
  • 适用于频繁变更的类型

6.2 可选成员

XCDR支持可选成员,通过@optional注解指定:

  • 可选成员可以存在也可以不存在
  • 如果可选成员不存在,则不会被序列化
  • 接收方可以通过检查成员头来确定成员是否存在
  • 可选成员总是使用参数化CDR编码

6.3 成员ID

每个成员都有一个唯一的成员ID,通过@id注解指定:

  • 成员ID用于在参数化CDR中标识成员
  • 如果没有显式指定,成员ID会自动从0开始分配
  • 建议显式指定成员ID以避免版本变更时的冲突
  • 成员ID在类型的继承层次中必须唯一

版本演进示例

idl 复制代码
// v1版本
@appendable
struct Person {
    @id(0) string name;
    @id(1) long age;
};

// v2版本(向后兼容)
@appendable
struct Person {
    @id(0) string name;
    @id(1) long age;
    @id(2) string email; // 在末尾添加新成员
};

7. 主流实现

目前最流行的CDR实现有以下三个,都符合OMG标准:

7.1 eProsima FastCDR

  • 最流行的开源CDR实现,被Fast DDS和ROS2广泛使用
  • 完全支持XCDR v1和XCDR v2
  • 提供两种序列化模式:
    • 标准模式:完全符合OMG标准,支持所有特性
    • Fast模式:修改版CDR,不使用对齐,速度更快但不兼容标准
  • 高性能,低内存占用
  • 支持C++11及以上版本
  • 提供动态序列化API,不需要代码生成

7.2 RTI Connext CDR

  • 商业级CDR实现,性能和稳定性最佳
  • 完全支持XCDR v1和XCDR v2
  • 提供丰富的配置选项
  • 支持可扩展性合规性掩码,可以调整序列化行为以兼容不同版本
  • 提供FlatData语言绑定,进一步优化大型数据的序列化性能
  • 提供完善的文档和技术支持

7.3 Eclipse Cyclone DDS CDR

  • 轻量级,高性能的开源CDR实现
  • 完全支持XCDR v1和XCDR v2
  • 基于EPL许可证,商业友好
  • 特别适合嵌入式和资源受限环境
  • 代码简洁,易于理解和修改

8. 性能分析与对比

8.1 CDR内部格式性能对比

编码格式 序列化速度 反序列化速度 数据大小 灵活性
PLAIN_CDR2 ★★★★★ ★★★★★ ★★★★★ ★☆☆☆☆
DELIMITED_CDR ★★★★☆ ★★★★☆ ★★★★☆ ★★★☆☆
PL_CDR2 ★★★☆☆ ★★★☆☆ ★★★☆☆ ★★★★★
PL_CDR ★★☆☆☆ ★★☆☆☆ ★★☆☆☆ ★★★★☆

8.2 与其他序列化格式对比

特性 CDR (XCDR2) Protobuf FlatBuffers JSON
序列化速度 最快
反序列化速度 几乎瞬时 最慢
数据大小 最小 中等 最大
跨语言支持 极好 极好
可扩展性 极好 极好
自描述性
零拷贝支持 有限 完全
标准性 国际标准 公司标准 公司标准 国际标准
实时性 极好 极好
多态支持

对于分布式实时系统,CDR是最佳选择

9. CDR在ROS2中的应用

ROS2完全基于DDS构建,因此CDR是ROS2消息的默认序列化格式。

9.1 ROS2消息与IDL的关系

ROS2的.msg文件本质上是IDL的简化语法,在编译时会自动转换为标准IDL,然后生成CDR序列化/反序列化代码

例如,以下ROS2消息:

msg 复制代码
string name
int32 age
float64 height

会被转换为以下IDL:

idl 复制代码
struct Person {
    string name;
    long age;
    double height;
};

9.2 ROS2中的CDR版本

  • ROS2 Humble及更早版本:默认使用XCDR v1
  • ROS2 Iron及以后版本:默认使用XCDR v2
  • 不同版本之间可以通过配置实现互操作

9.3 ROS2中的CDR性能优化

  • 使用--ros-args --param use_sim_time:=true可以减少时间戳的序列化开销
  • 对于大型数据(如点云、图像),使用零拷贝传输
  • 合理设计消息结构,避免嵌套过深和过多的可选成员

10. 实际应用

10.1 类型设计

  1. 优先使用@final类型 :除非确实需要扩展,否则使用@final类型以获得最佳性能
  2. 合理安排成员顺序:将相同大小的成员放在一起,减少填充字节
  3. 显式指定成员ID:为所有成员显式指定@id注解,避免版本变更时的冲突
  4. 避免使用可选成员:可选成员会增加序列化开销,尽量使用默认值代替
  5. 使用固定长度数组:对于长度固定的数据,使用数组而不是序列
  6. 限制字符串长度:为字符串指定最大长度,避免内存溢出

10.2 性能优化

  1. 升级到XCDR v2:XCDR v2比XCDR v1平均快20%,数据大小减少15-30%
  2. 预分配缓冲区:提前计算序列化大小并预分配缓冲区,避免动态内存分配
  3. 使用零拷贝技术:在支持的平台上使用零拷贝API,减少数据复制
  4. 避免嵌套过深:减少数据结构的嵌套层次,提高序列化效率
  5. 批量处理数据:将多个小消息合并为一个大消息,减少序列化开销

10.3 兼容性

  1. 遵循可扩展性规则:严格遵守@final、@appendable和@mutable类型的扩展规则
  2. 永远不要修改现有成员:不要修改现有成员的类型、ID或顺序
  3. 使用必须理解标志:对于关键成员,设置必须理解标志以确保接收方正确处理
  4. 测试兼容性:在发布新版本之前,测试与旧版本的双向兼容性
  5. 使用标准模式:除非你完全控制所有通信节点,否则不要使用FastCDR的Fast模式

11. 常见问题

11.1 字节序问题

问题 :不同架构的设备之间通信时出现数据乱码
解决方案

  • 确保CDR实现正确处理字节序标志
  • 在RTPS协议中,检查DATA子消息的字节序标志位
  • 避免手动操作字节流,使用CDR库提供的API

11.2 对齐问题

问题 :序列化后的数据大小与预期不符
解决方案

  • 了解不同CDR版本的对齐规则差异
  • 使用CDR库提供的大小计算API预先计算序列化大小
  • 在XCDR v2中,64位类型是4字节对齐,不是8字节对齐

11.3 兼容性问题

问题 :不同版本的应用之间无法通信
解决方案

  • 检查可扩展性类型是否正确
  • 确保成员ID没有冲突
  • 检查是否使用了相同的CDR版本
  • 在RTI Connext中,调整可扩展性合规性掩码

11.4 性能问题

问题 :序列化/反序列化速度太慢
解决方案

  • 切换到XCDR v2
  • 使用@final类型代替@mutable类型
  • 减少可选成员的使用
  • 预分配缓冲区
  • 使用FastCDR的标准模式(比其他实现更快)
相关推荐
Yang96111 小时前
鼎讯 IN51 水平极化全向监测天线:铁路高速频谱监测优选
信息与通信
刘大猫.2 小时前
重塑经典:Snapseed4.0全面登陆安卓,内置“胶片相机”与专业手动模式
android·数码相机·ai·机器人·大模型·算力·snapseed4.0
workflower3 小时前
人工智能全球治理
大数据·人工智能·设计模式·机器人·动态规划
workflower3 小时前
AI灵活高效的智慧用能核心场景
大数据·人工智能·设计模式·机器人·动态规划
kyle~3 小时前
RTPS(Real-Time Publish-Subscribe)---DDS的传输协议
c++·机器人·ros2
MIXLLRED5 小时前
Ubuntu 22.04 + ROS2 Humble 环境下对 RealSense D435 进行深度校准
ros2·校准·d435·深度图校准
一路往蓝-Anbo14 小时前
第二章:隔离硬件 —— 利用 CMock 伪造 GPIO 与定时器
stm32·单片机·嵌入式硬件·软件工程·信息与通信·tdd
郭龙飞98019 小时前
OpenClaw 飞书机器人搭建指南 远程 AI 操控电脑配置
人工智能·windows·机器人·飞书
数智工坊21 小时前
具身智能人形机器人:从实验室走向现实的下一代通用智能体
人工智能·深度学习·机器人