cdr序列化与反序列化
CDR(Common Data Representation)是 CORBA(Common Object Request Broker Architecture)中用于跨平台数据交换的二进制序列化格式。它解决了不同系统间(不同字节序、对齐方式)的数据兼容性问题。以下是 CDR 序列化和反序列化的详细解析:
CDR 核心特性
- 平台无关性
- 自动处理字节序(大端/小端)
- 处理数据对齐(如 4 字节对齐)
- 类型安全
- 支持 CORBA IDL 定义的所有数据类型
- 无自描述性
- 依赖 IDL 接口定义进行解析(无元数据)
CDR 序列化流程
以结构体 Person
为例:
// IDL 定义
struct Person {long id; // 4 字节整数string name; // 变长字符串
};
序列化步骤:
-
写字节序标志(1 字节)
0x00
表示大端序,0x01
表示小端序
-
序列化
id
(4 字节整数)- 按字节序写入(如
id=4487
→ 大端序0x00 0x00 0x11 0x87
)
- 按字节序写入(如
-
序列化字符串
name
:- 先写长度(4 字节无符号长整型)
- 如
"Alice"
→ 长度5
→0x00 0x00 0x00 0x05
- 如
- 再写字符串内容(含结束符
\0
)A l i c e \0
→0x41 0x6C 0x69 0x63 0x65 0x00
- 填充对齐:总长度按 4 字节对齐
- 示例:6 字节内容 → 填充 2 字节 →
0x00 0x00
- 示例:6 字节内容 → 填充 2 字节 →
- 先写长度(4 字节无符号长整型)
-
最终字节流(大端序示例):
| 字节序 | id (4B) | 长度 (4B) | 字符串内容 (6B) | 填充 (2B) |
|--------|-----------|-----------|-----------------|-----------|
| 0x00 | 00 00 11 87 | 00 00 00 05 | 41 6C 69 63 65 00 | 00 00 |
CDR 反序列化流程
-
读字节序标志
- 检测
0x00
/0x01
,决定后续解析方式
- 检测
-
反序列化
id
- 读取 4 字节 → 按字节序转换 →
0x00001187
= 4487
- 读取 4 字节 → 按字节序转换 →
-
反序列化字符串:
- 读长度(4 字节)→
5
- 读内容(5+1 字节)→
"Alice\0"
- 跳过填充字节(2 字节)
- 读长度(4 字节)→
字节序处理机制
// 序列化时的伪代码
void serialize_long(long value, CDRStream& stream) {if (stream.is_big_endian()) {write_bytes(htobe32(value)); // 转大端序} else {write_bytes(htole32(value)); // 转小端序}
}// 反序列化时的伪代码
long deserialize_long(CDRStream& stream) {bytes = read_bytes(4);if (stream.is_big_endian()) {return be32toh(bytes); // 大端转主机序} else {return le32toh(bytes); // 小端转主机序}
}
对齐规则
CDR 要求数据按类型自然边界对齐:
数据类型 | 对齐要求 |
---|---|
char | 1 字节 |
short | 2 字节 |
long/float | 4 字节 |
double | 8 字节 |
string/sequence | 4 字节(长度域) |
示例:
序列化 short + long
时:
short s = 0x1234;
long l = 0x56789ABC;
内存布局(小端序 + 2 字节填充):
| s (2B) | 填充 (2B) | l (4B) |
|--------|-----------|--------------|
| 34 12 | 00 00 | BC 9A 78 56 |
CDR 使用场景
- CORBA 远程调用
- 参数传递和返回值序列化
- DDS(Data Distribution Service)
- 实时系统中的数据发布/订阅
- 金融交易系统
- 交易所间的低延迟数据交换
- 航空电子系统
- ARINC 653 标准中的进程间通信
CDR 优缺点
优点 | 缺点 |
---|---|
零解析开销(直接内存映射) | 无自描述性(需 IDL 定义) |
自动处理跨平台差异 | 不支持动态模式演化 |
支持复杂类型(联合体、序列) | 二进制格式难以人工调试 |
💡 替代方案对比:
- Protocol Buffers:自描述、支持版本演化,但需要运行时解析
- JSON/XML:可读性好,但序列化开销大
实战示例(C++ 使用 ACE_CDR)
#include <ace/CDR_Stream.h>// 序列化
Person person{4487, "Alice"};
ACE_OutputCDR output;
output << person.id << person.name;
ACE_Message_Block* mb = output.begin(); // 获取CDR字节流// 反序列化
ACE_InputCDR input(mb);
long id;
ACE_CDR::Char* name;
input >> id >> name; // 自动处理字节序
通过 CDR 的标准化序列化机制,CORBA 实现了真正的跨平台对象通信,成为分布式系统的基石技术之一。
示例
下面是一个详细的小端序 CDR(Common Data Representation)序列化示例,展示如何在小端序系统上序列化和反序列化数据。我们将使用一个包含多种数据类型的结构体进行演示。
示例结构体定义(IDL)
struct SensorData {unsigned short sensorId; // 2 字节float temperature; // 4 字节string location; // 变长字符串
};
要序列化的数据:
sensorId = 0x1234
(十进制 4660)temperature = 25.5
(浮点数)location = "Lab"
(字符串)
步骤 1:小端序 CDR 序列化
在小端序系统中(如 x86 CPU),数据低位字节存储在低地址:
序列化流程:
- 字节序标志:写入
0x01
(表示小端序) - sensorId (2 字节):
- 原始值:
0x1234
- 小端序:
0x34
→0x12
- 原始值:
- 填充字节:因下个字段是 4 字节 float,需 2 字节填充
- temperature (4 字节):
- 25.5 的 IEEE 754 表示:
0x41CC0000
- 小端序:
0x00
→0x00
→0xCC
→0x41
- 25.5 的 IEEE 754 表示:
- location 字符串:
- 长度(4 字节):
"Lab"
= 3 字符 + 1 终止符 → 长度=4 →0x04000000
(小端序) - 内容:
L
(0x4C
),a
(0x61
),b
(0x62
),\0
(0x00
) - 填充:4 字节内容后无需填充(已是 4 字节对齐)
- 长度(4 字节):
完整字节流(低地址 → 高地址):
| 字段 | 值 (十六进制) | 说明 |
|-------------|-------------------------------|--------------------------|
| 字节序标志 | 01 | 小端序标识 |
| sensorId | 34 12 | 低位在前 |
| 填充 | 00 00 | 对齐下一个4字节字段 |
| temperature | 00 00 CC 41 | 浮点数字节逆序 |
| 字符串长度 | 04 00 00 00 | 长度=4 (小端序) |
| 字符串内容 | 4C 61 62 00 | "Lab\0" |
可视化内存布局:
低地址 → 高地址
01 34 12 00 00 00 00 CC 41 04 00 00 00 4C 61 62 00
步骤 2:小端序 CDR 反序列化
当接收方(小端序系统)读取此字节流时:
- 读字节序标志:
0x01
→ 确认小端序 - 读 sensorId:
- 取 2 字节:
0x34 0x12
- 按小端序解析:
0x1234
= 4660
- 取 2 字节:
- 跳过填充:忽略 2 字节
0x00 0x00
- 读 temperature:
- 取 4 字节:
0x00 0x00 0xCC 0x41
- 按小端序重组:
0x41CC0000
→ 25.5
- 取 4 字节:
- 读字符串:
- 读长度(4 字节):
0x04 0x00 0x00 0x00
→ 小端序 = 4 - 读 4 字节内容:
0x4C 0x61 0x62 0x00
→ “Lab”
- 读长度(4 字节):
关键机制解析
1. 字节序自动处理
// 伪代码:小端序序列化float
void serialize_float(float value, CDRStream& stream) {uint32_t intRep = *(uint32_t*)&value; // 二进制表示if (stream.is_little_endian()) {write_bytes(intRep); // 直接写入(小端系统)} else {write_bytes(swap_bytes(intRep)); // 大端需转换}
}
2. 对齐填充规则
偏移 | 字段 | 大小 | 对齐要求 | 操作 |
---|---|---|---|---|
0 | 字节序标志 | 1 | - | 写入 0x01 |
1 | sensorId | 2 | 2 | 直接写入 |
3 | <填充> | 2 | 4 | 填充 0x0000 |
5 | temperature | 4 | 4 | 写入浮点 |
9 | 字符串长度 | 4 | 4 | 直接写入 |
13 | 字符串内容 | 4 | 1 | 写入+无填充 |
3. 字符串处理
// 字符串序列化伪代码
void serialize_string(string s, CDRStream& stream) {uint32_t len = s.length() + 1; // 包含 \0serialize_uint32(len); // 写长度(小端序)write_bytes(s.c_str(), len); // 写原始字符// 4字节对齐填充 (字符数据无额外填充)
}
小端序 vs 大端序 CDR 对比
同一数据 SensorData{4660, 25.5, "Lab"}
的差异:
字节序 | CDR 字节流(十六进制) |
---|---|
小端序 | 01 34 12 00 00 00 00 CC 41 04 00 00 00 4C 61 62 00 |
大端序 | 00 12 34 00 00 41 CC 00 00 00 00 00 04 4C 61 62 00 |
差异说明:
- 字节序标志不同(
0x01
vs0x00
) - 数值字段字节逆序:
- sensorId:
34 12
vs12 34
- temperature:
00 00 CC 41
vs41 CC 00 00
- sensorId:
- 长度字段:
04 00 00 00
vs00 00 00 04
实际应用场景
-
嵌入式系统通信(ARM Cortex-M 小端设备)
// 发送端(小端系统) SensorData data = {0x1234, 25.5, "Lab"}; CDR_serialize(&output_stream, data); // 直接内存拷贝// 接收端(同为小端系统) CDR_deserialize(&input_stream, &data); // 零转换开销
-
混合字节序环境(需显式转换)
// 接收方是大端系统(如 PowerPC) CDRStream stream = receive_network_data(); if (stream.endian_flag == 0x01) { // 检测到小端数据convert_cdr_to_host_order(&stream); // 字节序转换 } deserialize(&stream, &data);
重要注意事项
- 字节序标志位置:始终是 CDR 流的第一个字节
- 嵌套结构处理:内部结构不添加额外字节序标志
- 浮点数兼容性:所有平台必须使用 IEEE 754 标准
- 数据对齐:即使在同字节序系统中,填充仍不可省略
- 性能优势:小端系统处理小端 CDR 时,可直接内存映射(零拷贝)
通过这个示例,您可以看到 CDR 如何在小端序系统中高效地处理数据序列化,同时保持跨平台兼容性。核心在于字节序标志和对齐规则的应用,使得不同架构的设备能正确解析数据。