目录
[3.1.定义 Protobuf 数据结构(.proto 文件)](#3.1.定义 Protobuf 数据结构(.proto 文件))
[3.2.编译生成 C++ 代码](#3.2.编译生成 C++ 代码)
[3.3.编写 C++ 业务代码](#3.3.编写 C++ 业务代码)
[6.Protobuf vs 其他序列化方案](#6.Protobuf vs 其他序列化方案)
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):必须字段
-
-
数据类型
:
int32、int64、uint32、uint64、sint32、sint64、fixed32、fixed64、sfixed32、sfixed64、float、double、bool、string、bytes、枚举、其他消息类型。
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语法更简洁; - 支持基础类型、嵌套消息、枚举、重复字段,完全满足复杂数据结构需求;
- 强类型、跨语言、向后兼容的特性,让它成为微服务、网络通信的工业级标准;