ProtoBuf

protobuf 官网

Protocol Buffers Documentation

序列化与反序列化:Json, XML, ProtoBuf

什么是序列化和反序列化?

  • 序列化:将数据结构或对象转换成可以存储或传输的格式(如 JSON、XML、ProtoBuf 等)的过程。

  • 反序列化:将序列化后的数据恢复成原有的数据结构或对象的过程。

Json, XML, ProtoBuf 简介

JSON

  • 特点:轻量级、易读、易解析的文本格式。

  • 应用场景:常用于前后端数据交换。

  • 序列化:直接将对象转换为 JSON 字符串。

  • 反序列化:将 JSON 字符串解析为对象。

XML

  • 特点:标记语言,数据自描述性强,支持复杂数据结构,但较为冗长。

  • 应用场景:早期的 Web 服务通信,仍在某些领域中使用。

  • 序列化:将对象转换为 XML 文档。

  • 反序列化:将 XML 文档解析为对象。

ProtoBuf

  • 特点:高效的二进制格式,比 XML 更小更快,跨语言跨平台,扩展性和兼容性好。

  • 应用场景:网络通信和数据存储,尤其在性能和空间要求高的场景。

  • 序列化和反序列化:通过编译 .proto 文件生成的代码,自动完成序列化和反序列化操作。

ProtoBuf 的概述和特点

什么是 ProtoBuf?

ProtoBuf (Protocol Buffers) 是一种由 Google 提出的用于序列化结构化数据的语言中立、平台无关的二进制格式。开发者可以用一种简单的语法描述数据结构,并通过工具生成特定语言的代码,从而简化序列化与反序列化操作。

ProtoBuf 的特点

  • 跨语言、跨平台支持:支持多种编程语言(如 C++、Java、Python、Go 等),且在不同平台上兼容。

  • 高效:数据以紧凑的二进制格式存储,序列化速度快,体积小,远小于 XML。

  • 扩展性:可以通过添加新的字段来扩展数据结构,而不影响现有的字段,向后兼容性好。

  • 结构化:采用强类型和字段编号定义,数据明确且易于解析。

ProtoBuf 的使用指南

创建和使用 ProtoBuf

1. 创建 .proto 文件

.proto 文件是用于描述数据结构的文件。在这里,我们以创建一个 contacts.proto 为例。

示例内容:

syntax = "proto3";      // 指定使用的语法版本
package contacts;       // 定义包名,相当于命名空间
​
// 定义一个消息类型 PeopleInfo
message PeopleInfo {
    string name = 1;    // 姓名
    int32 age = 2;      // 年龄
    // 字段编号不是赋值,而是唯一标识字段。编号不能重复。
}

2. 数据字段类型

ProtoBuf 提供了多种数据类型,主要分为标量数据类型和特殊类型。

标量数据类型

  • string:字符串,长度不能超过 2^32。

  • int32:有符号整型,变长编码。

  • uint32:无符号整型,变长编码。

  • sint32:有符号整型,用于负数的高效编码。

  • fixed32:无符号整型,定长编码。

  • bytes:二进制数据,可以包含任意字节序列,但不能超过 2^32

特殊类型

  • 枚举类型:定义特定的枚举值。

  • 嵌套消息类型:可以将一个消息类型嵌套在另一个消息类型中。

  • Map 类型:用于键值对数据的定义。

3. 字段编号

每个字段需要一个唯一编号,用于区分不同字段,这个编号在序列化时会被编码。

字段编号的选择:

  • 编号范围:1 - 2^29 - 1,但编号 19000 - 19999 不可用(预留给 ProtoBuf 内部使用)。

  • 编号 1 - 15 使用一个字节编码,适合频繁出现的字段。16 - 247 编号需要两个字节编码。

  • 注意:字段编号一旦使用,尽量不要更改,以免造成数据解析错误。

编译ProtoBuf 文件

chen@RE:~/l/m/p/p/fastTast|main⚡?

🤓☝️ protoc --cpp_out=. contacts.protoc

将会生成

chen@RE:~/l/m/p/p/fastTast|main⚡?

🤓☝️ ls 09:58:04

contacts.pb.cc contacts.pb.h contacts.proto

使用 -I 搜索目录

$ protoc -I fastTast/ --cpp_out=fastTast/

chen@RE:~/l/m/p/protobuf|main⚡?
🤓☝️protoc -I fastTast/ --cpp_out=fastTast/  contacts.proto                  10:00:13
chen@RE:~/l/m/p/protobuf|main⚡?
🤓☝️tree                        10:00:49
.
└── fastTast
    ├── contacts.pb.cc
    ├── contacts.pb.h
    └── contacts.proto
​
2 directories, 3 files

序列化后的结果为二进制字节序列,而非文本格式

使用序列化

MAKEFILE

main:main.cc contacts.pb.cc
    g++ -o  Test main.cc contacts.pb.cc  -std=c++17 -lprotobuf -labsl_log_internal_check_op -labsl_log_internal_message
.PHONY:clean
clean:
    rm -f main
​

MAIN.CC

#include <iostream>
#include "contacts.pb.h"
​
​
​
int main()
{
    std::string people_str;
    // 对联系人进行 序列化 输出
​
    {
        
        contacts::PeopleInfo people;
        people.set_name("C++");
        people.set_age(74);
​
        if(!people.SerializeToString(&people_str))
        {
            std::cerr<<" -- 反序列化失败 -- " << std::endl;
            return -1;
        }
        std::cout << " -- 序列化成功 -- :" << people_str << std::endl; 
    }
​
    std::cout << "/////////////////////////////////////////////////\
///////////////////////////////" << std::endl;
​
    // 对联系人进行 反序列化 输出
    {
        contacts::PeopleInfo people;
        if(!people.ParseFromString(people_str)){
            std::cerr << " --- 反序列化失败 --- " << std::endl; 
            return -1;
        }
        std::cout << " --- 反序列化成功 --- " << std::endl
                  << "  -- 姓名 : " << people.name() << std::endl
                  << "  -- 年龄 : " << people.age()  << std::endl;
​
    }
}

-- 序列化成功 -- :

C++J

/////////////////////////////////////////////////////////////////

--- 反序列化成功 ---

-- 姓名 : C++

-- 年龄 : 74

ProtoBuf 相对于xml 和 Json 来说 被编译成二进制 破解成本增大 ,protobuf是相对安全

字段规则

  • singular: 消息中课包含 该字段的0次或1次

  • repeated 消息中可以包含字段的任意次数 0次数 或者多次重复值的数据会被保留,可以理解为定义一个数组

相关接口 [ProtoBuf]

SerializeToOstreamParseFromIstream

这两个函数用于处理 Protobuf 数据的序列化和反序列化:

ParseFromIstream: contacts.ParseFromIstream(&input) 从输入流读取二进制数据,并将其反序列化为内存中的 contacts 对象。

add_contacts 方法:

  • 用途:用于向Contacts消息中添加一个新的PeopleInfo消息。

  • 用法:AddPeopleInfo(contacts.add_contacts()); 这行代码创建了一个新的PeopleInfo消息,并将其添加到contacts对象的contacts字段中。

  • 调用 contacts.add_contacts() 时,它会在 contacts 列表中创建一个新的 PeopleInfo 对象,并返回这个对象的指针(PeopleInfo* 类型的指针)。

    这个指针可以用来直接修改刚刚创建的 PeopleInfo 对象的内容。

二进制 转换 Ascii 码 工具 Hexdump

protoc -h 可以查看 protoc 命令的选项

--decode 查看二进制文件中的内容

使用方法

protoc --decode=contacts.Contacts contacts.proto < contacts.bin

🤓☝️ protoc --decode=contacts.Contacts  contacts.proto  < contacts.bin 
contacts {
  name: "reschen"
  age: 999
  phone {
    number: "111111111111111111"
  }
  phone {
    number: "111111111111111111111111"
  }
  phone {
    number: "3333333333333333333333333333"
  }
}

在反序列化的时候 枚举类型的默认值为 默认开始的枚举

Any 类

any类型 是一种 泛型类型

any接口

  • has_data():这是一个检查方法,用于确定 Any 类型是否包含数据。

  • Is<MessageType>():这是一个类型检查方法,它接受一个模板参数 MessageType,这个模板参数应该是一个 Protocol Buffers 消息类型。如果 Any 对象中存储的消息类型与 MessageType 匹配,那么 Is<MessageType>() 方法会返回 true,否则返回 false

  • PackFrom() 方法用于将一个 protobuf 消息打包到 Any 类型中

    any_type.mutable_data()->PackFrom(address);

  • UnpackTo() 方法用于从 Any 类型中提取消息。

其他

Protobuf 中 any 的妙用 - ExplorerMan - 博客园