4、数据项编码实现:如何在C语言中实现各数据类型的序列化与反序列化
SECS-II 消息的核心是数据项(Data Item),每个数据项由 数据项头(Item Header) 和 数据体(Data Body) 组成。在 C 语言中实现序列化(将结构体转换为字节流)与反序列化(将字节流还原为结构体),是协议栈移植中最基础且最容易出错的环节。本节将深入剖析 SECS-II 六种基本数据类型的编码规则,并给出可移植的 C 语言实现。
核心原则:SECS-II 采用大端字节序(Big-Endian)传输多字节数据。所有序列化/反序列化操作必须严格遵循大端序,否则会导致跨平台通信失败。
4.1 SECS-II 数据项格式总览
每个 SECS-II 数据项由三部分构成:长度字节(Length Bytes)、数据格式字节(Format Byte) 和 数据体(Data Body)。格式字节的高 4 位表示数据类型,低 4 位表示长度字节数(通常为 1 或 3)。
4.2 六种基本数据类型编码规则
SECS-II 定义了六种核心数据类型,每种类型在 C 语言中对应不同的序列化/反序列化策略。下表总结了各类型的格式码、C 语言映射及字节序要求:
| 类型名称 | 格式码 (Hex) | C 语言类型 | 字节序 | 长度字节数 | 典型用途 |
|---|---|---|---|---|---|
| List | 0x01 | 嵌套结构体 | — | 1 或 3 | 消息容器 |
| Binary | 0x00 | uint8_t[] | — | 1 或 3 | 原始字节 |
| Boolean | 0x0A | uint8_t | — | 1 | 标志位 |
| ASCII | 0x02 | char[] | — | 1 或 3 | 字符串 |
| I1 / I2 / I4 / I8 | 0x04~0x07 | int8_t / int16_t / int32_t / int64_t | 大端 | 1 | 有符号整数 |
| U1 / U2 / U4 / U8 | 0x08~0x0B | uint8_t / uint16_t / uint32_t / uint64_t | 大端 | 1 | 无符号整数 |
| F4 / F8 | 0x0C~0x0D | float / double | 大端 | 1 | 浮点数 |
关键点:List 类型不包含数据体,其“长度”表示子项个数而非字节数。Boolean 类型固定使用 1 字节长度,值 0x00 为假,非 0x00 为真。
4.3 序列化核心实现
序列化的本质是将 C 语言变量按大端序写入字节缓冲区。以下给出整数和浮点数的核心序列化函数:
/* 序列化 int32 (I4) 到缓冲区 */
void serialize_i4(uint8_t *buf, int32_t value) {
buf[0] = (uint8_t)((value >> 24) & 0xFF);
buf[1] = (uint8_t)((value >> 16) & 0xFF);
buf[2] = (uint8_t)((value >> 8) & 0xFF);
buf[3] = (uint8_t)(value & 0xFF);
}
/* 序列化 float (F4) 到缓冲区 */
void serialize_f4(uint8_t *buf, float value) {
uint32_t raw;
memcpy(&raw, &value, sizeof(raw));
buf[0] = (uint8_t)((raw >> 24) & 0xFF);
buf[1] = (uint8_t)((raw >> 16) & 0xFF);
buf[2] = (uint8_t)((raw >> 8) & 0xFF);
buf[3] = (uint8_t)(raw & 0xFF);
}
/* 序列化 ASCII 字符串到缓冲区 */
void serialize_ascii(uint8_t *buf, const char *str, uint32_t len) {
memcpy(buf, str, len);
}
注意:浮点数序列化必须通过
memcpy 将 float 转换为 uint32_t 后再进行大端转换,直接强制类型转换会触发未定义行为。ASCII 字符串不需要字节序转换,直接逐字节拷贝即可。
4.4 反序列化核心实现
反序列化是从字节流中按大端序还原出 C 语言变量。以下是对应的反序列化函数:
/* 反序列化 int32 (I4) 从缓冲区 */
int32_t deserialize_i4(const uint8_t *buf) {
return ((int32_t)buf[0] << 24) |
((int32_t)buf[1] << 16) |
((int32_t)buf[2] << 8) |
(int32_t)buf[3];
}
/* 反序列化 float (F4) 从缓冲区 */
float deserialize_f4(const uint8_t *buf) {
uint32_t raw = ((uint32_t)buf[0] << 24) |
((uint32_t)buf[1] << 16) |
((uint32_t)buf[2] << 8) |
(uint32_t)buf[3];
float value;
memcpy(&value, &raw, sizeof(value));
return value;
}
/* 反序列化 ASCII 字符串从缓冲区 */
void deserialize_ascii(const uint8_t *buf, char *out, uint32_t len) {
memcpy(out, buf, len);
out[len] = '\0'; /* 确保字符串终止 */
}
4.5 完整数据项编码与解码流程
实际应用中,序列化/反序列化需要组合格式字节、长度字节和数据体。以下流程图展示了编码一个 I4 数据项的完整步骤:
4.6 跨平台移植注意事项
在嵌入式系统或不同架构间移植时,以下问题必须处理:
- 字节序检测:使用预定义宏(如
__BYTE_ORDER__)或运行时检测,确保只在需要时进行字节序转换。 - 对齐问题:某些 ARM/MIPS 平台不允许非对齐访问,序列化/反序列化时应使用逐字节操作而非结构体强制转换。
- 浮点格式:确保目标平台使用 IEEE 754 标准,否则 F4/F8 序列化结果不兼容。
- 长度字段处理:当数据体长度超过 255 字节时,必须使用 3 字节长度字段(格式字节低 4 位为 0x03)。
推荐实践:在协议栈中统一使用
uint8_t* 作为序列化/反序列化的中间表示,避免直接操作结构体指针。这样可以最大程度保证代码的可移植性和安全性。
4.7 性能优化建议
对于高频消息处理场景,以下优化策略可显著提升吞吐量:
| 优化策略 | 适用场景 | 预期收益 | 实现复杂度 |
|---|---|---|---|
| 批量序列化 | List 中包含多个同类型数据项 | 减少函数调用开销 | 低 |
| 预分配缓冲区 | 已知最大消息长度 | 避免动态内存分配 | 低 |
| 查表法格式解析 | 频繁解析格式字节 | 减少分支预测失败 | 中 |
| SIMD 加速拷贝 | 大型 ASCII/Binary 数据项 | 提升大块数据拷贝速度 | 高 |
通过以上实现,C 语言可以高效、准确地完成 SECS-II 数据项的序列化与反序列化,为上层消息事务处理奠定坚实基础。