机器人/嵌入式通讯选型:序列化格式与传输协议

2026/06/01

主题:机器人/嵌入式通讯框架中,序列化格式与传输协议的选型决策。 姊妹篇:MCAP + Zenoh + Protobuf:应用层统一数据交换与记录方案。


一、二进制 JSON 体系(Schema-less)

这一类的设计目标:JSON 数据模型,二进制编码。拿来就能用,不用定义协议文档。

1.1 四者对比

JSONMessagePackCBORBSON
标准ECMA-404msgpack.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

定位几乎一样,关键区别:

选型:全行业通用二进制 JSON → CBOR(标准)或 MessagePack(生态);MongoDB 场景 → BSON。


二、Protobuf:强 Schema 序列化

2.1 核心能力

2.2 Protobuf 的缺点

没有银弹,优点(紧凑、强契约)同时也是缺点来源:

  1. 开发体验差 — 必须编译:写 proto → 跑 protoc → 生成代码。改一个字段就要重新编译,迭代慢。
  2. 不可自描述 — 数据离不开 Schema:编码后的二进制不带字段名,只有数字编号。没有 .proto 文件就是天书。
    • 调试困难:WireShark 抓包只能看字节流
    • 存档风险:数据湖里存 protobuf,丢了 proto 文件数据就废了
  3. 兼容性约束:追加字段必须加新字段号,删除字段需 reserved,改名要小心。小团队快速迭代会被拖慢。
  4. 二进制格式局限:浏览器原生不支持(需 protobuf-js 编译);配置文件/日志场景纯自虐;小数据时编译产物体积比数据还大。
  5. 类型系统不够丰富:无内置 decimal/日期类型(用 Well-Known Type 打补丁);map 的 value 不能是另一个 map;enum 名字全局唯一。
  6. 生态碎片化:proto2 vs proto3 语法割裂;第三方语言实现质量参差。

2.3 反序列化性能澄清

Protobuf 反序列化性能恰恰是强项,不是缺点。本质原因:不需要解析字段名,只认数字编号。

protobuf:     读 tag(字段编号+wire type) → 读长度 → memcpy
JSON:         逐字符扫描 → 匹配 key 字符串 → 转义 → 转类型
MessagePack:  读类型标记 → 读长度 → 赋值(介于两者之间)

性能排序(反序列化):

JSON < MessagePack ≈ CBOR < Protobuf < FlatBuffers/Cap'n Proto
 慢                                                       快(零拷贝)

“protobuf 反序列化慢"的说法通常来自三种误解:

  1. 跟 FlatBuffers/Cap’n Proto(零拷贝)比 —— 设计哲学差异,不是 protobuf 差
  2. 大消息 + 动态内存分配 —— 用 Arena 分配器解决
  3. 烂语言的烂实现 —— 官方 C++/Java/Go 实现都很能打

三、零拷贝序列化:FlatBuffers / Cap’n Proto

3.1 设计哲学

FlatBuffers ≈ “跨平台 + 可演化 + 变长友好的 C struct”。

C struct 直接 memcpy 到网络/磁盘确实是零拷贝的灵魂,但有三个死穴:

FlatBuffers 用 vtable(字段偏移表)解决:

[数据缓冲区]
  root ──→ [vtable]
            [字段1偏移]   ← 老 schema 只知道 1 和 2
            [字段2偏移]
            ...
           [name = "..."]
           [age = 30]

3.2 对比

C structFlatBuffers
零拷贝是(memcpy)是(offset 寻址)
跨平台否,依赖编译器是,固定布局
Schema 演化否,加字段就炸原生支持
变长字段否,固定数组是,offset 引用
访问开销直接取成员一次 offset 跳转(很小)

Cap’n Proto 与 FlatBuffers 同路线(零拷贝 + 无解析),区别在 wire format 细节和生态。


四、C++ 编译时反射库(源码即 Schema)

游戏引擎和实时系统常见模式:写普通 C struct,编译时工具自动生成元信息,运行时反射库完成序列化。Schema 从独立描述文件(.proto/.fbs)移到源码里,省去手工维护两份定义。

4.1 三种实现路线

  1. 代码生成器:Python/Clang 脚本编译前扫描 struct,自动生成反射注册代码
    • 例:Unreal Engine 的 UHT、Qt 的 MOC
  2. 宏驱动:struct 里写宏,展开时构建反射表,零外部工具依赖
    • 例:Boost.PFR、rttr
  3. PDB/DWARF 解析:运行时解析调试信息获取布局(旁门左道,多用于逆向)

4.2 现成开源库

库C++ 标准零标注零拷贝适用场景
Boost.PFRC++14是(聚合类型)否通用反射,配合其他库用。Structured Bindings + 模板元编程
GlazeC++23(推荐)是否JSON/BEVE(自有二进制)/CBOR/MessagePack 等 10+ 格式,结构化反射场景性能极强
struct_pack (yalantinglibs)C++20一行宏否高性能 RPC,protobuf 替代。编译期反射 + 内存布局优化
cistaC++17是是共享内存、mmap 存盘,struct 连同 vector/string 连续化
bitseryC++14需写 serialize 函数否极致性能二进制序列化
cerealC++11需写 serialize 函数否老牌稳定,支持 JSON/XML/Binary
zpp_bitsC++20一行宏否protobuf 替代,API 简单

4.3 性能参考(struct_pack 官方/社区)

4.4 为什么不用 Protobuf/FlatBuffers

C struct + 反射方式的好处:只有 C struct(编译时自动生成元信息),不用维护两份类型定义。游戏引擎已有几百个 struct,给每个写 proto 文件是灾难。这是工程差异,不是性能差异。


五、类 ROS 通讯框架分层选型

ROS 这种通讯框架是多层系统工程,每层可独立选型:

层ROS 1ROS 2自建需决定
IDL / Schema.msg 自研.msg + .idl用谁描述数据结构
序列化roscpp 模板rosidl 生成 C 代码序列化格式
传输层TCPROS/UDPROSDDS(FastDDS/CycloneDDS)走什么协议
发现机制ROS Master 中心化DDS 去中心化节点怎么找到彼此
QoS几乎没有丰富要不要,多深
工具链rostopic/rosbag/rvizros2 topic/ros2 bag调试/录包/可视化

5.1 决策维度

  1. 部署形态:同板(共享内存/零拷贝优先)还是跨网络(需网络协议)
  2. 语言需求:只要 C++(C struct 反射最优)还是跨语言(必须独立 IDL)
  3. 实时性:>10ms 可接受(gRPC 够用)还是 <1ms 硬实时(FlatBuffers + iceoryx / DDS)
  4. 生态依赖:是否复用 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

维度ZenohDDS (ROS 2 默认)gRPCMQTT
局域网延迟~10μs~30-100μs~100μs-msms 级
线头开销5 字节50+ 字节较大2 字节
嵌入式适配极好(最小 300B / 30KB Flash)差(10-50MB 内存)中中
去中心化是是否(需 proxy)否(需 Broker)
跨域组网原生支持需桥接需网关需桥接
Storage/查询内置 Storage 插件无无无

6.3 三个杀手特性

  1. 自适应传输:同一套 API,板内走 SHM(零拷贝,纳秒级),跨板走 UDP(10μs),跨网络走 TCP/QUIC。应用代码不用改。

  2. Storage 插件 — 传输与持久化天然打通:声明一个 Storage 后,所有匹配 key 的 pub 数据自动落盘。实时传输和历史回放共用一个接口,DDS/gRPC 做不到。

  3. 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 年代现在
SchemaROS .msgROS 2 .idl / protobufprotobuf(跨生态)
传输TCPROS(自研)DDS (Cyclone/FastDDS)Zenoh(更轻更快)
记录rosbagrosbag2MCAP(标准化)
可视化rvizrviz2Foxglove Studio(Web 原生)
嵌入式rosserial(重)micro-ROSZenoh-Pico + Nanopb(轻量)

7.3 结论

如果 2026 年从头做一个类 ROS 通讯框架,Proto + Zenoh + MCAP 是当前技术选型的天花板:比 ROS 2 轻,比自研稳,比 MQTT 强。详细可行性评估见姊妹篇《MCAP + Zenoh + Protobuf 整合方案》,核心缺失的胶水代码是 Zenoh MCAP Storage Plugin(约 1-2 人周工作量)。


附:选型速查表

场景推荐方案
替代 JSON 压缩 APIMessagePack / 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 层

参考资料