深入浅出(六)序列化库 FlatBuffers、Protobuf、MessagePack

深入浅出(二)序列化库 FlatBuffers、Protobuf、MessagePack

  • [1. FlatBuffers、Protobuf 、MessagePack对比](#1. FlatBuffers、Protobuf 、MessagePack对比)
  • [2. FlatBuffers](#2. FlatBuffers)
    • [2.1 FlatBuffers 简介](#2.1 FlatBuffers 简介)
    • [2.2 FlatBuffers 编译](#2.2 FlatBuffers 编译)
    • [2.3 C++ 示例](#2.3 C++ 示例)
  • [3. Protobuf](#3. Protobuf)
    • [3.1 Protobuf 简介](#3.1 Protobuf 简介)
    • [3.2 Protobuf 编译](#3.2 Protobuf 编译)
    • [3.3 C++ 示例](#3.3 C++ 示例)
    • [附录一 除了 gRPC,protobuf 还能与谁一起使用?(常见组合)](#附录一 除了 gRPC,protobuf 还能与谁一起使用?(常见组合))
    • [附录二 Protobuf 适用场景](#附录二 Protobuf 适用场景)
    • [附录三 Protobuf 的缺点](#附录三 Protobuf 的缺点)
  • [4. MessagePack](#4. MessagePack)
    • [4.1 MessagePack 简介](#4.1 MessagePack 简介)
    • [4.2 MessagePack C++ 示例](#4.2 MessagePack C++ 示例)

1. FlatBuffers、Protobuf 、MessagePack对比

FlatBuffersProtobufMessagePack都是高效的序列化(serialization)库,用来把结构化数据转成二进制,以便存储或网络传输。但它们在设计理念、性能特征、内存模型和使用场景上有明显区别。详细对比如下:

protobuf 高效、安全、可扩展的网络数据格式(需解析)
flatbuffers 极致性能、零拷贝访问的结构化数据格式(无需解析)
MessagePack 像是"编译后的 JSON 协议(需解析)

  1. 应用场景

    应用场景 推荐
    网络通信 / gRPC / 后端服务 protobuf
    长期存储、易扩展协议 protobuf
    游戏引擎(Unity、UE)、嵌入式系统 flatbuffers
    大数据量、频繁读取、实时性能要求高 flatbuffers
    跨语言轻量通信、缓存、日志、JSON替代 MessagePack
  2. 核心设计理念差异

    特性 Protocol Buffers (protobuf) FlatBuffers MessagePack
    设计目标 高效、紧凑的序列化格式 零拷贝(Zero-copy)访问、极致性能 像 JSON 一样易用,但更高效紧凑"的二进制数据交换格式
    访问方式 反序列化后使用对象(需要解析) 可直接访问序列化内存(无需解析) 需反序列化为对象才能使用(非零拷贝)
    作者/来源 Google(广泛用于RPC、gRPC) Google(用于游戏、嵌入式、实时系统) 2008 年创建
  3. 内存与性能对比

    对比项 protobuf flatbuffers
    序列化速度 较快 更快(通常快1.5--3倍)
    反序列化速度 需要解析生成对象 → 较慢 无需解析 → 直接读取内存,非常快
    内存占用 高(因为要创建完整对象) 低(直接访问内存)
    零拷贝访问 ❌ 不支持 ✅ 支持(可直接读取缓冲区)
    随机访问 ❌ 必须先反序列化 ✅ 可以直接访问任意字段
    数据可修改性 ✅ 可修改反序列化后的对象 ⚠️ 设计上主要只读(需要重新构建才能修改)
  4. 性能直观对比

    项目 Protobuf FlatBuffers MessagePack
    序列化速度 非常快 中等偏快
    反序列化速度 慢(需要解析) 极快(直接访问,零拷贝) 中等(需解析,但比 JSON 快)
    内存使用 中等
    支持语言 C++, Java, Python, Go, C#, Rust, JS... C++, C#, Go, Java, Rust, Python... C++, Python, Go, Java, Rust, JS...
    向后兼容性 很好(通过 tag 编号) 一般(可选字段但限制较多) 较好(字段可省略,结构灵活)
    数据可读性 不可读(二进制) 不可读(二进制) 类似二进制 JSON(可用工具查看)
    是否需 schema 定义 ✅ 需要 .proto ✅ 需要 .fbs ❌ 不需要(可选)
    适用场景 RPC 通信、配置文件、网络协议 游戏、实时系统、嵌入式、高性能应用 跨语言通信、缓存、日志、JSON 替代

2. FlatBuffers

2.1 FlatBuffers 简介

FlatBuffers 是 Google 开发的零拷贝序列化库,专为实时系统、游戏引擎、嵌入式设计。

它的核心理念是:"直接访问序列化数据,不必反序列化。"

说明 地址
官网 https://google.github.io/flatbuffers/
GitHub https://github.com/google/flatbuffers

FlatBuffers 特性

特性 说明
数据访问 零拷贝(直接访问)
是否需 schema .fbs 文件
内存使用
向后兼容 一般(可选字段有限)
典型应用 游戏、嵌入式、AR/VR、实时仿真

2.2 FlatBuffers 编译

  1. Git 克隆代码

  2. CMake GUI 打开代码。只需要修改INSTALL目录变量 即可

  3. VS 2022 打开,选择Release X64 模式,先ALL_BUILD ,再INSTALL ,右键选择生成即可

  4. 生成目录如下

2.3 C++ 示例

  1. 创建一个文件:example.fbs

    fbs 复制代码
    namespace MyGame;
    
    table Monster {
      id:int;
      name:string;
      hp:int;
      mana:int = 150;
    }
    
    root_type Monster;
  2. 使用 flatc.exe 生成 C++ 文件

    执行:

    bash 复制代码
    flatc -c example.fbs

    会生成:

    bash 复制代码
    example_generated.h

    把它们加入你的 VS/CMake 项目即可。示例图如下:

  3. C++ 使用示例(序列化 & 反序列化)

    可直接编译运行:

    cpp 复制代码
    #include "example_generated.h"
    #include <iostream>
    #include <fstream>
    
    int main() {
        flatbuffers::FlatBufferBuilder builder(1024);
    
        // 创建字符串
        auto name = builder.CreateString("Orc Warrior");
    
        // 构造 Monster
        MyGame::MonsterBuilder monsterBuilder(builder);
        monsterBuilder.add_id(1);
        monsterBuilder.add_name(name);
        monsterBuilder.add_hp(80);
        monsterBuilder.add_mana(200);
        auto monster = monsterBuilder.Finish();
    
        builder.Finish(monster);  // 完成构建,monster 作为根
    
        // === 序列化到文件 ===
        uint8_t* buf = builder.GetBufferPointer();
        size_t size = builder.GetSize();
    
        std::ofstream ofs("monster.bin", std::ios::binary);
        ofs.write(reinterpret_cast<char*>(buf), size);
        ofs.close();
    
        std::cout << "Monster serialized to monster.bin\n";
    
        // === 从文件读取并解析 ===
        std::ifstream ifs("monster.bin", std::ios::binary);
        std::vector<char> data((std::istreambuf_iterator<char>(ifs)),
                               std::istreambuf_iterator<char>());
    
        auto monsterObj = MyGame::GetMonster(data.data());
    
        std::cout << "ID: " << monsterObj->id() << "\n";
        std::cout << "Name: " << monsterObj->name()->c_str() << "\n";
        std::cout << "HP: " << monsterObj->hp() << "\n";
        std::cout << "Mana: " << monsterObj->mana() << "\n";
    
        return 0;
    }

    运行结果

    vbnet 复制代码
    Monster serialized to monster.bin
    ID: 1
    Name: Orc Warrior
    HP: 80
    Mana: 200

3. Protobuf

3.1 Protobuf 简介

Protocol Buffers(protobuf) 是 Google 开发的一种高效、跨语言的结构化数据序列化协议。它在网络通信、配置文件、RPC(尤其是 gRPC)中被广泛使用。

🚀 特点:体积小、速度快;语言无关;向后兼容性强;需要 .proto 文件定义数据结构。

主要用途:

  • 高吞吐二进制通讯
  • 客户端/服务端消息结构统一
  • 自定义帧头 + protobuf 消息体
说明 地址
官网 https://protobuf.dev/
GitHub https://github.com/protocolbuffers/protobuf

3.2 Protobuf 编译

  1. Git 克隆代码
  2. CMake GUI 打开代码。只需要修改INSTALL目录变量 即可
  3. VS 2022 打开,选择Release X64 模式,先ALL_BUILD ,再INSTALL ,右键选择生成即可
  4. 生成目录如下

3.3 C++ 示例

  1. 编写一个 test.proto

    nginx 复制代码
    syntax = "proto3";
    
    package demo;
    
    message Point {
        double x = 1;
        double y = 2;
        double z = 3;
    }
    
    message ScanResult {
        int32 id = 1;
        repeated Point points = 2;
    }
  2. 使用 protoc 生成 C++ 文件

    在当前目录执行:

    bash 复制代码
    protoc --cpp_out=. test.proto

    会生成:

    bash 复制代码
    test.pb.h
    test.pb.cc

    把它们加入你的 VS/CMake 项目即可。示例图如下:

  3. CMake 中使用 Protobuf

    你的 CMakeLists 可以这样写:

    cmake 复制代码
    find_package(Protobuf REQUIRED)
    
    add_executable(demo main.cpp test.pb.cc)
    target_include_directories(demo PRIVATE ${Protobuf_INCLUDE_DIRS})
    target_link_libraries(demo PRIVATE ${Protobuf_LIBRARIES})
  4. C++ 使用示例(序列化 & 反序列化)

    可直接编译运行:

    cpp 复制代码
    #include <iostream>
    #include "test.pb.h"
    
    int main() {
        // 构造消息
        demo::ScanResult msg;
        msg.set_id(123);
    
        auto* pt = msg.add_points();
        pt->set_x(1.1);
        pt->set_y(2.2);
        pt->set_z(3.3);
    
        // 序列化到字符串(二进制)
        std::string out;
        msg.SerializeToString(&out);
    
        std::cout << "Serialized size: " << out.size() << " bytes\n";
    
        // 反序列化
        demo::ScanResult msg2;
        if (!msg2.ParseFromString(out)) {
            std::cout << "Parse failed!\n";
            return -1;
        }
    
        std::cout << "ID: " << msg2.id() << "\n";
        std::cout << "First point: "
                  << msg2.points(0).x() << ", "
                  << msg2.points(0).y() << ", "
                  << msg2.points(0).z() << "\n";
    
        return 0;
    }

    输出:

    yaml 复制代码
    Serialized size: 26 bytes
    ID: 123
    First point: 1.1, 2.2, 3.3
  5. Protobuf 与文件读写示例

    写到文件:

    cpp 复制代码
    std::ofstream ofs("scan.data", std::ios::binary);
    msg.SerializeToOstream(&ofs);

    从文件读:

    cpp 复制代码
    demo::ScanResult r;
    std::ifstream ifs("scan.data", std::ios::binary);
    r.ParseFromIstream(&ifs);

    非常适合保存扫描路径、点云元数据、标定结果。

  6. 高级示例:Protobuf + TCP 消息收发(工业项目常用)

    一般定义一个包格式:

    css 复制代码
    [4 bytes 消息长度][Protobuf 二进制]

    发送端:

    cpp 复制代码
    bool SendMessage(int sock, const google::protobuf::Message& msg) {
        std::string data;
        msg.SerializeToString(&data);
    
        uint32_t len = htonl(data.size());
    
        // 发送长度
        send(sock, (char*)&len, 4, 0);
    
        // 发送内容
        send(sock, data.data(), data.size(), 0);
    
        return true;
    }

    接收端(解决粘包的标准做法)

    cpp 复制代码
    bool RecvMessage(int sock, google::protobuf::Message& msg) {
        uint32_t len_net;
        if (recv(sock, (char*)&len_net, 4, MSG_WAITALL) <= 0)
            return false;
    
        uint32_t len = ntohl(len_net);
    
        std::string data(len, '\0');
        if (recv(sock, data.data(), len, MSG_WAITALL) <= 0)
            return false;
    
        return msg.ParseFromString(data);
    }

    这两个函数你可以直接放到你的工业通讯库里,用来解决:

    • 粘包
    • 拆包
    • 统一消息格式

    几乎所有工控系统都用这种模式。

附录一 除了 gRPC,protobuf 还能与谁一起使用?(常见组合)

  1. Protobuf + TCP(最常见的自定义协议)

    工业相机、机器人、PLC、分布式系统自带的一种组合。

    用途:

    • 高吞吐二进制通讯
    • 客户端/服务端消息结构统一
    • 自定义帧头 + protobuf 消息体

    示例结构:

    css 复制代码
    [0x55AA][消息类型][protobuf长度][protobuf 数据...]
  2. Protobuf + UDP

    适合高频率、低延迟数据,如:

    • 传感器流数据(激光雷达、IMU)
    • 控制命令通讯
    • 多机器人状态同步
  3. Protobuf + ZeroMQ

    多用于分布式系统的消息队列。

    优势:

    • ZeroMQ 负责传输
    • Protobuf 负责结构设计

    用途:分布式处理框架、后台服务总线。

  4. Protobuf + MQTT / Kafka / Redis Stream

    给 IoT(物联网)和分布式系统用。

  5. Protobuf + WebSocket

    做前端实时数据推送(如 3D 可视化、状态监控)。

  6. Protobuf + 自己实现的 RPC 框架

    许多公司会自己写:

    • protobuf 负责序列化
    • TCP 负责传输
    • 自己实现 RPC(比 gRPC 更可控、带实时性优化)

    典型用于工业控制、机器人、嵌入式。

  7. Protobuf 用于文件序列化 (非网络)

    • 保存扫描工件的点云
    • 保存机器人扫描路径
    • 保存结构化配置文件
    • 保存机器学习模型数据

    Google 内部大量配置文件(非文本)用 protobuf 存。

附录二 Protobuf 适用场景

  1. 机器人、自动化、运动控制

    • 工件扫描路径
    • 多轴运动状态
    • 嵌入式控制器通讯
    • 实时传感器数据

    比 JSON 快太多。

  2. 工业领域(你正在做的 Visual Platform / Path Planner / DART 这些)

    • 工业机器人标定
    • 扫描仪数据
    • CNC/PLC 状态数据
    • 自定义控制协议

    非常适合高频数据传输。

  3. 游戏引擎 / 仿真(O3DE / Unity / UE)

    • 网络同步状态
    • 序列化场景数据
    • 客户端-服务器通讯
  4. 云服务 / 微服务

    大型项目的标准做法。

附录三 Protobuf 的缺点

  1. 人类不可读

    是二进制,调试困难,需要 protoc --decode。(你可能觉得比 JSON 难调试)

  2. 不适用于超大二进制数据(如图像/点云)

    通常需要外部存储 + Protobuf 存 metadata。比如:

    nginx 复制代码
    message CloudData {
        string file_path = 1;
    }
  3. 实时性 "极致要求"的情况不如 FlatBuffers

    • 1ms 以下硬实时
    • 不想拷贝内存(zero-copy)

    这时候 FlatBuffers 更适合。

  4. schema 管理成本高

    proto 文件要统一管理,更新要同步。

4. MessagePack

说明 地址
官网 https://protobuf.dev
GitHub https://github.com/protocolbuffers/protobuf

Protobuf 特性

特性 说明
数据格式 二进制,紧凑高效
支持语言 C++, Python, Go, Java, Rust, JS...
是否需 schema ✅ 需要 .proto
向后兼容 ✅ 通过 tag 编号保持兼容
零拷贝 ❌ 需要反序列化

4.1 MessagePack 简介

MessagePack 是一种类似 JSON 的二进制序列化格式,由日本工程师 Sadayuki Furuhashi 于 2008 年开发。目标是:"让数据像 JSON 一样简单,但更快、更小。"

官网 https://msgpack.org/
GitHub https://github.com/msgpack/msgpack

MessagePack 特性

特性 说明
格式类型 二进制 JSON
是否需 schema ❌ 不需要
语言支持 C++, Python, Go, Java, Rust, JS...
可读性 较好(可用工具解析)
向后兼容 好(字段可省略)

简单性能对比:JSON vs MessagePack

项目 JSON MessagePack
体积 小 40~70%
序列化速度
可读性
是否跨语言

4.2 MessagePack C++ 示例

cpp 复制代码
#include <msgpack.hpp>
#include <iostream>

struct Person {
    std::string name;
    int age;
    MSGPACK_DEFINE(name, age);
};

int main() {
    Person p{"Alice", 30};

    std::stringstream ss;
    msgpack::pack(ss, p); // 序列化

    std::string data = ss.str();
    msgpack::object_handle oh = msgpack::unpack(data.data(), data.size());
    Person p2 = oh.get().as<Person>(); // 反序列化

    std::cout << p2.name << " " << p2.age << std::endl;
}
相关推荐
埃伊蟹黄面2 小时前
模拟算法思想
c++·算法·leetcode
Unlyrical2 小时前
Valgrind快速使用
c++·valgrind
李余博睿(新疆)2 小时前
c++练习题-双分支
c++
xlp666hub2 小时前
C语言实战:手搓高并发异步日志库(基于 Ring Buffer + 生产者消费者模型)
开源
司徒轩宇2 小时前
C++ 内存分配详解
开发语言·c++
alibli3 小时前
一文学会设计模式之创建型模式及最佳实现
c++·设计模式
️停云️3 小时前
C++类型转换、IO流与特殊类的设计
c语言·开发语言·c++
周杰伦_Jay3 小时前
【LangGraph】图结构智能体框架核心特性
python·开源
进击的荆棘3 小时前
C++起始之路——类和对象(下)
开发语言·c++