主题:机器人/嵌入式通讯框架中,序列化格式与传输协议的选型决策。 姊妹篇:MCAP + Zenoh + Protobuf:应用层统一数据交换与记录方案。
一、二进制 JSON 体系(Schema-less)
这一类的设计目标:JSON 数据模型,二进制编码。拿来就能用,不用定义协议文档。
1.1 四者对比
| JSON | MessagePack | CBOR | BSON | |
|---|---|---|---|---|
| 标准 | ECMA-404 | msgpack.org 协议 | RFC 7049 / RFC 8949(更新版) | MongoDB 规范 |
| 自描述 | 是 | 是 | 是 | 是 |
| Schema | 不需要 | 不需要 | 不需要 | 不需要 |
| 体积 vs JSON | 基准 | 小 30-50% | 小 50-70% | 更大(带长度前缀) |
| 类型丰富度 | 基础 | 基础 + 时间戳扩展 | 丰富(bigint/decimal/二进制标签) | 多 Date/ObjectId/Regex 等 |
| 生态 | 通用 | 成熟(Redis/Fluentd) | IETF 标准,IoT 领域 | MongoDB 绑定 |
1.2 CBOR vs MessagePack
定位几乎一样,关键区别:
- CBOR 更规范:IETF RFC 标准,实现一致性高,分歧处理规则明确
- CBOR 类型更丰富:原生支持 bigint、decimal、二进制日期标签等
- MessagePack 生态更成熟:工具链丰富,JSON 互转基本无损
选型:全行业通用二进制 JSON → CBOR(标准)或 MessagePack(生态);MongoDB 场景 → BSON。
二、Protobuf:强 Schema 序列化
2.1 核心能力
- IDL(
.proto)定义数据结构,强类型,protoc 自动生成代码 - 跨语言:C++/Java/Python/Go/Rust/JS/C# 等 20+ 语言
- 二进制编码紧凑(比 JSON 小 3-10 倍)
- 向前/向后兼容(字段编号不变即可演进)
2.2 Protobuf 的缺点
没有银弹,优点(紧凑、强契约)同时也是缺点来源:
- 开发体验差 — 必须编译:写 proto → 跑 protoc → 生成代码。改一个字段就要重新编译,迭代慢。
- 不可自描述 — 数据离不开 Schema:编码后的二进制不带字段名,只有数字编号。没有
.proto文件就是天书。- 调试困难:WireShark 抓包只能看字节流
- 存档风险:数据湖里存 protobuf,丢了 proto 文件数据就废了
- 兼容性约束:追加字段必须加新字段号,删除字段需 reserved,改名要小心。小团队快速迭代会被拖慢。
- 二进制格式局限:浏览器原生不支持(需 protobuf-js 编译);配置文件/日志场景纯自虐;小数据时编译产物体积比数据还大。
- 类型系统不够丰富:无内置 decimal/日期类型(用 Well-Known Type 打补丁);map 的 value 不能是另一个 map;enum 名字全局唯一。
- 生态碎片化:proto2 vs proto3 语法割裂;第三方语言实现质量参差。
2.3 反序列化性能澄清
Protobuf 反序列化性能恰恰是强项,不是缺点。本质原因:不需要解析字段名,只认数字编号。
protobuf: 读 tag(字段编号+wire type) → 读长度 → memcpy
JSON: 逐字符扫描 → 匹配 key 字符串 → 转义 → 转类型
MessagePack: 读类型标记 → 读长度 → 赋值(介于两者之间)
性能排序(反序列化):
JSON < MessagePack ≈ CBOR < Protobuf < FlatBuffers/Cap'n Proto
慢 快(零拷贝)
“protobuf 反序列化慢"的说法通常来自三种误解:
- 跟 FlatBuffers/Cap’n Proto(零拷贝)比 —— 设计哲学差异,不是 protobuf 差
- 大消息 + 动态内存分配 —— 用 Arena 分配器解决
- 烂语言的烂实现 —— 官方 C++/Java/Go 实现都很能打
三、零拷贝序列化:FlatBuffers / Cap’n Proto
3.1 设计哲学
FlatBuffers ≈ “跨平台 + 可演化 + 变长友好的 C struct”。
C struct 直接 memcpy 到网络/磁盘确实是零拷贝的灵魂,但有三个死穴:
- 跨平台二进制不兼容:对齐、字节序、int 宽度都依赖编译器/平台
- Schema 无法演化:加个字段所有已存数据全废
- 变长数据处理差:字符串只能固定数组,浪费且易溢出
FlatBuffers 用 vtable(字段偏移表)解决:
[数据缓冲区]
root ──→ [vtable]
[字段1偏移] ← 老 schema 只知道 1 和 2
[字段2偏移]
...
[name = "..."]
[age = 30]
- 新增字段:老代码看不到新字段,不影响读老字段
- 删除字段:跳过对应 vtable 条目
- 平台无关:固定 little-endian,类型宽度固定
3.2 对比
| C struct | FlatBuffers | |
|---|---|---|
| 零拷贝 | 是(memcpy) | 是(offset 寻址) |
| 跨平台 | 否,依赖编译器 | 是,固定布局 |
| Schema 演化 | 否,加字段就炸 | 原生支持 |
| 变长字段 | 否,固定数组 | 是,offset 引用 |
| 访问开销 | 直接取成员 | 一次 offset 跳转(很小) |
Cap’n Proto 与 FlatBuffers 同路线(零拷贝 + 无解析),区别在 wire format 细节和生态。
四、C++ 编译时反射库(源码即 Schema)
游戏引擎和实时系统常见模式:写普通 C struct,编译时工具自动生成元信息,运行时反射库完成序列化。Schema 从独立描述文件(.proto/.fbs)移到源码里,省去手工维护两份定义。
4.1 三种实现路线
- 代码生成器:Python/Clang 脚本编译前扫描 struct,自动生成反射注册代码
- 例:Unreal Engine 的 UHT、Qt 的 MOC
- 宏驱动:struct 里写宏,展开时构建反射表,零外部工具依赖
- 例:Boost.PFR、rttr
- PDB/DWARF 解析:运行时解析调试信息获取布局(旁门左道,多用于逆向)
4.2 现成开源库
| 库 | C++ 标准 | 零标注 | 零拷贝 | 适用场景 |
|---|---|---|---|---|
| Boost.PFR | C++14 | 是(聚合类型) | 否 | 通用反射,配合其他库用。Structured Bindings + 模板元编程 |
| Glaze | C++23(推荐) | 是 | 否 | JSON/BEVE(自有二进制)/CBOR/MessagePack 等 10+ 格式,结构化反射场景性能极强 |
| struct_pack (yalantinglibs) | C++20 | 一行宏 | 否 | 高性能 RPC,protobuf 替代。编译期反射 + 内存布局优化 |
| cista | C++17 | 是 | 是 | 共享内存、mmap 存盘,struct 连同 vector/string 连续化 |
| bitsery | C++14 | 需写 serialize 函数 | 否 | 极致性能二进制序列化 |
| cereal | C++11 | 需写 serialize 函数 | 否 | 老牌稳定,支持 JSON/XML/Binary |
| zpp_bits | C++20 | 一行宏 | 否 | protobuf 替代,API 简单 |
4.3 性能参考(struct_pack 官方/社区)
- struct_pack 比 protobuf 快 2-20 倍(编译期反射 + 内存布局优化,非零拷贝)
- struct_pack 比 msgpack 快 10 倍以上
- 一行代码完成标准 C++ 结构体的序列化/反序列化,无需 IDL
4.4 为什么不用 Protobuf/FlatBuffers
C struct + 反射方式的好处:只有 C struct(编译时自动生成元信息),不用维护两份类型定义。游戏引擎已有几百个 struct,给每个写 proto 文件是灾难。这是工程差异,不是性能差异。
五、类 ROS 通讯框架分层选型
ROS 这种通讯框架是多层系统工程,每层可独立选型:
| 层 | ROS 1 | ROS 2 | 自建需决定 |
|---|---|---|---|
| IDL / Schema | .msg 自研 | .msg + .idl | 用谁描述数据结构 |
| 序列化 | roscpp 模板 | rosidl 生成 C 代码 | 序列化格式 |
| 传输层 | TCPROS/UDPROS | DDS(FastDDS/CycloneDDS) | 走什么协议 |
| 发现机制 | ROS Master 中心化 | DDS 去中心化 | 节点怎么找到彼此 |
| QoS | 几乎没有 | 丰富 | 要不要,多深 |
| 工具链 | rostopic/rosbag/rviz | ros2 topic/ros2 bag | 调试/录包/可视化 |
5.1 决策维度
- 部署形态:同板(共享内存/零拷贝优先)还是跨网络(需网络协议)
- 语言需求:只要 C++(C struct 反射最优)还是跨语言(必须独立 IDL)
- 实时性:>10ms 可接受(gRPC 够用)还是 <1ms 硬实时(FlatBuffers + iceoryx / DDS)
- 生态依赖:是否复用 ROS 2 包(直接用 ROS 2 改 RMW 层)
六、Zenoh:统一传输层
6.1 定位
Zenoh 把"传输 + 发现 + QoS + 记录"四层合并成一个协议,跨传输层自适应(SHM/UDP/TCP/QUIC/串口)。
| 层 | 传统方案 | Zenoh |
|---|---|---|
| 传输 | gRPC + 自制 UDP,或 iceoryx + eCAL | 一把梭,自适应 SHM/UDP/TCP |
| 发现 | 自建注册中心,或 eCAL discovery | 内建去中心化 |
| QoS | 基本没有,或 eCAL | 内建可靠性/持久性 |
| 记录 | 无 | Storage 插件 → MCAP |
6.2 vs DDS / gRPC / MQTT
| 维度 | Zenoh | DDS (ROS 2 默认) | gRPC | MQTT |
|---|---|---|---|---|
| 局域网延迟 | ~10μs | ~30-100μs | ~100μs-ms | ms 级 |
| 线头开销 | 5 字节 | 50+ 字节 | 较大 | 2 字节 |
| 嵌入式适配 | 极好(最小 300B / 30KB Flash) | 差(10-50MB 内存) | 中 | 中 |
| 去中心化 | 是 | 是 | 否(需 proxy) | 否(需 Broker) |
| 跨域组网 | 原生支持 | 需桥接 | 需网关 | 需桥接 |
| Storage/查询 | 内置 Storage 插件 | 无 | 无 | 无 |
6.3 三个杀手特性
自适应传输:同一套 API,板内走 SHM(零拷贝,纳秒级),跨板走 UDP(10μs),跨网络走 TCP/QUIC。应用代码不用改。
Storage 插件 — 传输与持久化天然打通:声明一个 Storage 后,所有匹配 key 的 pub 数据自动落盘。实时传输和历史回放共用一个接口,DDS/gRPC 做不到。
Query 语义 — 比 pub/sub 多一种模式:查询者问一句,所有匹配节点各自回答(多播聚合),不是一对一 RPC。适合系统健康检查、配置下发。
七、最终推荐:Proto + Zenoh + MCAP 三件套
7.1 架构
IDL: Protobuf (.proto)
序列化: protobuf / nanopb (MCU 端)
传输: Zenoh (pub/sub + query),自适应 SHM/UDP/TCP
发现: Zenoh 内建去中心化
记录: Zenoh Storage Plugin → MCAP 自动落盘
回放/分析: MCAP 文件 → Python SDK / Foxglove Studio
MCU 侧: Zenoh-Pico + Nanopb (~120KB Flash)
7.2 三个时代的选型演进
| 2010 年代 | 2020 年代 | 现在 | |
|---|---|---|---|
| Schema | ROS .msg | ROS 2 .idl / protobuf | protobuf(跨生态) |
| 传输 | TCPROS(自研) | DDS (Cyclone/FastDDS) | Zenoh(更轻更快) |
| 记录 | rosbag | rosbag2 | MCAP(标准化) |
| 可视化 | rviz | rviz2 | Foxglove Studio(Web 原生) |
| 嵌入式 | rosserial(重) | micro-ROS | Zenoh-Pico + Nanopb(轻量) |
7.3 结论
如果 2026 年从头做一个类 ROS 通讯框架,Proto + Zenoh + MCAP 是当前技术选型的天花板:比 ROS 2 轻,比自研稳,比 MQTT 强。详细可行性评估见姊妹篇《MCAP + Zenoh + Protobuf 整合方案》,核心缺失的胶水代码是 Zenoh MCAP Storage Plugin(约 1-2 人周工作量)。
附:选型速查表
| 场景 | 推荐方案 |
|---|---|
| 替代 JSON 压缩 API | MessagePack / CBOR |
| 缓存序列化 | MessagePack |
| 微服务 RPC、严格接口治理 | Protobuf + gRPC |
| 游戏引擎/实时系统,纯 C++ | C struct 反射(struct_pack / Glaze) |
| 极致低延迟、只读部分字段 | FlatBuffers / Cap’n Proto |
| 共享内存进程间通信 | cista + iceoryx |
| 机器人通讯框架(首选) | Proto + Zenoh + MCAP |
| 复用 ROS 2 生态 | ROS 2 + 改 RMW 层 |
参考资料
- MCAP + Zenoh + Protobuf 整合方案(姊妹篇)
- Eclipse Zenoh: https://zenoh.io
- Glaze (C++): https://github.com/stephenberry/glaze
- yalantinglibs struct_pack: https://github.com/alibaba/yalantinglibs
- Boost.PFR: https://www.boost.org/doc/libs/release/libs/pfr
- FlatBuffers: https://google.github.io/flatbuffers
- CBOR RFC 8949: https://datatracker.ietf.org/doc/html/rfc8949