一、Protobuf 核心介绍
- 什么是 Protobuf
Protobuf(Protocol Buffers)是由 Google 开发的一种语言无关、平台无关、可扩展的序列化数据格式,用于结构化数据的存储与传输,常作为接口数据交互、数据持久化的解决方案,相比 JSON、XML 具有更高的传输效率、更小的数据体积和更强的兼容性。
- 核心优势
高效紧凑:采用二进制编码,数据体积远小于 JSON/XML(通常仅为 JSON 的 1/3 甚至更小),传输和存储成本更低;
跨语言跨平台:支持 C++、Java、Python、Go 等几乎所有主流编程语言,可在不同系统(Windows、Linux、Mac)间无缝交互;
可扩展性强:支持向后兼容(新增字段不影响旧版本解析)和向前兼容(旧版本可忽略新增字段),无需破坏原有数据格式;
解析速度快:二进制编码格式无需复杂的文本解析,解析效率远高于 JSON/XML;
强类型约束:通过 .proto 文件定义数据结构,具有严格的类型校验,避免数据格式混乱。
- 适用场景
分布式系统间的 RPC 通信(如 gRPC 框架默认使用 Protobuf);
大规模数据存储与传输(如日志采集、大数据同步);
跨语言服务间的数据交互(如后端与前端、不同语言微服务之间);
配置文件存储(相比 JSON 更紧凑、更易维护)。
二、Protobuf 安装教程(Linux 环境,以 3.21.11 版本为例)
-
安装前置依赖
更新软件源
sudo apt update && sudo apt upgrade -y
安装编译所需工具
sudo apt install -y cmake build-essential libssl-dev wget
-
卸载系统原有不兼容版本(可选,避免版本冲突)
卸载系统自带的低版本 Protobuf
sudo apt remove -y protobuf-compiler libprotobuf-dev
sudo apt autoremove -y
-
下载 Protobuf 源码
步骤 1:下载源码包
# 创建安装目录并进入
mkdir -p ~/protobuf_install && cd ~/protobuf_install
# 下载 3.21.11 版本源码(对应 libprotoc 3.21.11)
wget https://github.com/protocolbuffers/protobuf/archive/refs/tags/v21.11.tar.gz
步骤 2:解压并配置编译
# 解压源码包
tar -zxvf v21.11.tar.gz
cd protobuf-21.11
# 创建编译目录并进入
mkdir build && cd build
# 配置编译参数(指定安装路径、生成共享库)
cmake .. \
-DCMAKE_INSTALL_PREFIX=/usr/local \ # 全局安装路径,所有用户可使用
-DBUILD_SHARED_LIBS=ON \ # 生成动态链接库(.so 文件)
-DCMAKE_BUILD_TYPE=Release # 发布模式,优化编译性能
步骤 3:编译与安装
# 多线程编译(根据 CPU 核心数调整,如 -j8,建议不超过核心数)
make -j4
# 安装到系统
sudo make install
# 刷新动态链接库缓存,让系统识别新安装的库
sudo ldconfig
-
验证安装成功
查看 Protobuf 编译器版本
protoc --version
查看 Protobuf 库版本
pkg-config --modversion protobuf
三、Protobuf 项目实战
项目目录结构
protobuf_cpp_demo/ # 项目根目录
├── build/ # 编译目录(用于存放 CMake 生成文件和编译产物)
├── bin/ # 可执行程序输出目录
├── data/ # 数据存储目录(用于存放序列化后的二进制文件)
├── src/ # 源码目录
│ ├── main.cpp # 主程序文件(业务逻辑)
│ └── proto/ # Protobuf 定义目录
│ └── user.proto # 数据结构定义文件
└── CMakeLists.txt # CMake 构建配置文件
在 src/proto/user.proto 中编写数据结构定义:
cpp
// 指定 Protobuf 语法版本(必须指定,v3 为主流版本)
syntax = "proto3";
// 定义命名空间(C++ 中会生成对应命名空间)
package cpp_demo;
// 枚举类型定义(性别)
enum Gender {
UNKNOWN = 0; // proto3 中枚举默认值必须为 0
MALE = 1;
FEMALE = 2;
}
// 消息类型定义(地址信息)
message Address {
string province = 1; // 字段类型 字段名 = 字段编号(唯一,从 1 开始)
string city = 2;
string detail = 3;
}
// 消息类型定义(用户信息,可嵌套其他消息类型)
message User {
int64 user_id = 1; // 64位整数(用户ID)
string username = 2; // 字符串(用户名)
string email = 3; // 字符串(邮箱)
int32 age = 4; // 32位整数(年龄)
Gender gender = 5; // 枚举类型(性别)
repeated Address addresses = 6; // 重复字段(相当于数组,可包含多个地址)
}
字段编号:用于二进制编码标识字段,1-15 占用 1 个字节,16-2047 占用 2 个字节,常用字段建议使用 1-15;
支持的基础类型:int32、int64、uint32、uint64、string、bool、bytes 等;
复合类型:repeated(数组)、嵌套消息、枚举。
- 生成 C++ 代码
bash
通过 protoc 编译器将 .proto 文件转换为 C++ 可调用的头文件(.h)和源文件(.cc):
# 进入 proto 目录
cd ~/frame/proto_tset/protobuf_cpp_demo/src/proto
# 生成 C++ 代码(--cpp_out=输出目录 源文件)
protoc --cpp_out=./ ./user.proto
执行后会在 src/proto 目录下生成 user.pb.h(头文件)和 user.pb.cc(源文件),包含了数据结构的操作接口(如 set_xxx()、add_xxx()、序列化 / 反序列化方法)。
- 编写业务代码(main.cpp)
cpp
#include <iostream>
#include <string>
#include <fstream>
#include "user.pb.h" // 引入 Protobuf 生成的头文件(路径已通过 CMake 配置)
using namespace std;
using namespace cpp_demo; // 使用 Protobuf 定义的命名空间
// 序列化 User 对象到文件
bool SerializeUserToFile(const User& user, const string& file_path) {
ofstream ofs(file_path, ios::out | ios::binary);
if (!ofs.is_open()) {
cerr << "错误:无法打开文件 " << file_path << " 进行写入!" << endl;
return false;
}
// 调用 Protobuf 内置的序列化方法
return user.SerializeToOstream(&ofs);
}
// 从文件反序列化得到 User 对象
bool DeserializeUserFromFile(User& user, const string& file_path) {
ifstream ifs(file_path, ios::in | ios::binary);
if (!ifs.is_open()) {
cerr << "错误:无法打开文件 " << file_path << " 进行读取!" << endl;
return false;
}
// 调用 Protobuf 内置的反序列化方法
return user.ParseFromIstream(&ifs);
}
// 程序入口函数(必须位于全局命名空间,签名为 int main())
int main() {
// 初始化 Protobuf 库,验证版本兼容性
GOOGLE_PROTOBUF_VERIFY_VERSION;
// 1. 构建 User 对象(通过 Protobuf 生成的接口设置字段值)
User user;
user.set_user_id(10001);
user.set_username("Zhang San");
user.set_email("zhangsan@example.com");
user.set_age(28);
user.set_gender(Gender::MALE);
// 添加多个地址(repeated 字段通过 add_xxx() 方法添加元素)
Address* addr1 = user.add_addresses();
addr1->set_province("Guangdong");
addr1->set_city("Shenzhen");
addr1->set_detail("Science and Technology Park");
Address* addr2 = user.add_addresses();
addr2->set_province("Beijing");
addr2->set_city("Beijing");
addr2->set_detail("Chaoyang District");
// 2. 内存序列化(将对象转换为二进制字符串)
string serialized_data;
if (!user.SerializeToString(&serialized_data)) {
cerr << "错误:用户对象内存序列化失败!" << endl;
return -1;
}
cout << "✅ 内存序列化成功,数据长度:" << serialized_data.size() << " 字节" << endl;
// 3. 文件序列化(将对象写入二进制文件)
string data_file = "../data/user_data.bin";
if (SerializeUserToFile(user, data_file)) {
cout << "✅ 文件序列化成功,存储路径:" << data_file << endl;
} else {
cerr << "错误:用户对象文件序列化失败!" << endl;
return -1;
}
// 4. 文件反序列化(从二进制文件恢复为对象)
User file_user;
if (DeserializeUserFromFile(file_user, data_file)) {
cout << "\n===== 反序列化结果 =====\n";
cout << "用户ID:" << file_user.user_id() << endl;
cout << "用户名:" << file_user.username() << endl;
cout << "邮箱:" << file_user.email() << endl;
cout << "年龄:" << file_user.age() << endl;
cout << "性别:" << (file_user.gender() == Gender::MALE ? "男" : "女") << endl;
cout << "地址数量:" << file_user.addresses_size() << endl;
for (int i = 0; i < file_user.addresses_size(); ++i) {
const Address& addr = file_user.addresses(i);
cout << "地址 " << i+1 << ":" << addr.province() << " "
<< addr.city() << " " << addr.detail() << endl;
}
} else {
cerr << "错误:用户对象文件反序列化失败!" << endl;
return -1;
}
// 释放 Protobuf 库资源(程序退出前调用)
google::protobuf::ShutdownProtobufLibrary();
cout << "\n🎉 程序执行完成!" << endl;
return 0;
}
- 配置 CMake 构建文件(CMakeLists.txt)
在项目根目录创建 CMakeLists.txt,配置编译规则,确保正确链接 Protobuf 库:
cpp
# 最低 CMake 版本要求
cmake_minimum_required(VERSION 3.10)
# 项目名称与编程语言(C++)
project(ProtobufCppDemo LANGUAGES CXX)
# 设置 C++ 标准(C++11 及以上,Protobuf 要求)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# 查找 Protobuf 依赖(确保版本统一)
find_package(Protobuf REQUIRED)
if (PROTOBUF_FOUND)
message(STATUS "✅ 找到 Protobuf,版本:${PROTOBUF_VERSION}")
message(STATUS "Protobuf 头文件目录:${PROTOBUF_INCLUDE_DIRS}")
message(STATUS "Protobuf 库文件:${PROTOBUF_LIBRARIES}")
else ()
message(FATAL_ERROR "❌ 未找到 Protobuf,请先安装!")
endif ()
# 包含头文件目录(让编译器找到 user.pb.h 和 main.cpp 的依赖)
include_directories(
${PROJECT_SOURCE_DIR}/src # src 目录
${PROJECT_SOURCE_DIR}/src/proto # proto 生成的头文件目录
${PROTOBUF_INCLUDE_DIRS} # Protobuf 系统头文件目录
)
# 显式指定源码文件(避免漏文件,比 file(GLOB) 更稳定)
set(SRC_FILES
${PROJECT_SOURCE_DIR}/src/main.cpp # 主程序文件
${PROJECT_SOURCE_DIR}/src/proto/user.pb.cc # Protobuf 生成的源文件
)
# 指定可执行程序输出目录(生成的程序放在 bin 目录下)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/bin)
# 生成可执行程序
add_executable(protobuf_cpp_app ${SRC_FILES})
# 链接 Protobuf 库和线程库(Protobuf 依赖 pthread)
target_link_libraries(protobuf_cpp_app ${PROTOBUF_LIBRARIES} pthread)
- 编译与运行项目
cpp
步骤 1:创建必要目录
cd ~/frame/proto_tset/protobuf_cpp_demo
mkdir -p build bin data # 分别创建编译目录、程序输出目录、数据存储目录
步骤 2:编译项目
# 进入 build 目录
cd build
# 生成 Makefile
cmake ..
# 编译项目(单线程便于查看日志,多线程可使用 make -j4)
make
步骤 3:运行程序
# 运行可执行程序
../bin/protobuf_cpp_app
四、核心注意事项
版本一致性:Protobuf 编译器(protoc)与运行时库(libprotobuf)版本必须统一,否则会出现编译错误(如未定义符号、函数参数不匹配);
头文件路径:main.cpp 中引入 user.pb.h 时,需根据 CMake 的 include_directories 配置确定路径,避免多余前缀导致文件查找失败;
程序入口合法性:C++ 程序入口 main() 必须位于全局命名空间,签名为 int main(),且包含返回值,否则会出现 undefined reference to 'main' 链接错误;
目录提前创建:data 目录需提前创建,否则序列化时会因文件写入失败导致程序异常;
资源释放:程序退出前需调用 google::protobuf::ShutdownProtobufLibrary(),释放 Protobuf 库占用的资源;
字段编号不可修改:.proto 文件中字段的编号一旦确定并投入使用,不可随意修改,否则会导致旧版本无法解析新版本数据(兼容性破坏)。
总结
Protobuf 是高效的结构化数据序列化格式,适用于跨语言、跨平台的数据交互与存储;
安装核心是保证编译器与运行时库版本统一,源码编译是最稳定的安装方式;
项目开发流程:定义 .proto 数据结构 → 生成对应语言代码 → 编写业务逻辑 → 配置构建工具 → 编译运行;
关键配置:CMake 需正确包含头文件目录、指定源码文件、链接 Protobuf 库和线程库,避免路径与依赖问题。