跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客GitHub 精选镜像工具UI配色美学隐私政策关于联系
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
C++

Protobuf C++ 实战入门指南

介绍 Google Protocol Buffers(Protobuf)在 C++ 中的实战应用。涵盖序列化概念、Windows/Linux 环境搭建、proto 文件编写及编译流程。详解 Proto3 语法特性包括字段规则、嵌套消息、枚举、oneof 及 map。通过通讯录系统案例演示版本兼容性与 Any 类型使用。对比 Protobuf 与 JSON 性能差异,并提供字段编号规则、兼容性处理及网络编程示例。适合 C++ 开发者快速掌握高效数据传输方案。

laoliangsh发布于 2026/3/26更新于 2026/5/3124 浏览
Protobuf C++ 实战入门指南

前言

作为一名 C++ 开发者,你是否曾为以下问题烦恼过?

  • 不同服务之间数据传输格式混乱
  • JSON 序列化/反序列化性能瓶颈
  • 协议字段频繁变更导致的兼容性问题
  • 手写解析代码繁琐且容易出错

今天我要介绍的 Google Protocol Buffers(简称 Protobuf)就是解决这些问题的利器!下面让我用最通俗易懂的方式,带你从零开始掌握 Protobuf。

一、什么是 Protobuf?

1.1 序列化的概念

序列化:把内存中的对象转换为字节序列的过程 反序列化:把字节序列恢复为对象的过程

简单来说,就像你要把一本书寄给朋友:

  • 序列化 = 把书打包成包裹
  • 反序列化 = 朋友收到包裹后拆开阅读
1.2 为什么需要序列化?
  1. 存储数据:将对象保存到文件或数据库
  2. 网络传输:通过网络发送对象数据
  3. 分布式系统:不同服务间的数据交换
1.3 Protobuf vs JSON vs XML
特性JSONXMLProtobuf
格式文本文本二进制
可读性好好差(需反序列化)
大小中等大小
速度中等慢快
通用性通用通用Google 生态

简单理解:Protobuf 就像快递的真空压缩包装,体积小、速度快,但需要专门工具才能打开。

二、环境安装(C++ 版)

2.1 Windows 安装
# 1. 下载 protoc-xx.x-win64.zip
# 从 https://github.com/protocolbuffers/protobuf/releases 下载
# 2. 解压到 D:\protobuf
# 目录结构:
# D:\protobuf\bin\protoc.exe
# D:\protobuf\include\google\protobuf\...
# 3. 添加环境变量
# 将 D:\protobuf\bin 添加到系统 Path
# 4. 验证安装
protoc --version
2.2 Linux 安装
# 1. 安装依赖
sudo apt-get install autoconf automake libtool curl make g++ unzip -y
# 2. 下载并解压
wget https://github.com/protocolbuffers/protobuf/releases/download/v21.11/protobuf-all-21.11.zip
unzip protobuf-all-21.11.zip
cd protobuf-21.11
# 3. 编译安装
./autogen.sh
./configure
make
sudo make install
# 4. 验证安装
protoc --version
2.3 C++ 项目配置
# CMakeLists.txt 示例
cmake_minimum_required(VERSION 3.10)
project(MyProtobufDemo)
# 查找 Protobuf
find_package(Protobuf REQUIRED)
# 包含目录
include_directories(${Protobuf_INCLUDE_DIRS})
# 添加可执行文件
add_executable(demo main.cpp contacts.pb.cc)
# 链接库
target_link_libraries(demo ${Protobuf_LIBRARIES})

三、第一个 Protobuf 程序

3.1 创建 .proto 文件

创建 contacts.proto:

// 指定使用 proto3 语法
syntax = "proto3";
// 包名,相当于 C++ 的命名空间
package contacts;
// 定义联系人消息
message PeopleInfo {
    string name = 1; // 姓名
    int32 age = 2;   // 年龄
}

字段格式:字段类型 字段名 = 字段编号;

3.2 编译生成 C++ 代码
# 生成 C++ 头文件和源文件
protoc --cpp_out=. contacts.proto
# 生成的文件:
# contacts.pb.h - 类声明
# contacts.pb.cc - 类实现
3.3 编写 C++ 测试程序

创建 main.cpp:

#include <iostream>
#include <string>
#include "contacts.pb.h"

int main() {
    // 1. 创建联系人对象
    contacts::PeopleInfo people;
    people.set_name("张三");
    people.set_age(20);

    // 2. 序列化为字符串
    std::string serialized_str;
    if (!people.SerializeToString(&serialized_str)) {
        std::cerr << "序列化失败!" << std::endl;
        return -1;
    }
    std::cout << "序列化成功!字节数:" << serialized_str.size() << std::endl;

    // 3. 反序列化
    contacts::PeopleInfo new_people;
    if (!new_people.ParseFromString(serialized_str)) {
        std::cerr << "反序列化失败!" << std::endl;
        return -1;
    }

    // 4. 输出结果
    std::cout << "姓名:" << new_people.name() << std::endl;
    std::cout << "年龄:" << new_people.age() << std::endl;
    return 0;
}
3.4 编译运行
# 编译
g++ main.cpp contacts.pb.cc -o demo -std=c++11 -lprotobuf
# 运行
./demo

输出结果:

序列化成功!字节数:7
姓名:张三
年龄:20

四、Proto3 语法详解

4.1 字段规则
基本字段类型
.proto 类型C++ 类型说明
stringstd::stringUTF-8 字符串
int32int32_t32 位整数
int64int64_t64 位整数
boolbool布尔值
doubledouble双精度浮点数
floatfloat单精度浮点数
bytesstd::string字节序列
字段修饰符
// singular: 0 或 1 个(proto3 默认)
string name = 1;
// repeated: 多个(类似数组)
repeated string phone_numbers = 2;
// optional: 可选字段(proto3 需要显式声明)
optional string email = 3;
4.2 嵌套消息
message PeopleInfo {
    string name = 1;
    int32 age = 2;
    // 内嵌消息
    message Phone {
        string number = 1;
        string type = 2; // "home", "work", "mobile"
    }
    repeated Phone phones = 3;
}

C++ 中使用:

contacts::PeopleInfo person;
contacts::PeopleInfo_Phone* phone = person.add_phones();
phone->set_number("13800138000");
phone->set_type("mobile");
4.3 枚举类型
message Phone {
    string number = 1;
    enum PhoneType {
        MP = 0; // 移动电话
        TEL = 1; // 固定电话
    }
    PhoneType type = 2;
}
4.4 导入其他 proto 文件
// phone.proto
syntax = "proto3";
package phone;
message Phone {
    string number = 1;
}

// contacts.proto
syntax = "proto3";
package contacts;
import "phone.proto"; // 导入其他 proto
message PeopleInfo {
    string name = 1;
    repeated phone.Phone phones = 2; // 使用导入的类型
}

五、实战项目:通讯录系统

让我们通过一个完整的通讯录项目来深入学习。

5.1 版本 1.0:基础通讯录

contacts_v1.proto:

syntax = "proto3";
package contacts_v1;
// 联系人
message PeopleInfo {
    string name = 1;
    int32 age = 2;
}
// 通讯录
message Contacts {
    repeated PeopleInfo contacts = 1;
}

write_contacts.cpp:

#include <iostream>
#include <fstream>
#include "contacts_v1.pb.h"

void AddPeople(contacts_v1::PeopleInfo* person) {
    std::cout << "请输入姓名:";
    std::string name;
    std::getline(std::cin, name);
    person->set_name(name);
    std::cout << "请输入年龄:";
    int age;
    std::cin >> age;
    person->set_age(age);
    std::cin.ignore(256, '\n');
}

int main(int argc, char* argv[]) {
    if (argc != 2) {
        std::cerr << "Usage: " << argv[0] << " CONTACTS_FILE" << std::endl;
        return -1;
    }
    contacts_v1::Contacts contacts;
    // 读取已有通讯录
    std::fstream input(argv[1], std::ios::in | std::ios::binary);
    if (input) {
        if (!contacts.ParseFromIstream(&input)) {
            std::cerr << "解析通讯录失败!" << std::endl;
            return -1;
        }
    }
    input.close();
    // 添加新联系人
    AddPeople(contacts.add_contacts());
    // 写入文件
    std::fstream output(argv[1], std::ios::out | std::ios::trunc | std::ios::binary);
    if (!contacts.SerializeToOstream(&output)) {
        std::cerr << "写入通讯录失败!" << std::endl;
        return -1;
    }
    output.close();
    std::cout << "联系人添加成功!" << std::endl;
    return 0;
}
5.2 版本 2.0:增强功能

让我们为通讯录添加更多功能:

contacts_v2.proto:

syntax = "proto3";
package contacts_v2;
message PeopleInfo {
    string name = 1;
    int32 age = 2;
    message Phone {
        string number = 1;
        enum PhoneType {
            MP = 0; // 移动电话
            TEL = 1; // 固定电话
        }
        PhoneType type = 2;
    }
    repeated Phone phones = 3;
    // 使用 oneof 实现多选一
    oneof other_contact {
        string qq = 4;
        string wechat = 5;
    }
    // 使用 map 存储备注
    map<string, string> remarks = 6;
}

高级特性解析:

  1. oneof 字段:多个字段中只能设置一个
  2. map 字段:键值对存储
  3. 枚举类型:类型安全的选项
5.3 版本 3.0:Any 类型和未知字段

contacts_v3.proto:

syntax = "proto3";
package contacts_v3;
import "google/protobuf/any.proto";
message Address {
    string home = 1;
    string office = 2;
}
message PeopleInfo {
    string name = 1;
    int32 age = 2;
    // 使用 Any 存储任意类型
    google.protobuf.Any extra_info = 3;
}

使用 Any 类型:

// 设置 Any 类型
contacts_v3::PeopleInfo person;
contacts_v3::Address address;
address.set_home("北京市");
address.set_office("海淀区");
google::protobuf::Any* any_data = person.mutable_extra_info();
any_data->PackFrom(address);

// 读取 Any 类型
if (person.has_extra_info() && person.extra_info().Is<contacts_v3::Address>()) {
    contacts_v3::Address unpacked_addr;
    person.extra_info().UnpackTo(&unpacked_addr);
    std::cout << "家庭地址:" << unpacked_addr.home() << std::endl;
}

六、性能对比测试

让我们通过实际测试看看 Protobuf 的优势:

compare_performance.cpp:

#include <iostream>
#include <chrono>
#include <json/json.h>
#include "contacts.pb.h"

// 测试 10000 次序列化/反序列化
const int TEST_COUNT = 10000;

void TestProtobuf() {
    contacts::PeopleInfo person;
    person.set_name("张三");
    person.set_age(25);
    auto start = std::chrono::high_resolution_clock::now();
    std::string buffer;
    for (int i = 0; i < TEST_COUNT; ++i) {
        person.SerializeToString(&buffer);
        contacts::PeopleInfo new_person;
        new_person.ParseFromString(buffer);
    }
    auto end = std::chrono::high_resolution_clock::now();
    auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
    std::cout << "Protobuf 测试 " << TEST_COUNT << " 次耗时:" << duration.count() << "ms" << std::endl;
    std::cout << "序列化大小:" << buffer.size() << " 字节" << std::endl;
}

void TestJSON() {
    Json::Value person;
    person["name"] = "张三";
    person["age"] = 25;
    Json::StreamWriterBuilder writer;
    auto start = std::chrono::high_resolution_clock::now();
    for (int i = 0; i < TEST_COUNT; ++i) {
        std::string json_str = Json::writeString(writer, person);
        Json::Value parsed;
        Json::Reader().parse(json_str, parsed);
    }
    auto end = std::chrono::high_resolution_clock::now();
    auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
    std::cout << "JSON 测试 " << TEST_COUNT << " 次耗时:" << duration.count() << "ms" << std::endl;
    std::cout << "序列化大小:" << Json::writeString(writer, person).size() << " 字节" << std::endl;
}

int main() {
    std::cout << "=== 性能对比测试 ===" << std::endl;
    TestProtobuf();
    TestJSON();
    return 0;
}

测试结果(仅供参考):

=== 性能对比测试 ===
Protobuf 测试 10000 次耗时:45ms
序列化大小:12 字节
JSON 测试 10000 次耗时:120ms
序列化大小:25 字节

可以看到 Protobuf 在性能和空间上都有明显优势!

七、最佳实践和注意事项

7.1 字段编号规则
  1. 编号范围: 1-536,870,911(但 1-15 更高效)
  2. 保留编号: 不要修改已使用的字段编号
  3. 删除字段: 使用 reserved 关键字
message MyMessage {
    // 保留已删除的字段编号
    reserved 2, 15, 9 to 11;
    reserved "old_field";
    string new_field = 16;
}
7.2 版本兼容性
// 错误:修改字段类型
// int32 age = 2; // 原来 string age = 2;
// 错误!
// 正确:添加新字段
int32 age = 2;
string birthday = 3; // 新增
7.3 编译优化选项
// 优化速度(默认)
option optimize_for = SPEED;
// 优化代码大小
option optimize_for = CODE_SIZE;
// 精简运行时(移动设备)
option optimize_for = LITE_RUNTIME;

八、网络应用示例

让我们看看如何在网络编程中使用 Protobuf:

server.cpp(简化版):

#include <iostream>
#include <thread>
#include <vector>
#include "contacts.pb.h"

class ContactServer {
private:
    contacts::Contacts contacts_;
public:
    void AddContact(const contacts::PeopleInfo& person) {
        *contacts_.add_contacts() = person;
        SaveToFile();
    }
    std::string SerializeContacts() {
        std::string data;
        contacts_.SerializeToString(&data);
        return data;
    }
private:
    void SaveToFile() {
        std::ofstream file("contacts.dat", std::ios::binary);
        contacts_.SerializeToOstream(&file);
    }
};

// 网络处理线程
void HandleClient(int client_fd, ContactServer& server) {
    char buffer[1024];
    ssize_t n = read(client_fd, buffer, sizeof(buffer));
    if (n > 0) {
        contacts::PeopleInfo person;
        if (person.ParseFromArray(buffer, n)) {
            server.AddContact(person);
            std::cout << "收到新联系人:" << person.name() << std::endl;
        }
    }
    close(client_fd);
}

九、常见问题解答

Q1: Protobuf 和 JSON 如何选择?

使用 Protobuf:

  • 高性能要求的场景
  • 内部服务通信
  • 移动端应用
  • 需要向前/向后兼容

使用 JSON:

  • 需要人类可读的配置文件
  • Web API 接口
  • 简单的数据交换
  • 第三方接口对接
Q2: Protobuf 有哪些缺点?
  1. 可读性差:二进制格式无法直接阅读
  2. 需要编译:需要 proto 编译器
  3. 灵活性差:相比 JSON 动态性较差
  4. 生态限制:主要在 Google 生态中流行
Q3: 如何处理字段变更?
  1. 添加字段:直接添加,使用新编号
  2. 删除字段:标记为 reserved,不要重用编号
  3. 修改字段:创建新字段,逐步迁移

十、总结

通过本文的学习,你应该已经掌握了:

✅ 基础概念:理解序列化和 Protobuf 的作用 ✅ 环境搭建:在不同平台安装配置 Protobuf ✅ 语法掌握:熟悉 proto3 的各种语法特性 ✅ 实战开发:完成通讯录项目的各个版本 ✅ 性能优化:了解 Protobuf 的性能优势 ✅ 最佳实践:掌握开发中的注意事项

Protobuf 作为现代分布式系统的核心技术之一,掌握它对于 C++ 开发者来说至关重要。希望这篇教程能帮助你在 Protobuf 的学习道路上顺利前行!

目录

  1. 前言
  2. 一、什么是 Protobuf?
  3. 1.1 序列化的概念
  4. 1.2 为什么需要序列化?
  5. 1.3 Protobuf vs JSON vs XML
  6. 二、环境安装(C++ 版)
  7. 2.1 Windows 安装
  8. 1. 下载 protoc-xx.x-win64.zip
  9. 从 https://github.com/protocolbuffers/protobuf/releases 下载
  10. 2. 解压到 D:\protobuf
  11. 目录结构:
  12. D:\protobuf\bin\protoc.exe
  13. D:\protobuf\include\google\protobuf\...
  14. 3. 添加环境变量
  15. 将 D:\protobuf\bin 添加到系统 Path
  16. 4. 验证安装
  17. 2.2 Linux 安装
  18. 1. 安装依赖
  19. 2. 下载并解压
  20. 3. 编译安装
  21. 4. 验证安装
  22. 2.3 C++ 项目配置
  23. CMakeLists.txt 示例
  24. 查找 Protobuf
  25. 包含目录
  26. 添加可执行文件
  27. 链接库
  28. 三、第一个 Protobuf 程序
  29. 3.1 创建 .proto 文件
  30. 3.2 编译生成 C++ 代码
  31. 生成 C++ 头文件和源文件
  32. 生成的文件:
  33. contacts.pb.h - 类声明
  34. contacts.pb.cc - 类实现
  35. 3.3 编写 C++ 测试程序
  36. 3.4 编译运行
  37. 编译
  38. 运行
  39. 四、Proto3 语法详解
  40. 4.1 字段规则
  41. 基本字段类型
  42. 字段修饰符
  43. 4.2 嵌套消息
  44. 4.3 枚举类型
  45. 4.4 导入其他 proto 文件
  46. 五、实战项目:通讯录系统
  47. 5.1 版本 1.0:基础通讯录
  48. 5.2 版本 2.0:增强功能
  49. 5.3 版本 3.0:Any 类型和未知字段
  50. 六、性能对比测试
  51. 七、最佳实践和注意事项
  52. 7.1 字段编号规则
  53. 7.2 版本兼容性
  54. 7.3 编译优化选项
  55. 八、网络应用示例
  56. 九、常见问题解答
  57. Q1: Protobuf 和 JSON 如何选择?
  58. Q2: Protobuf 有哪些缺点?
  59. Q3: 如何处理字段变更?
  60. 十、总结
  • 💰 8折买阿里云服务器限时8折了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 🤖 一键搭建Deepseek满血版了解详情
  • 一键打造专属AI 智能体了解详情
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • 基于 LangChain 的 ReAct AI Agent 搭建实战
  • 本地离线部署AI大模型:OpenClaw + Ollama + Qwen3.5:cloud/Qwen3:0.6b 超详细教程(无需GPU)
  • AI 产品经理进阶路线图:产业链、分类与核心能力提升
  • C++ 哈希表:概念、冲突解决与代码实现
  • CoPaw:阿里云通义推出的个人智能体工作台
  • Llama-3.2-3B在低配笔记本通过Ollama稳定生成千字长文
  • 前端 Word 文档生成实战:DOCX.js 完整使用指南
  • AI Agent 赋能新闻媒体:自动写作与内容分发实践
  • SpringBoot 集成 MyBatis-Plus Dynamic-Datasource 实现主从多数据源
  • 基于 FPGA 的 CARRY4 抽头延迟链 TDC 延时仿真
  • 无人机 Remote ID Beacon 帧字段详解
  • Oracle 解锁被封锁账户
  • Java 环境配置与首个程序实战指南
  • Stable Diffusion 显存优化与内存不足解决方案
  • 使用昇腾 Atlas 300I Duo 推理卡部署 32B 大语言模型 (MindIE+WebUI)
  • Ollama 与 FastGPT 本地私有化大模型部署指南
  • 三本关键 AI 大模型开发书籍推荐与学习指南
  • 码云(Gitee)代码推送全流程与实操指南
  • 本地部署 Kimi K2 模型:llama.cpp、vLLM 与 Docker 方案
  • Windows 下配置 KDB+、JupyterQ 与 Python (embedPy) 环境

相关免费在线工具

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online

  • JSON 压缩

    通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online

  • JSON美化和格式化

    将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online