C++ 序列化 Protocol Buffers:高效数据交换

目录

1.简介

2.安装

2.1.快速安装

2.2.源码编译安装

3.C++中使用流程

[3.1.定义 Protobuf 数据结构(.proto 文件)](#3.1.定义 Protobuf 数据结构(.proto 文件))

[3.2.编译生成 C++ 代码](#3.2.编译生成 C++ 代码)

[3.3.编写 C++ 业务代码](#3.3.编写 C++ 业务代码)

3.4.编译和链接

4.核心用法详解

5.注意事项

[6.Protobuf vs 其他序列化方案](#6.Protobuf vs 其他序列化方案)

7.总结


1.简介

Protocol Buffers(简称 Protobuf) ------ Google 开发的一种语言无关、平台无关、可扩展的结构化数据序列化协议。相比 JSON/XML,它体积更小、速度更快、兼容性更强,是微服务、网络通信、数据存储的首选序列化方案。

特点有:

  • 高效:二进制序列化,体积比 JSON 小 3-10 倍,速度快 20-100 倍;
  • 强类型:编译期检查数据类型,避免运行时错误;
  • 兼容:向后 / 向前兼容,新增字段不影响旧代码;
  • 跨语言 :一套 .proto 定义,生成 C++/Java/Python/Go 等代码;非常适合多语言微服务架构。

2.安装

2.1.快速安装

系统 安装命令
Ubuntu/Debian sudo apt install protobuf-compiler libprotobuf-dev
macOS brew install protobuf
Windows vcpkg install protobuf protobuf:x64-windows

验证安装:

cpp 复制代码
protoc --version  # 查看编译器版本,成功输出则安装完成

2.2.源码编译安装

cpp 复制代码
git clone https://github.com/protocolbuffers/protobuf.git
cd protobuf
git checkout v3.21.12  # 选择稳定版本
mkdir build && cd build
cmake ..
make -j$(nproc)
sudo make install

3.C++中使用流程

3.1.定义 Protobuf 数据结构(.proto 文件)

创建 person.proto,这是 Protobuf 的接口定义文件 ,所有数据结构都在这里声明(推荐使用 proto3 语法,更简洁):

cpp 复制代码
// 指定语法版本(必须写在第一行)
syntax = "proto3";

// 生成的 C++ 代码命名空间
package tutorial;

// 定义消息体(对应 C++ 类)
message Person {
  // 字段规则:字段类型 字段名 字段编号(必须唯一)
  string name = 1;    // 姓名
  int32 age = 2;      // 年龄
  string email = 3;   // 邮箱

  // 嵌套枚举类型
  enum PhoneType {
    MOBILE = 0;
    HOME = 1;
    WORK = 2;
  }

  // 嵌套消息类型
  message PhoneNumber {
    string number = 1;
    PhoneType type = 2;
  }

  // 重复字段(对应 C++ 容器,存储多个手机号)
  repeated PhoneNumber phones = 4;
}

关键语法说明:

  • 字段编号

    :每个字段后的 = 1= 2 是唯一的字段标识符,一旦定义就不能更改,这是向后兼容性的关键。

  • 字段规则

    • singular

      (默认):0 或 1 个值

    • repeated

      :0 或多个值(类似 C++ 的 vector

    • optional

      (proto2 或 proto3 2023+):可选字段

    • required

      (仅 proto2):必须字段

  • 数据类型

    int32int64uint32uint64sint32sint64fixed32fixed64sfixed32sfixed64floatdoubleboolstringbytes、枚举、其他消息类型。

3.2.编译生成 C++ 代码

使用 protoc 编译器将 .proto 文件编译为 C++ 头文件和源文件:

cpp 复制代码
# --cpp_out=. :生成 C++ 代码到当前目录
protoc --cpp_out=. person.proto

执行后会生成两个文件:

  • person.pb.h:头文件(声明类、方法)
  • person.pb.cc:源文件(实现序列化 / 反序列化)

3.3.编写 C++ 业务代码

创建 main.cpp,实现对象赋值、序列化、反序列化核心逻辑:

cpp 复制代码
#include <iostream>
#include <fstream>
#include <string>
// 引入Protobuf生成的头文件
#include "person.pb.h"

// 序列化:Person对象 → 二进制字符串
void Serialize(const tutorial::Person& person, std::string& data) {
    person.SerializeToString(&data);
}

// 反序列化:二进制字符串 → Person对象
void Deserialize(tutorial::Person& person, const std::string& data) {
    person.ParseFromString(data);
}

int main() {
    // 1. 初始化Protobuf(必须调用,全局仅一次)
    GOOGLE_PROTOBUF_VERIFY_VERSION;

    // ========== 2. 构造Person对象 ==========
    tutorial::Person person;
    person.set_name("张三");
    person.set_age(25);
    person.set_email("zhangsan@test.com");

    // 添加重复字段(手机号)
    auto* phone1 = person.add_phones();
    phone1->set_number("13800138000");
    phone1->set_type(tutorial::Person::MOBILE);

    auto* phone2 = person.add_phones();
    phone2->set_number("010-12345678");
    phone2->set_type(tutorial::Person::HOME);

    // ========== 3. 序列化 ==========
    std::string serialized_data;
    Serialize(person, serialized_data);
    std::cout << "序列化后字节数:" << serialized_data.size() << std::endl;

    // ========== 4. 反序列化 ==========
    tutorial::Person deserialized_person;
    Deserialize(deserialized_person, serialized_data);

    // ========== 5. 读取反序列化后的数据 ==========
    std::cout << "\n姓名:" << deserialized_person.name() << std::endl;
    std::cout << "年龄:" << deserialized_person.age() << std::endl;
    std::cout << "邮箱:" << deserialized_person.email() << std::endl;

    // 遍历重复字段
    for (int i = 0; i < deserialized_person.phones_size(); ++i) {
        const auto& phone = deserialized_person.phones(i);
        std::cout << "手机号:" << phone.number() << " 类型:" << phone.type() << std::endl;
    }

    // 清理Protobuf内存(可选)
    google::protobuf::ShutdownProtobufLibrary();
    return 0;
}

3.4.编译和链接

CMakeLists.txt 示例:

cpp 复制代码
cmake_minimum_required(VERSION 3.10)
project(protobuf_example)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# 查找 Protobuf 包
find_package(Protobuf REQUIRED)

# 生成 protobuf 源文件
protobuf_generate_cpp(PROTO_SRCS PROTO_HDRS Person.proto)

# 添加可执行文件
add_executable(protobuf_example main.cpp ${PROTO_SRCS}${PROTO_HDRS})

# 链接 Protobuf 库
target_link_libraries(protobuf_example ${Protobuf_LIBRARIES})
target_include_directories(protobuf_example PRIVATE ${Protobuf_INCLUDE_DIRS}${CMAKE_CURRENT_BINARY_DIR})

编译命令:

cpp 复制代码
mkdir build && cd build
cmake ..
make

4.核心用法详解

1.基础字段操作

字段类型 C++ 赋值方法 C++ 读取方法
string set_xxx(值) xxx()
int32 set_xxx(值) xxx()
bool set_xxx(true) xxx()

2.重复字段(数组 / 列表)

  • add_xxx():添加新元素(返回指针);
  • xxx_size():获取元素个数;
  • xxx(index):通过下标访问元素;
  • clear_xxx():清空所有元素。

3.序列化 / 反序列化进阶

除了序列化到字符串,还支持文件、流序列化:

cpp 复制代码
// 序列化到文件
std::ofstream out("person.bin", std::ios::binary);
person.SerializeToOstream(&out);
out.close();

// 从文件反序列化
std::ifstream in("person.bin", std::ios::binary);
tutorial::Person person;
person.ParseFromIstream(&in);

4.嵌套消息

直接使用嵌套类操作:

cpp 复制代码
// 嵌套消息赋值
tutorial::Person::PhoneNumber phone;
phone.set_number("123456");

5.注意事项

1.向后兼容性

演进的黄金法则:

  • 可以添加新字段:使用新的字段编号

  • 可以删除字段 :但字段编号不能重用,标记为 reserved

  • 不能更改字段编号

  • 不能更改字段类型(某些安全转换除外)

cpp 复制代码
message UserV2 {
  reserved 2, 3;  // 保留已删除的字段编号
  reserved "name", "email";  // 保留已删除的字段名

int32 id = 1;
// name 字段已删除,使用 full_name 替代
string full_name = 7;
int32 age = 4;
  UserStatus status = 5;
repeated Address addresses = 6;
}

2.使用 Arena 分配器

Arena 可以减少内存分配开销,特别是在创建大量小对象时。

3.复用消息对象

避免频繁创建和销毁消息对象,使用 Clear() 方法复用:

cpp 复制代码
myproject::User user;
for (int i = 0; i < 1000; ++i) {
    user.Clear();  // 清空消息,保留分配的内存
    user.set_id(i);
    user.set_name("User " + std::to_string(i));
// ... 序列化或处理
}

4.使用 ByteSizeLong() 预分配缓冲区

cpp 复制代码
myproject::User user;
// ... 设置字段

size_t size = user.ByteSizeLong();  // 获取序列化后的大小
std::string buffer;
buffer.reserve(size);  // 预分配内存,避免多次重新分配
user.SerializeToString(&buffer);

5.零拷贝序列化(高级)

对于性能要求极高的场景,可以使用 SerializeToArray

cpp 复制代码
myproject::User user;
// ... 设置字段

size_t size = user.ByteSizeLong();
std::unique_ptr<char[]> buffer(newchar[size]);
user.SerializeToArray(buffer.get(), size);
// 现在 buffer 包含序列化数据,可以直接发送或写入

6.Protobuf vs 其他序列化方案

特性 Protobuf JSON XML MessagePack
数据大小 最小 最大
解析速度 最快 最慢
可读性 差(二进制) 好(文本) 好(文本) 差(二进制)
类型安全
向后兼容 优秀 一般 一般 一般
人类可编辑

7.总结

  • Protobuf 是 C++ 高性能序列化的首选方案,核心流程:定义 .proto → 生成 C++ 代码 → 序列化 / 反序列化
  • 编译时必须链接 -lprotobuf 库,使用 proto3 语法更简洁;
  • 支持基础类型、嵌套消息、枚举、重复字段,完全满足复杂数据结构需求;
  • 强类型、跨语言、向后兼容的特性,让它成为微服务、网络通信的工业级标准;
相关推荐
爱学习的程序媛13 小时前
C 语言全景指南:从底层原理到工业级实战
c++·c#·c
神仙别闹13 小时前
基于QT(C++)+SQL Server 2008 实现相机租赁系统
开发语言·c++·数码相机
Stzzfntty13 小时前
嵌软c八股刷题记录
c语言·开发语言·算法
xier_ran13 小时前
【C++】堆(Heap)与栈(Stack)内存详解
java·开发语言·c++
暴躁小师兄数据学院13 小时前
【AI大模型应用开发工程师特训笔记】第04讲(第8章):面向对象编程
开发语言·python
LuminousCPP13 小时前
C 语言通讯录补坑篇:终版遗留 Bug 修复,解决修改姓名输入错乱问题
c语言·开发语言·数据结构·经验分享·笔记·顺序表
黄小白的进阶之路13 小时前
C++提高编程---3.7 STL-常用容器-list 容器【P215~P222】
c++
码界筑梦坊13 小时前
164-基于Python的甜点销售数据可视化分析系统
开发语言·python·信息可视化·数据分析·毕业设计