【学习笔记】Protobuf相关知识

Protobuf相关知识学习笔记

【学习笔记】Protobuf

Protocol Buffers(简称 Protobuf)是由 Google 开发的跨平台、高效的数据序列化协议,用于结构化数据的存储和传输。它类似于 JSON/XML,但具有更高的效率、更小的体积和更强的跨语言支持,广泛应用于微服务、分布式系统和移动应用的数据交互中。

Protobuf 通过二进制编码、自动代码生成和灵活的字段扩展机制,在性能、兼容性和开发效率上实现了多维突破。

主要其中的bytes类型也能支持json格式传输,接收方直接调用json的库直接实现大量数据的传输。在数据量大并且一个一个写字段复杂的情况下,很友好。

一、protobuf实现流程

定义.proto 文件

每个字段需指定唯一编号(1, 2, 3…),用于二进制格式中标识字段,编号在后续版本中不可修改。

syntax = "proto3";  // 指定版本(当前主流为proto3)

package tutorial;  // 包名,避免命名冲突

// 消息类型定义
message Person {
  int32 id = 1;              // 字段编号1,int32类型
  string name = 2;       // 字段编号2,字符串类型
  repeated string emails = 3;  // 重复字段(数组)
  enum Gender {         // 枚举类型
    MALE = 0;
    FEMALE = 1;
  }
  Gender gender = 4;     // 枚举字段
}

使用 Protobuf 编译器protoc生成目标语言代码,这里用的是C++。

protoc --cpp_out=. example.proto
#include <iostream>
#include <fstream>
#include "example.pb.h"  // 假设protoc已生成该头文件

using namespace std;

int main() {
    // 序列化(编码)
    tutorial::Person person;  // tutorial为.proto文件中的package
    person.set_id(123);
    person.set_name("Alice");
    person.add_emails("alice@example.com");
    person.set_gender(tutorial::Person::FEMALE);  // 枚举类型

    // 序列化为二进制数据
    string data;
    person.SerializeToString(&data);

    // 或者输出到文件流
    // fstream output("person.bin", ios::out | ios::binary);
    // person.SerializeToOstream(&output);

    // 反序列化(解码)
    tutorial::Person parsedPerson;
    parsedPerson.ParseFromString(data);

    // 或者从文件流读取
    // fstream input("person.bin", ios::in | ios::binary);
    // parsedPerson.ParseFromIstream(&input);

    cout << "Name: " << parsedPerson.name() << endl;  // 输出:Alice
    return 0;
}

二、 bytes类型结合json

个人比较喜欢的点:

比较喜欢的一点:

protobuf不仅仅局限于一个变量一个变量的定义与传递,还支持bytes类型,可以将这个类型传递json格式的字符串,在解析的时候利用json格式解析即可,如果数据量很大不愿意一个一个定义字段的情况下,极为方便。

syntax = "proto3";
package example;

message JsonContainer {
  bytes json_content = 1;       // 存储JSON文本的二进制数据
}
#include <iostream>
#include <fstream>
#include <string>
#include "json_container.pb.h"  // 由protoc生成
#include "nlohmann/json.hpp"    // JSON解析库(需额外安装)

using json = nlohmann::json;    // 简化命名

int main() {
    // === 创建JSON数据 ===
    json layer_json;
    layer_json["Info"]["id"] = 1;
    layer_json["Info"]["name"] = "Alice";
    layer_json["Info"]["emails"] = "alice@example.com";
    
    // 将JSON转换为字符串
    std::string json_str = layer_json.dump();  // 转为JSON文本
    std::cout << "原始JSON: " << json_str << std::endl;


    // === Protobuf序列化(JSON → bytes) ===
    example::JsonContainer container;
    container.set_json_content(json_str);      // 将JSON文本存入bytes字段
    
    // 序列化为二进制数据
    std::string serializedData;
    container.SerializeToString(&serializedData);
    std::cout << "序列化后大小: " << serializedData.size() << " 字节" << std::endl;


    // === Protobuf反序列化(bytes → JSON) ===
    example::JsonContainer parsedContainer;
    parsedContainer.ParseFromString(serializedData);
    
    // 提取JSON字符串
    std::string extractedJson = parsedContainer.json_content();
    
    // 解析JSON
    try {
        json parsedJson = json::parse(extractedJson);
        
        // 验证解析结果
        std::cout << "解析后的JSON:" << std::endl;
        std::cout << "ID: " << parsedJson["Info"]["id"] << std::endl;
        std::cout << "Name: " << parsedJson["Info"]["name"] << std::endl;
        std::cout << "Email: " << parsedJson["Info"]["emails"] << std::endl;
    } catch (const json::parse_error& e) {
        std::cerr << "JSON解析错误: " << e.what() << std::endl;
        return 1;
    }

    return 0;
}

结果:

原始JSON: {"Info":{"id":1,"name":"Alice","emails":"alice@example.com"}}
序列化后大小: 75 字节
解析后的JSON:
ID: 1
Name: Alice
Email: alice@example.com

json一般用的nlohmann:

json介绍
nlohmann/json- 头文件仅需包含单一文件
- C++11 兼容
- 简洁的 API(类似 Python)
- 自动类型推导

这种方式有几个优势:

​ 1、 可以充分利用两者的优势。

(1)protobuf的优势包括数据压缩(序列化为 Protobuf 的bytes后,体积通常比原始 JSON 小 30%~70%,尤其适合带宽受限或存储密集型场景(如物联网、大数据传输))传输效率(Protobuf 的二进制解析速度远快于 JSON(如 C++ 的 Protobuf 库解析效率约为 JSON 库的 3~5 倍),在处理高频数据交互时(如实时通信、API 接口),可显著降低 CPU 开销)。

(2)JSON是文本格式,可读性好,容易解析,结合起来香饽饽。

​ 2、 利用json格式的灵活性,可以随时添加字段而不需要去修改protobuf的配置文件添加字段,对于随时需要添加字段的数据比较方便。

​ 3、 后端服务可使用 Protobuf 进行高效通信(如 C++),而前端或低性能设备可通过解析 JSON 数据减少计算开销。例如:服务端用 Protobuf 序列化 JSON 数据为bytes,通过网络传输;客户端接收到数据后,用 Protobuf 解析出bytes,再转换为 JSON 进行展示。

三、repeated 关键字

repeated 关键字用于声明一个字段为数组类型(或称为 “重复字段”),类似于 C++ 中的 std::vector。它允许消息中包含零个或多个相同类型的值,且这些值会按顺序存储。

在一般的轮巡场景中可以用到这个关键字。

// addressbook.proto
syntax = "proto3";

message Person {
  string name = 1;
  int32 id = 2;
  string email = 3;
}

message AddressBook {
  repeated Person people = 1;  // 重复字段:存储多个Person对象
}
#include <iostream>
#include "addressbook.pb.h"  // 由protoc生成的头文件

int main() {
    // 创建AddressBook对象
    tutorial::AddressBook address_book;

    // 添加第一个Person
    tutorial::Person* person1 = address_book.add_people();  // 创建并添加新Person
    person1->set_name("Alice");
    person1->set_id(1);
    person1->set_email("alice@example.com");

    // 添加第二个Person
    tutorial::Person* person2 = address_book.add_people();
    person2->set_name("Bob");
    person2->set_id(2);
    person2->set_email("bob@example.com");

    // 访问重复字段
    std::cout << "联系人数量: " << address_book.people_size() << std::endl;

    // 遍历所有Person
    for (int i = 0; i < address_book.people_size(); i++) {
        const tutorial::Person& person = address_book.people(i);
        std::cout << "联系人 #" << i + 1 << ":\n";
        std::cout << "  姓名: " << person.name() << "\n";
        std::cout << "  ID: " << person.id() << "\n";
        std::cout << "  Email: " << person.email() << "\n";
    }

    // 修改现有元素(例如修改第一个联系人的邮箱)
    if (address_book.people_size() > 0) {
        address_book.mutable_people(0)->set_email("alice.new@example.com");
        std::cout << "修改后第一个联系人的邮箱: " 
                  << address_book.people(0).email() << std::endl;
    }

    return 0;
}

注意:

序列化顺序:repeated 字段的元素在序列化时会按添加顺序排列,反序列化时也会保持相同顺序。这也是正常轮巡过程中按照顺序进行轮巡的要求之一了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值