1. 认识ProtoBuf
1.1 序列化概念
序列化和反序列化
- 序列化:把对象转换为字节序列(字符串)的过程称为对象的序列化。
- 反序列化:把字节序列恢复为对象的过程称为对象的反序列化。
例如上图中有一个User结构体,将u1对象转化成str字符串就是序列化,将str序列化转化成u1就是反序列化。
什么情况下需要序列化
- 存储数据:当你想把的内存中的对象状态保存到一个文件中或者存到数据库中时。
- 网络传输:网络直接传输数据,但是无法直接传输对象,所以要在传输前序列化,传输完成后反序列化成对象。例如我们之前学习过socket编程中发送与接收数据。
如何实现序列化
xml、json、protobuf
总结:protobuf是实现序列化与反序列化的一种手段。
1.2 ProtoBuf是什么
Protocol Buffers(全称为Protocol Buffer)是Google的一种语言无关、平台无关、可扩展的序列化结构数据的方法,它可用于(数据)通信协议、数据存储等。
Protocol Buffers相比于XML,是一种灵活,高效,自动化机制的结构数据序列化方法,但是比XML更小、更快、更为简单。
你可以定义数据的结构,然后使用特殊生成的源代码轻松的在各种数据流中使用各种语言进行编写和读取结构数据。你甚至可以更新数据结构,而不破坏由旧数据结构编译的已部署程序。
简单来讲,ProtoBuf是让结构数据序列化的方法,其具有以下特点:
- 语言无关、平台无关:即ProtoBuf支持Java、C++、Python等多种语言,支持多个平台。
- 高效:即比XML更小、更快、更为简单。
- 扩展性、兼容性好:你可以更新数据结构,而不影响和破坏原有的旧程序。
1.3 ProtoBuf如何使用
1. 编写一个.proto文件,目的是为了定义结构对象(message)及属性内容。
2. 使用protoc编译器编译.proto文件,生成一系列接口代码,存放在新生成头文件和源文件中。
3. 依赖生成的接口,将编译生成的头文件包含进我们的代码中,实现对.proto文件中定义的字段进行设置和获取,和对message对象进行序列化和反序列化。
总的来说:ProtoBuf是需要依赖通过编译生成的头文件和源文件来使用的。有了这种代码生成机制,开发人员再也不用编写那些协议解析的代码了。
2. 安装ProtoBuf
2.1 Windows平台下
下载地址:Releases · protocolbuffers/protobufReleases · protocolbuffers/protobufReleases · protocolbuffers/protobuf
以安装v21.11版本为例,找到v21.11版本,点击。
选择win64版本。
点击之后即可自动下载,下载完毕之后解压,解压完毕之后打开该文件夹,可以看到有一个bin文件夹和一个include文件夹。
我们将bin目录的地址复制下来。
打开环境变量配置,双击系统变量中的Path
然后再点击新建,将刚才复制的地址填进去,最后点击确定即可。
现在就已经安装完成了,我们使用cmd查看是否安装成功。
输入protoc --version,如果能正确显示版本则说明安装成功。
2.2 Linux平台下
下载ProtoBuf前要安装依赖库:autoconf automake libtool curl make g++ unzip如未安装,安装命令如下:
Ubuntu用户选择:
sudo apt-get install autoconf automake libtool curl make g++ unzip -y
Centos用户选择:
sudo yum install autoconf automake libtool curl make gcc-c++ unzip -y
下载完之后就可以正式来安装ProtoBuf了,下载地址:ProtoBuf下载
同样,还是以21.11版本演示:
- 如果要在C++下使用ProtoBuf,可以选择cpp.zip;
- 如果要在JAVA下使用ProtoBuf,可以选择java.zip;
- 其他语言选择对应的链接即可。
- 希望支持全部语言,选择all.zip。
在这里我们希望支持全部语言,所以选择protobuf-all-21.11.zip,右键将下载链接复制出来。
下载命令:
wget https://github.com/protocolbuffers/protobuf/releases/download/v21.11/protobuf-all-21.11.zip
下载完成后,解压zip包:
unzip protobuf-all-21.11.zip
解压完成后,会生成protobuf-21.11文件,进入文件:
cd protobuf-21.11
内容如下:
进入解压好的文件,执行以下命令:
1)第一步执行autogen.sh,但如果下载的是具体的某一门语言,不需要执行这⼀步
./autogen.sh
2)第二步执行configure,有两种执行方式,任选其一即可,如下:
1、protobuf默认安装在 /usr/local 目录,lib、bin都是分散的
./configure
2、修改安装目录,统一安装在/usr/local/protobuf下
./configure --prefix=/usr/local/protobuf
再依次执行:
make
make check
sudo make install
注意:在执行make check过程中可能会出现如下问题
出现以上错误的原因是test的模块里面有非常多的测试用例,这些用例对服务器环境要求特别严格,需要增大下swap分区,具体操作可参考:Ubuntu 18.04 swap分区扩展_ubuntu18.04 如何查看swapfile文件路径-优快云博客
(建议可以先扩大3G,再执行make check 。如果还是报错,再扩大到5G重新执行make check )
执行make check 后,出现以下内容就可以执行sudo make install。
到此,需要你回忆⼀下在执行configure时,如果当时选择了第一种执行方式,也就是./configure ,那么到这就可以正常使用protobuf了。如果选择了第⼆种执行方式,即修改了安装⽬录,那么还需要在/etc/profile中添加⼀些内容:
sudo vim /etc/profile
将下面内容添加进去
# 添加内容如下:
#(动态库搜索路径) 程序加载运⾏期间查找动态链接库时指定除了系统默认路径之外的其他路径
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/protobuf/lib/
#(静态库搜索路径) 程序编译期间查找动态链接库时指定查找共享库的路径
export LIBRARY_PATH=$LIBRARY_PATH:/usr/local/protobuf/lib/
#执⾏程序搜索路径
export PATH=$PATH:/usr/local/protobuf/bin/
#c程序头⽂件搜索路径
export C_INCLUDE_PATH=$C_INCLUDE_PATH:/usr/local/protobuf/include/
#c++程序头⽂件搜索路径
export CPLUS_INCLUDE_PATH=$CPLUS_INCLUDE_PATH:/usr/local/protobuf/include/
#pkg-config 路径
export PKG_CONFIG_PATH=/usr/local/protobuf/lib/pkgconfig/
最后⼀步,重新执行/etc/profile文件:
source /etc/profile
检查是否安装成功
输入protoc --version查看版本,有显示说明安装成功。
3. 快速上手
在快速上手中,会编写第一版本的通讯录1.0。在通讯录1.0版本中,将实现:
- 对⼀个联系人的信息使用ProtoBuf进行序列化,并将结果打印出来。
- 对序列化后的内容使用PB进行反序列,解析出联系人信息并打印出来。
- 联系人包含以下信息:姓名、年龄。
通过通讯录1.0,我们便能了解使用ProtoBuf初步要掌握的内容,以及体验到ProtoBuf的完整使用流程。
3.1 编写contacts.proto文件
在编写contacts.proto文件时需要注意以下几点
1)文件规范
- 创建.proto文件时,文件命名应该使用全小写字母命名,多个字母之间用_连接。例如:lower_snake_case.proto。
- 书写.proto文件代码时,应使用2个空格的缩进。
我们为通讯录1.0新建文件:contacts.proto
2)添加注释
向文件添加注释,可使用 // 或者 /* ... */
3)指定proto3语法
在.proto文件中,要使用 syntax = "proto3"; 来指定文件语法为proto3,并且必须写在除去注释内容的第一行。如果没有指定,编译器会使用proto2语法。
在通讯录1.0的contacts.proto文件中,可以为文件指定proto3语法,内容如下:
syntax = "proto3";
4)package声明符
package是一个可选的声明符,能表示.proto文件的命名空间,在项目中要有唯一性。它的作用是为了避免我们定义的消息出现冲突。当设置了package之后,通过ProtoBuf编译器得到的.c和.h文件中的对象就是保存在该命名空间下。
在通讯录1.0的contacts.proto文件中,可以声明其命名空间,内容如下:
syntax = "proto3";
package contacts;
5)定义消息(message)
消息(message):要定义的结构化对象,我们可以给这个结构化对象中定义其对应的属性内容(类似于C语言中的结构体)。
所以ProtoBuf就是以message的方式来支持我们定制协议字段,后期帮助我们形成类和方法来使用。在通讯录1.0中我们就需要为联系人定义⼀个message,其中包括姓名和年龄字段,内容如下:
syntax = "proto3";
package contacts;
// 定义联系⼈消息
message PeopleInfo
{
}
6)定义消息字段
在message中我们可以定义其属性字段,字段定义格式为:字段类型字段名=字段唯⼀编号;
• 字段名称命名规范:全小写字母,多个字母之间用_连接。
• 字段类型分为:标量数据类型和特殊类型(包括枚举、其他消息类型等)。
• 字段唯一编号:用来标识字段,一旦开始使用就不能够再改变。
该表格展示了定义于消息体中的标量数据类型,以及编译.proto文件之后自动生成的类中与之对应的字段类型。在这里展示了与C++语言对应的类型。
[1]变长编码是指:经过protobuf编码后,原本4字节或8字节的数可能会被变为其他字节数。
更新contacts.proto(通讯录1.0),新增姓名、年龄字段:
//指定proto3语法
syntax = "proto3";
//声明命名空间
package contacts;
//创建命为PeopleInfo的message对象
message PeopleInfo
{
string name = 1; //姓名
int32 age = 2; //年龄
}
注意这里的 = 号并不是赋值的意思,而是给他们编号
在这里还要特别讲解一下字段唯一编号的范围:1~536,870,911(2^29-1),其中19000~19999不可用。
19000~19999不可用是因为:在Protobuf协议的实现中,对这些数进行了预留。如果非要在.proto文件中使用这些预留标识号,例如将name字段的编号设置为19000,编译时就会报警。
值得一提的是,范围为1~15的字段编号需要⼀个字节进行编码,16~2047内的数字需要两个字节进行编码。编码后的字节不仅只包含了编号,还包含了字段类型。所以1~15要用来标记出现非常频繁的字段,要为将来有可能添加的、频繁出现的字段预留⼀些出来。
3.2 编译contacts.proto文件,生成C++文件
编译的命令行格式为:
protoc [--proto_path=IMPORT_PATH] --cpp_out=DST_DIR path/to/file.proto
- protoc:Protocol Buffer 提供的命令行编译工具。
- --proto_path:指被编译的.proto文件所在目录,可多次指定。可简写成 -I。当某个.proto文件 import 其他.proto文件时,或需要编译的.proto文件不在当前目录下,这时就要用-I来指定搜索目录。
- IMPORT_PATH :如不指定该参数,则在当前目录进行搜索。
- --cpp_out= 指编译后的文件为 C++ ⽂件。
- OUT_DIR 编译后生成文件的目标路径。
- path/to/file.proto 要编译的.proto文件
编译contacts.proto⽂件命令如下:
protoc --cpp_out=. contacts.proto
编译完成之后
编译后生成了两个文件: contacts.pb.h contacts.pb.cc 。
对于编译生成的C++代码,包含了以下内容:
1)对于每个message,都会生成一个对应的消息类。
2)在消息类中,编译器为每个字段提供了获取和设置方法,以及⼀下其他能够操作字段的方法。
- 1) 其中的name()和age()会返回PeopleInfo对象中name和age的值
- 2) set_name()和set_age()用于设置PeopleInfo对象中的name和age的值
- 3) clear_name()和clear_age()用于清除name和age的值
- 4) mutable_xxx() :获取当前字段的地址,对于自定义类型来说protoc不会为其生成set_xxx()的方法来设置值,因为它没办法预测用户到底想如何给自定义类型设置值,但是它会生成对应的mutable_xxx()函数,利用mutable_xxx()函数我们可以获取到当前字段的地址,然后再利用字段的内置set_xxx()方法进行对自定义类型中的各个字段进行初始化,对于protobuf内置的类型protoc不会为其生成mutable_xxx()方法,当然string等除外,这些基本类型利用set_xxx()方法就能够设置值了。
3)编辑器会针对于每个 .proto 文件生成 .h 和 .cc 文件,分别用来存放类的声明与类的实现。
序列化和反序列化的代码放在消息类的父类 MessageLite 中,提供了读写消息实例的放法,包括序列化方法和反序列化方法。
.proto中各个元素对应生成的代码关系:
在查看.pb.h文件时,你可以看到PeopleInfo对象,以及其中的name和age字段,但是最重要的序列化和反序列化代码却找不到。
其中protobuf是通过继承的方式:让PeopleInfo继承Message对象,Message对象继承MessageLite对象,而MessageLite对象中包含很多序列化和反序列化的函数。