Protobuf详解(从安装到实战)

一、Protobuf 核心介绍

  1. 什么是 Protobuf

Protobuf(Protocol Buffers)是由 Google 开发的一种语言无关、平台无关、可扩展的序列化数据格式,用于结构化数据的存储与传输,常作为接口数据交互、数据持久化的解决方案,相比 JSON、XML 具有更高的传输效率、更小的数据体积和更强的兼容性

  1. 核心优势

高效紧凑:采用二进制编码,数据体积远小于 JSON/XML(通常仅为 JSON 的 1/3 甚至更小),传输和存储成本更低;

跨语言跨平台:支持 C++、Java、Python、Go 等几乎所有主流编程语言,可在不同系统(Windows、Linux、Mac)间无缝交互;

可扩展性强:支持向后兼容(新增字段不影响旧版本解析)和向前兼容(旧版本可忽略新增字段),无需破坏原有数据格式;

解析速度快:二进制编码格式无需复杂的文本解析,解析效率远高于 JSON/XML;

强类型约束:通过 .proto 文件定义数据结构,具有严格的类型校验,避免数据格式混乱。

  1. 适用场景

分布式系统间的 RPC 通信(如 gRPC 框架默认使用 Protobuf);

大规模数据存储与传输(如日志采集、大数据同步);

跨语言服务间的数据交互(如后端与前端、不同语言微服务之间);

配置文件存储(相比 JSON 更紧凑、更易维护)。

二、Protobuf 安装教程(Linux 环境,以 3.21.11 版本为例)

  1. 安装前置依赖

    更新软件源

    sudo apt update && sudo apt upgrade -y

    安装编译所需工具

    sudo apt install -y cmake build-essential libssl-dev wget

  2. 卸载系统原有不兼容版本(可选,避免版本冲突)

    卸载系统自带的低版本 Protobuf

    sudo apt remove -y protobuf-compiler libprotobuf-dev

    sudo apt autoremove -y

  3. 下载 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
  1. 验证安装成功

    查看 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(数组)、嵌套消息、枚举。

  1. 生成 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()、序列化 / 反序列化方法)。
  1. 编写业务代码(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;

}
  1. 配置 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)
  1. 编译与运行项目
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 库和线程库,避免路径与依赖问题。

相关推荐
hmbbcsm2 小时前
python做题小记(八)
开发语言·c++·算法
再睡一夏就好2 小时前
深入Linux线程:从轻量级进程到双TCB架构
linux·运维·服务器·c++·学习·架构·线程
特立独行的猫a3 小时前
C++开发中的Pimpl机制与类声明机制深度解析:现代C++的编译解耦艺术
开发语言·c++·pimpl
GoWjw3 小时前
在C&C++指针的惯用方法
c语言·开发语言·c++
君义_noip3 小时前
信息学奥赛一本通 1453:移动玩具 | 洛谷 P4289 [HAOI2008] 移动玩具
c++·算法·信息学奥赛·csp-s
superman超哥3 小时前
仓颉语言中错误恢复策略的深度剖析与工程实践
c语言·开发语言·c++·python·仓颉
玖剹3 小时前
记忆化搜索题目(二)
c语言·c++·算法·leetcode·深度优先·剪枝·深度优先遍历
陳10303 小时前
C++:string(3)
开发语言·c++
搬砖的kk4 小时前
Lycium++ - OpenHarmony PC C/C++ 增强编译框架
c语言·开发语言·c++