1.ProtoBuf概述

protobuf也叫protocol buffer,是google的一种数据交换的格式,它独立于语言,独立于平台。google提供了多种语言的实现,如java、c#、c++、go和python等,每一种实现都包含了相应语言的编译器以及库文件。

1.作用:

  • 数据的持久化存储

    • 序列化:将数据从内存存储到磁盘
    • 反序列化:将数据从磁盘加载到内存
  • 数据的网络传输:客户端与服务端之间的数据传输

    • 序列化:对象到字节序列
    • 反序列化:字节序列到对象

2.特点

  • 语言无关、平台无关:ProtoBuf支持java、c++、python等多种语言、支持多个平台
  • 高效:比XML更小、更快、更为简单
  • 扩展性、兼容性好:可以更新数据结构,而不影响和破坏原有的旧程序

3.使用特点(c++):ProtoBuf是需要依赖通过编译生成的头文件和源文件来使用的。

比如说对于常规发送消息,首先是需要定义一个Class类,在该类里面有:(1)一些列属性字段(要发送的)、(2)处理字段的方法、(3)处理类的方法(序列化、反序列化)。可以发现(2)和(3)是非常耗时,且对于开发者来说比较伤脑筋的,则通过protobuf就可以避免这些问题。

2.ProtoBuf在window下的使用

1.安装

1.下载地址:Releases · protocolbuffers/protobuf

2.下载对应的压缩包,解压后在bin文件夹下有一个protoc.exe

3.在该文件夹下打开终端,执行protoc –version,打印对应版本即安装成功

4.为方便使用,将protoc.exe的目录添加到环境变量中

2.使用

1.定义消息字段

在message中我们可以定义其属性字段,字段定义格式为:字段类型 字段名=字段唯一编号;

  • 字段名称命名规范:全小写字母,多个字母之间用下划线_连接
  • 字段类型分为:标量数据类型特殊类型(包括枚举、其他消息类型等)
  • 字段唯一编号:用来标识字段,一旦开始使用就不能够再改变

该表格展示了定义消息体中的标量数据类型,以及编译.proto文件之后自动生成的类中与之对应的字段类型。再这里展示了与c++语言对应的类型。

.proto type Notes c++ Type
double double
float float
int32 使用变长编码,负数的编码效率较低,若字段可能为负值,应使用sint32代替 int32
int64 使用变长编码,负数的编码效率较低,若字段可能为负值,应使用sint64代替 int64
uint32 使用变长编码 uint32
uint64 使用变长编码 uint64
sint32 使用变长编码,符号整型,负值的编码效率高于常规的int32类型 int32
sint64 使用变长编码,符号整型,负值的编码效率高于常规的int64类型 int64
fixed32 定长4字节,若值常大于2^28,则会比uint32更高效 uint32
fixed64 定长8字节,若值常大于2^56,则会比uint64更高效 uint64
sfixed32 定长4字节 int32
sfixed64 定长8字节 int64
bool bool
string 包含UTF-8和ASCII编码的字符串,长度不能超过2^32 string
bytes 可包含任意的字节序列,但长度不能超过2^32 string

注意:变长编码是指经过protobuf编码后,原本4字节或8字节的数可能会被变为其他字节数。

2.字段唯一编号的范围:1-536870911(2^29-1),其中19000-19999不可用

19000-19999不可用的原因:在protobuf协议的实现中,对这些数进行了预留。如果非要在.proto文件中使用这些预留标识号,编译时就会报警

值得一提的是,范围为1-15的字段编号需要一个字节进行保存编码(只是指编号占的空间,不是指一整条字段),16-2047内的数字需要两个字节进行保存编码。所以1-15要用来标记出现非常频繁的字段,要为将来有可能添加的、频繁出现的字段预留一些出来。

3.实践

1.创建一个文件contacts.proto,在该文件内编写以下内容

1
2
3
4
5
6
7
8
9
//首行:语法指定行
syntax = "proto3";
package contacts;

//定义联系人message
message PeopleInfo{
string name = 1; //姓名
int32 age = 2; //年龄
}

2.在该文件的目录下打开终端执行:protoc --cpp_out=. contacts.proto

注意:编译contacts.proto文件后会发生什么?

编译contacts.proro文件后,会发生所选择语言的代码,如果选择的是c++,那么编译后生成两个文件:contacts.pb.h、contacts.pb.cc。

对于编译生成的c++代码,包含了以下内容:

  • 对于每个meaaage,都会生成一个对应的消息类
  • 在消息类中,编译器为每个字段提供了获取和设置方法,以及一些其它能够操作字段的方法
  • 编译器会针对于每个.proto文件生成.h和.cc文件,分别用来存放类的声明与类的实现

特别的,对于处理类的方法(序列化、反序列化)并不在PeopleInfo里面,而是在它的父类里面定义的。

序列化的结果为二进制字节序列,而非文本格式(json)。

3.编写main.cc文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <iostream>
#include "contacts.pb.h"

int main(){
std::string people_str;

{
//对一个联系人的信息使用PB进行序列化,并将结果打印出来
contacts::PeopleInfo people; //通过bao(命名空间)定义一个对象
people.set_name("lxx");
people.set_age("25");
if(!people.SerializeToString(&people_str)){ //序列化
std::cerr<<"序列化联系人失败!"<<std::endl;
return -1;
}
std::cout<<"序列化成功,结果:"<<people_str<<std::endl; //打印出的是乱码
}

{
//对序列化后的内容使用PB进行反序列化,解析出联系人信息并打印出来
contacts::PeopleInfo people;
if(!people.ParseFromString(people_str)){ //反序列化
std::cerr<<"反序列化联系人失败!"<<std::endl;
return -1;
}
std::cout<<"反序列化成功,结果:"<<people_str<<std::endl
<<"姓名:"<<people.name()<<std::endl
<<"年龄:"<<people.age()<<std::endl;
}
return 0;
}

在终端输入:g++ -o TestPb main.cc contacts.pb.cc -std=c++11 -lprotobuf

  • 生成TestPb可执行文件
  • -lprotobuf:加入要使用的库,不加会有链接错误
  • -std=c++11:要使用c++11的语法

总结:相对于XML和JSON来说,因为被编码成二进制,破解成本增大,Protobuf编码是相对安全的