从帧边界识别到数据编码:嵌入式通信协议与数据序列化方案深度对比
帧格式
定义消息的边界和基本结构,确保接收端能完整提取一帧数据。主要用于“ 一条消息的边界 ”识别。帧格式在外层,定义报文头、长度、类型。
TLV (Type-Length-Value)
TLV 是一种常用的数据编码格式:
- T (Type):字段类型,表示这段数据的含义
- L (Length):数据长度,表示这段数据有多长
- V (Value):具体的数据内容
它的核心目标是:
- 在字节流中自描述每一段数据的类型和长度
- 方便灵活扩展,新增字段不会破坏旧协议
基本结构:
Type | Length | Value |
---|---|---|
1字节 | 2字节 | Length字节数据 |
Type:通常 1 个字节(可以扩展到 2 字节)
Length:表示 Value 的字节数,常用 1–2 字节
Value:实际数据,可以是字符串、数值、二进制块等
LV (Length-Value)
LV (Length-Value) 是一种非常简单的帧格式:
- L (Length):表示数据长度
- V (Value):实际数据内容
它的主要功能是解决帧边界问题,让接收端知道一帧数据从哪里开始、在哪里结束。
基本结构:
Length | Value |
---|---|
2字节 | Length字节数据 |
Length:通常 1–2 个字节,表示 Value 的字节数
Value:实际传输的数据,长度由 Length 决定
Delimited
Delimited(分隔符帧格式)指的是用特定的分隔符(Delimiter)标记一帧数据的开始或结束,从而让接收端知道一条消息在哪里结束,下一条从哪里开始。常见分隔符如下:
- 文本协议:"\n"、"\r\n"(如 HTTP 请求头、CSV 文件)
- 二进制协议:特定字节 0x7E(如 HDLC 协议用 0x7E 标记帧边界)
基本结构:
[ Data1 ] D
[ Data2 ] D
[ Data3 ] D
Data:一帧数据
D (Delimiter):分隔符,标记帧的边界
帧格式总结
帧结构 | 优点 | 缺点 | 串口适配性 | 典型场景 |
---|---|---|---|---|
Delimited | 实现最简单,用分隔符标记帧尾 | 需要转义,数据不能含分隔符 | 适合简单命令传输 | 文本协议、简单 AT 命令 |
LV | 低开销,解析简单,性能好 | 不能灵活扩展,字段固定 | 适合固定结构数据 | Modbus RTU、gRPC Frame |
TLV | 自描述性强,灵活,易扩展 | 帧头比 LV 稍大,解析略复杂 | 适合复杂、可扩展协议 | BLE GATT、SNMP、IoT 自定义协议 |
根据不同的应用场景有不同的选择:
需求场景 | 推荐帧结构 | 原因 |
---|---|---|
只传输简单命令或文本数据 | Delimited | 实现简单,调试方便 |
固定字段、性能优先 | LV | 长度前缀+负载,结构简单 |
多字段、未来可能扩展 | TLV | Type+Length+Value 自描述灵活 |
二进制+高可靠+复杂协议 | TLV + CRC 校验 | 易扩展、可校验数据完整性 |
在工业控制、物联网里,常用的组合是:
[ 起始符 ] + [ 长度 ] + [ Type ] + [ Value ] + [ CRC ]
- 起始符:标记帧开始,避免同步丢失
- 长度:明确帧结束位置
- Type+Value:支持多字段、可扩展
- CRC:保证数据可靠
这样结合了 Delimited + LV + TLV 的优点,最通用,也最安全
数据序列化方案
定义帧内有效载荷数据如何编码成字节流,保证结构化数据可传输。将内存中的结构化数据(如对象、数组、字典等)转换为可存储、可传输的线性格式(通常是二进制流或文本字符串)的过程。
JSON
JSON(JavaScript Object Notation)是一种轻量级的数据交换格式, 基于键值对(key-value)表示数据 ,广泛用于前后端通信、配置文件、存储和物联网等场景。
示例:
{"name": "Alice","age": 30,"isStudent": false,"scores": [95, 82, 77],"address": {"city": "New York","zip": "10001"}
}
优点:
- 易于阅读和调试
- 跨语言支持广泛
- 与 JavaScript 原生对象映射简单
缺点:
- 体积较大:冗余键名,传输效率低
- 解析速度较慢:文本解析比二进制慢
- 类型有限:没有二进制数据类型,需要编码(如 Base64)
- 无严格 Schema:容易出现字段缺失或类型不匹配问题
BSON
BSON一种基于 JSON 语法设计的二进制数据格式,最早由 MongoDB 推出,核心目标是解决 JSON 文本格式在数据传输和存储中的效率问题(如体积过大、解析速度慢)。与纯文本的 JSON 不同,BSON 通过二进制编码优化了数据存储结构,同时保留了 JSON 的 “键值对” 核心逻辑,兼顾了可读性(可解析为 JSON)和性能。
支持数据类型:
BSON 类型 | 对应 JSON 类型 | 说明 |
---|---|---|
Double | Number (浮点) | 64 位浮点数 |
String | String | UTF-8 编码 |
Object | Object | 嵌套文档 |
Array | Array | 有序列表 |
Binary data | - | 原生二进制数据 |
ObjectId | - | MongoDB 特有唯一 ID |
Boolean | Boolean | true/false |
Date | - | 64 位整数,表示 UTC 时间戳 |
Null | Null | 空值 |
Int32 | Number | 32 位整数 |
Int64 | Number | 64 位整数 |
Decimal128 | Number | 高精度浮点数 |
示例:
{"name": "Alice","age": 30,"isStudent": true
}
对应的 BSON 结构:
1D 00 00 00
02 6E 61 6D 65 00 06 00 00 00 41 6C 69 63 65 00
10 61 67 65 00 1E 00 00 00
08 69 73 53 74 75 64 65 6E 74 00 01
00
字段 | 类型 | 内容 |
---|---|---|
文档长度 | Int32 | 1D 00 00 00 (29 bytes) |
name 字段 | String (0x02) | 02(type) 6E 61 6D 65 00(key="name"+null结束) 06 00 00 00(len) 41 6C 69 63 65(value="Alice") 00(null结束符) |
age 字段 | Int32 (0x10) | 10 61 67 65 00 1E 00 00 00 (30 decimal = 0x1E) |
isStudent | Boolean (0x08) | 08 69 73 53 74 75 64 65 6E 74 00 01 (true = 0x01) |
文档结尾 | - | 00 |
优点:
- 支持更多类型(Date、Binary、Int64 等)
- 内部存储和传输高效
- 易于与 MongoDB 集成
缺点:
- 不可读:不适合直接调试
- 体积可能比 JSON 大:尤其是短字符串或小对象时,类型前缀会增加额外字节
- 依赖特定库解析(如 PyMongo、bson.js)
CBOR
CBOR(Concise Binary Object Representation,简洁二进制对象表示)是一种轻量级、高效的二进制数据序列化格式,由 IETF 标准化(RFC 8949),设计目标是成为 “二进制版的 JSON”—— 既保留 JSON 的灵活性和兼容性,又通过二进制编码解决 JSON 体积大、解析慢的问题。
数据类型:
JSON 类型 | CBOR 类型 | 说明 |
---|---|---|
Object | Map (5) | 完全对应,CBOR 更紧凑,可嵌套 |
Array | Array (4) | 对应,长度编码更紧凑 |
String | UTF-8 String (3) | 对应,长度可直接编码 |
Number | Unsigned/Negative Int (0/1) 或 Float (7) | CBOR 可表示大整数和各种浮点数 |
Boolean | Simple type (7) | true → F5, false → F4 |
Null | Simple type (7) | null → F6 |
二进制数据 | Byte String (2) | JSON 不支持,CBOR 原生支持 |
日期/时间 | Tag (6) + value | JSON 用字符串,CBOR 可直接用 Tag 0/1 表示 |
大整数 | Tag 2/3 + value | JSON 没有原生类型,需要字符串 |
示例:
{"name": "Alice","age": 30,"isStudent": true,"scores": [95, 82, 77],"profile_pic": "<binary_data>"
}
CBOR 编码规则
对象 → Map → Major type 5
文档 5 个键值对 → 0xA5
字符串 → Major type 3
长度直接编码在初始字节
整数 → Major type 0
小于 24 → 初始字节直接编码
大于 23 → 0x18 + 值
布尔 → Major type 7
true → 0xF5
数组 → Major type 4
长度 3 → 0x83
二进制 → Major type 2
假设长度 4 → 0x44 + 4 字节数据
A5 # Map, 5 键值对63 6E 61 6D 65 # key: "name", 长度 3
65 41 6C 69 63 65 # value: "Alice", 长度 563 61 67 65 # key: "age", 长度 3
18 1E # value: 3069 69 73 53 74 75 64 65 6E 74 # key: "isStudent", 长度 9
F5 # value: true66 73 63 6F 72 65 73 # key: "scores", 长度 6
83 18 5F 18 52 18 4D # array [95, 82, 77]
# 95 → 0x5F, 82 → 0x52, 77 → 0x4D, 小于 24 用 0x18 前缀6B 70 72 6F 66 69 6C 65 5F 70 69 63 # key: "profile_pic", 长度 11
44 FF D8 FF E0 # Byte string, 长度 4, 数据示例: FF D8 FF E0
优点:
- 二进制,紧凑高效
- 支持丰富类型(整数、浮点、二进制、标签)
- 可扩展
- 与 JSON 结构兼容
缺点:
- 不可读(需工具解析)
- 生态比 JSON、Protobuf 少一些
Protocol Buffer
https://protobuf.dev/?utm_source=chatgpt.com
Protocol Buffer(简称 Protobuf)是 Google 开发的一种高效的二进制数据序列化格式,旨在替代 XML、JSON 等文本格式,用于数据存储和网络传输。它的核心优势在于体积小、解析快、跨语言支持好,尤其适合高性能场景。
数据类型:
类型 | 描述 | 对应 JSON 类型 |
---|---|---|
double | 64 位浮点 | Number |
float | 32 位浮点 | Number |
int32 | 32 位有符号整数 | Number |
int64 | 64 位有符号整数 | Number |
uint32 | 32 位无符号整数 | Number |
uint64 | 64 位无符号整数 | Number |
sint32 | 32 位有符号整数,zig-zag 编码 | Number |
sint64 | 64 位有符号整数,zig-zag 编码 | Number |
fixed32 | 固定长度 32 位整数 | Number |
fixed64 | 固定长度 64 位整数 | Number |
sfixed32 | 固定长度有符号 32 位整数 | Number |
sfixed64 | 固定长度有符号 64 位整数 | Number |
bool | 布尔值 | Boolean |
string | UTF-8 字符串 | String |
bytes | 原始二进制 | Byte string |
enum | 枚举 | String/Number |
message | 嵌套消息 | Object |
repeated | 重复字段 | Array |
map<K,V> | Map / 字典 | Object |
示例:
{"name": "Alice","age": 30,"isStudent": true,"scores": [95, 82, 77]
}
syntax = "proto3";message Student {string name = 1;int32 age = 2;bool isStudent = 3;repeated int32 scores = 4;
}
Field | Tag | Wire Type | 内容 | 说明 |
---|---|---|---|---|
name | 1 | 2 | “Alice” | length-delimited |
age | 2 | 0 | 30 | varint |
isStudent | 3 | 0 | true | varint (1) |
scores | 4 | 0 | 95, 82, 77 | repeated varint |
0a 05 41 6c 69 63 65 # field 1: name, length 5, value "Alice"
10 1e # field 2: age = 30
18 01 # field 3: isStudent = true
20 5f 20 52 20 4d # field 4: scores = 95, 82, 77
优点:
优点 | 说明 |
---|---|
高效紧凑 | Protobuf 使用二进制编码,不存字段名,整数采用 varint 压缩,整体比 JSON/CBOR/BSON 更小。 |
跨语言 | 官方支持 C++, Java, Python, Go, C#, JavaScript 等多种语言,可轻松实现跨语言通信。 |
类型安全 | 每个字段都有明确类型(int32、string、bool 等),避免类型歧义。 |
向后/向前兼容 | 可以新增字段而不破坏旧版本数据(未定义字段会被忽略),非常适合分布式系统升级。 |
支持嵌套消息和复杂结构 | 支持数组(repeated)、嵌套消息(message)、Map 等复杂数据结构。 |
可生成代码 | 可以根据 .proto 文件自动生成各种语言的类,减少手写序列化/反序列化工作量。 |
广泛应用 | gRPC、微服务、IoT 数据通信等大规模系统广泛采用。 |
缺点:
缺点 | 说明 |
---|---|
不可读 | 二进制编码,调试或人工查看不方便,需要工具解析。 |
需要编译生成代码 | 必须通过 protoc 或相关插件生成对应语言的类文件,增加编译步骤。 |
不支持动态字段名 | 与 JSON 不同,Protobuf 不存储字段名,只能通过 tag 访问字段,灵活性低。 |
学习成本 | 需要学习 .proto 文件语法、tag 规则、wire type 等概念。 |
不直接支持 JSON 的某些特性 | 如直接存储 null 或任意嵌套 Map,虽然可以通过 message/optional/fallback 实现,但不如 JSON 灵活。 |
扩展类型有限 | 不像 CBOR 可以自由扩展自定义标签,需要提前定义 message 或 enum。 |
MessagePack
MessagePack 是一种高效的二进制数据序列化格式,核心设计理念是 “像 JSON 一样简单,但比 JSON 更快、更小”。它旨在保留 JSON 的灵活性(无 Schema 约束、支持常见数据结构),同时通过二进制编码解决 JSON 文本格式 “体积大、解析慢” 的问题,广泛应用于跨语言通信、数据存储等场景。
数据类型:
类型 | 描述 | 对应 JSON 类型 |
---|---|---|
nil | 空值 | null |
bool | true / false | Boolean |
int | 有符号整数 | Number |
uint | 无符号整数 | Number |
float | 32/64 位浮点数 | Number |
str | UTF-8 字符串 | String |
bin | 二进制数据 | 无(JSON 不支持) |
array | 有序列表 | Array |
map | 键值对 | Object |
ext | 扩展类型 | 自定义类型(日期、UUID 等) |
示例
{"name": "Alice","age": 30,"isStudent": true,"scores": [95, 82, 77]
}
84 # map, 4 键值对a4 6e 61 6d 65 # key: "name", fixstr 4
a5 41 6c 69 63 65 # value: "Alice", fixstr 5a3 61 67 65 # key: "age", fixstr 3
1e # value: 30 (positive fixint)a9 69 73 53 74 75 64 65 6e 74 # key: "isStudent", fixstr 9
c3 # value: truea6 73 63 6f 72 65 73 # key: "scores", fixstr 6
93 5f 52 4d # array of 3 elements: 95, 82, 77
优点:
优点 | 说明 |
---|---|
紧凑高效 | 二进制编码,比 JSON 小 30–70% 空间 |
与 JSON 兼容 | 数据模型一致,易于理解 |
支持二进制 | 原生支持 bin 类型 |
支持数组、Map、嵌套 | 与 JSON 完全兼容 |
可扩展 | 支持 ext 类型,如 UUID、日期、自定义对象 |
跨语言 | Python、Go、C++、JavaScript 等都有库 |
缺点:
缺点 | 说明 |
---|---|
不可读 | 二进制格式,调试需工具 |
类型不严格 | 没有像 Protobuf 那样的强类型检查 |
向后兼容有限 | 新增字段可能被旧版本忽略,但没有严格的 tag 机制 |
浮点数/大整数精度 | 需要注意超大整数可能超出 JS Number 精度 |
选择
各个方案优缺点对比
特性 | JSON | BSON | CBOR | Protocol Buffers | MessagePack |
---|---|---|---|---|---|
数据格式 | 文本 | 二进制 | 二进制 | 二进制 | 二进制 |
可读性 | 高 | 低 | 低 | 低 | 低 |
紧凑性 | 差 | 中 | 高 | 高 | 高 |
类型安全 | 弱 | 弱 | 部分 | 强 | 弱 |
支持二进制 | ❌ | ✅ | ✅ | ✅ | ✅ |
扩展类型 | ❌ | MongoDB 特定类型 | ✅(Tag) | ✅(自定义 message/enum) | ✅(ext) |
嵌套/数组 | ✅ | ✅ | ✅ | ✅ | ✅ |
向后兼容 | ❌ | 可能 | 可能 | ✅ | 有限 |
跨语言 | ✅ | ✅ | ✅ | ✅ | ✅ |
典型应用 | 配置文件、Web API | MongoDB 存储 | IoT、低带宽网络 | gRPC、微服务通信、IoT | 高性能网络通信、缓存、IoT |
选择原则如下:
场景 | 推荐方案 | 说明 |
---|---|---|
可读性优先 / 调试 / 配置文件 | JSON | 易于编辑、调试,跨语言方便 |
数据库存储(MongoDB 原生) | BSON | MongoDB 内部使用,支持二进制存储 |
低带宽 IoT / 二进制传输 / 嵌套数据 | CBOR / MessagePack | 紧凑,高效,支持嵌套和扩展类型 |
高性能微服务 / 跨语言 RPC | Protocol Buffers | 强类型、紧凑、向后兼容,适合 gRPC 或 IoT 高性能通信 |
轻量级网络通信 / 缓存 | MessagePack | 与 JSON 数据模型一致,序列化快,二进制紧凑 |
需要扩展类型(日期/UUID 等) | CBOR / MessagePack | 原生支持扩展类型,不用额外字段约定 |
选择数据序列化方案应根据场景权衡:JSON 可读性高、易调试,适合配置和 Web API;BSON 适合 MongoDB 存储,支持二进制;CBOR 与 MessagePack 紧凑高效,适合低带宽或 IoT 通信,且支持扩展类型;Protocol Buffers 则强类型、向后兼容、跨语言高性能,适合微服务或大规模网络通信。总之,可读性优先选 JSON,二进制紧凑优先选 CBOR/MessagePack/Protobuf,严格类型与跨语言要求选 Protobuf。