ProtoBuf提供对数据的序列化和反序列化,ProtoBuf可以用于结构化数据的串行序列化,并且以Key-Value格式存储数据,因为采用二进制格式,所以序列化出来的数据比较少,作为网络传输的载体效率很高

参考资料

安装

源码下载与安装

1
2
3
4
5
6
7
8
9
# 以 protobuf 3.21.12 为例
# 自行下载源码包, 解压缩
$ tar zxvf protobuf-cpp-3.21.12.tar.gz
# 进入到解压目录
$ cd protobuf-3.21.12/
# 构建并安装
$ ./configure # 检查安装环境, 生成 makefile
$ make # 编译
$ sudo make install # 安装

动态库链接失败处理

可以使用$ protoc --version测试是否安装成功

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 测试时发现动态库链接失败
$ protoc --version
protoc: error while loading shared libraries: libprotoc.so.32: cannot open shared object file: No such file or directory

# 方法1:
# find指令查找动态库路径
$ sudo find /usr/local/ -name libprotoc.so
/usr/local/lib/libprotoc.so

# 将查找到的目录添加到/etc/ld.so.conf配置文件中:
$ sudo vim /etc/ld.so.conf
# 尾部追加/usr/local/lib/

# 方法2
# 直接使用ldconfig指令
sudo ldconfig

# 处理完成后:
$ protoc --version
libprotoc 3.21.12

快速上手

使用流程

  1. 确定数据格式,数据可简单可复杂,比如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    // 要序列化的数据
    // 第一种: 单一数据类型
    int number;

    // 第二种: 复合数据类型
    struct Person
    {
    int id;
    string name;
    string sex;
    int age;
    };
  2. 创建一个新的文件, 文件名随意指定, 文件后缀为 .proto

  3. 根据protobuf的语法, 编辑.proto文件

  4. 使用 protoc 命令将 .proto 文件转化为相应的 C++ 文件

    • 源文件: xxx.pb.cc –> xxx对应的名字和 .proto文件名相同
    • 头文件: xxx.pb.h –> xxx对应的名字和 .proto文件名相同
  5. 需要将生成的c++文件添加到项目中, 通过文件中提供的类 API 实现数据的序列化/反序列化

    Protobuf与C++的类型对照

  • 几个主要注意点:
    • C++中的string到Protobuf中统一使用bytes比较好
    • C++中的整型到Protobuf中为 int+size,如int→int32
    • C++中的结构体与类对应到Protobuf中为message(消息体)
      • Protobuf中其他类型最终也都要封装到消息体里

image.png

基本使用

1
2
3
4
5
6
7
8
syntax = "proto3";

message Person{
int32 id = 1;
bytes name = 2;
bytes sex = 3;
int32 age = 4;
}

生成对应的.h和.c文件

1
2
# protoc <proto文件路径> --cpp_out=<输出路径>
$ protoc Person.proto --cpp_out=./

repeated关键字

  • repeated标志对应的成员是动态数组
1
2
3
4
5
syntax = "proto3";

message Person{
repeated int32 id = 1;
}

枚举

  • proto3 中的第一个枚举值必须为 0,第一个元素以外的元素值可以随意指定
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 定义枚举类型
enum Color
{
Red = 0;
Green = 3; // 第一个元素以外的元素值可以随意指定
Yellow = 6;
Blue = 9;
}
// 在该文件中对要序列化的结构体进行描述
message Person
{
int32 id = 1;
repeated bytes name = 2;
bytes sex = 3;
int32 age = 4;
// 枚举类型
Color color = 5;
}

proto文件的导入

  • 使用import语句在当前.ptoto中导入其它的.proto文件。这样就可以在一个.proto文件中引用并使用其它文件中定义的消息类型和枚举类型。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
syntax = "proto3";
// 使用另外一个proto文件中的数类型, 需要导入这个文件
import "Address.proto";

// 在该文件中对要序列化的结构体进行描述
message Person
{
int32 id = 1;
repeated bytes name = 2;
bytes sex = 3;
int32 age = 4;

// 添加地址信息, 使用的是外部proto文件中定义的数据类型
Address addr = 6;
}

包(package)

  • 在 Protobuf 中,可以使用package关键字来定义一个消息所属的包(package)。包是用于组织和命名消息类型的一种机制,类似于命名空间的概念
  • 在一个.proto文件中,可以通过在顶层使用package关键字来定义包:
1
2
3
4
5
6
7
8
9
10
syntax = "proto3";
// 添加命名空间 Dabing
package Dabing;

// 地址信息, 这个Address类属于命名空间: Dabing
message Address
{
bytes addr = 1;
bytes number = 2;
}
  • 其他.proto文件导入以上文件后,可以通过Dabing.Address使用该文件定义的消息体Address
  • .c文件使用该proto文件生成的对象时,通过Dabing::Address使用该文件定义的消息体

Protobuf API

通过对象调用Protobuf API

成员操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 获取成员的const版本(数组成员需要传入索引)
<成员名>()

// 获取成员的可修改版本(数组成员需要传入索引)
mutable_<成员名>()

// 设置成员(数组成员需要传入索引)
set_<成员名>()

// 清空成员
clear_<成员名>()

// 获取数组成员的size
<成员名>_size()

// 向数组中添加成员
add_<成员名>()

序列化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 头文件目录: google\protobuf\message_lite.h
// --- 将序列化的数据 数据保存到内存中
// 将类对象中的数据序列化为字符串: C++风格的字符串, 参数是一个传出参数
bool SerializeToString(std::string* output) const;
// 将类对象中的数据序列化为字符串: C风格的字符串, 参数 data 是一个传出参数
bool SerializeToArray(void* data, int size) const;

// ------ 写磁盘文件, 只需要调用这个函数, 数据自动被写入到磁盘文件中
// -- 需要提供流对象/文件描述符关联一个磁盘文件
// 将数据序列化写入到磁盘文件中, c++ 风格
// ostream 子类 ofstream -> 写文件
bool SerializeToOstream(std::ostream* output) const;
// 将数据序列化写入到磁盘文件中, c 风格
bool SerializeToFileDescriptor(int file_descriptor) const;

反序列化

1
2
3
4
5
6
7
8
// 头文件目录: google\protobuf\message_lite.h
bool ParseFromString(const std::string& data) ;
bool ParseFromArray(const void* data, int size);
// istream -> 子类 ifstream -> 读操作
// wo ri
// w->写 o: ofstream , r->读 i: ifstream
bool ParseFromIstream(std::istream* input);
bool ParseFromFileDescriptor(int file_descriptor);