Protocol buffers 简称pb,是google开发的一个可序列化、反序列化object的数据交换格式,类似于xml,但是比xml更轻、更快、更简单。而且以上的重点突出一个跨平台,和xml、json等数据序列化一样,跨平台跨语言。
目前已更新到3.x.x版本,正在使用的还有2.x.x版本。
一、特点
1.使用.proto
格式文件描述数据层级结构。
2.protobuf编译器会根据.proto
文件的描述自动创建一个class编码转换成指定的数据结构,同时生成的类会自动提供 数据转换的getter和setter方法,完成一个protocol buffer的内容的组成或是读写。
3.protobuf格式支持格式扩展兼容,使用旧的proto协议编码仍可以读取使用了新协议编码的数据,当然更新的新协议也是可以兼容之前proto的定义。
二、proto介绍
XXX.proto
文件模板举例
package tutorial;
message Person {
required string name = 1;
required int32 id = 2;
optional string email = 3;
enum PhoneType {
MOBILE = 0;
HOME = 1;
WORK = 2;
}
message PhoneNumber {
required string number = 1;
optional PhoneType type = 2 [default = HOME];
}
repeated PhoneNumber phone = 4;
}
message AddressBook {
repeated Person person = 1;
}
1. 文件基础
.proto
文件开头声明使用的语法proto2或proto3:syntax = "proto2";
如果不声明默认会使用proto2语法.proto
文件必须以package xxx;
声明开头(语法声明之后位置),作为协议唯一的标识,避免不同的命名冲突- Python本身在做关系调用的时候,是作为一种正常的索引结构调用的,也就相当于将编译后的
xxx_pb.py
结尾的文件认为是普通的python脚本使用,所以在proto中定义的package在编译过程中是没有用到package的,但是避免其他语言使用的时候出现命名冲突,最好还是在文件开头做好声明。
2. message
- 一个
message
相当于一个指定类型的集合,像bool/int32/float/double/string
这些类型都可以直接使用在proto协议中的某个message里,用作指定数据类型。 - 而且一个message可以直接嵌套另一个message使用,被嵌套的message就相当于string,被认为是一种数据类型。
3. message中的编号和字段
- message里标记1/2/3的数字,作用是标识每种类型元素在当前二进制编码中的专属tag,不可以重复
- 编码的时候1-15的tag数字标签为一个bytes,16及以上的tag为两个bytes,所以设计message的时候尽量优化当前message数据结构,尽量给常用的或者使用过程中repeated比较多的数据使用1-15的tag数字,后续编码转换过程中可以保持最优状态
- 每个message的字段必须要声明是
required|repeated|optional
,需要关注的是,一旦定义字段为required,后面就不能再修改,否则可能引起很多问题:- 新编辑的proto内容,不可以修改已存在的filed的tag值
- 不能添加和删除任何required的filed
- 新添加的filed要使用新的tag
- 遵循以上原则才可以做到新旧版本message相互解析
- required字段:一定要提供一个值给该字段,否则这条message会被认为“没有初始化”。序列化没有初始化的message会出现异常。解析一条没有初始化的message会失败。之外,这个required字段的行为更类似一个optional字段。
- optional字段:该字段可以设置也可以不设置。如果一个可选字段没有设置值,会用缺省的值。系统对缺省的值定义是为0给整数类型,空串给字符串类型,False给布尔类型。optional类型的filed最好在tag编号后面和
;
前面给一个默认值[default = value]
,当然可以不指定,默认为空。 - repeated字段:该字段会重复几次一些号码(包括0)。重复的值按顺序保存在protocl buffer中。重复的字段会被认为是动态的数组。
PS:required is forever,你应该非常小心地字段标记为required。如果在某一刻你希望停止写或发送一个必填字段,那就把不确定的字段更改为一个可选的字段---旧的阅读器会认为没有这个字段message是不完整的,而且可能会无意中拒绝或删除它们。你应该考虑为你的buffer编写特定于应用程序的自定义验证流程。某些来自Google的结论:使用required弊大于利;他们更愿意只用optional和repeated。
4. message方法
message类含有一些检查或操作整个message的方式,如下:
Isinitialized()
:检查是否所有required域都已赋值。str()
:返回message的可读形式,可以通过str(message)或者print message触发,用于调试代码。Clear()
:将所有域的赋值清空。MergeFrom(other_msg)
:将给定的other_msg的内容合并到当前message,独立的域使用other_msg的值覆盖写入,repeated域的内容append到当前message的对应字段。独立的子message和group被递归的合并。CopyFrom(other_msg)
:先对于message调用Clear()方法,再调用MergeFrom(other_msg)。MergeFromString(serialized)
:将PB二进制字符解析后合并到本message,合并规则与MergeFrom
方法一致。ListFields()
:以(google.protobuf.descriptor.FieldDescriptor,value)的列表形式返回非空的域,独立的域如果HasField返回True则是非空的,repeated域至少包含一个元素则是非空的。ClearField(field_name)
:清空某个域,如果被清空的域名不存在,抛出ValueError异常。ByteSize()
:返回message占用的空间大小。WichOneof(oneof_group)
:返回oneof组中被设置的域的名字或None,如果提供的oneof的组名不存在,抛出ValueError异常。
5. Enums
Enums是由metaclass扩展成一组具有符号常量的整数值。
6. 序列化和解析
每个message类都有序列化和解析方法:
SerializeToString()
:将message序列化并返回str类型的结果。(str类型只是二进制数据的一个容器而已,不是文本内容)。如果message没有初始化,跑出message.EncodeError异常。SerializePartialToString()
:将message序列化并返回str类型的结果,但不检查message是否初始化。ParseFromString(data)
:从给定的二进制str解析得到message对象。
PS:如果要在生成的PB类的基础上增加新功能,应采用包装(wrapper)的方式,永远不要将PB类作为基类派生子类添加新功能。
三、安装
# ===Linux 系统===
# 1.进入官网,下载对应系统的压缩包
# 2.解压包到自定义目录:
tar -zxvf protobuf-3.12.0.tar.gz
# 3.进入protobuf文件夹,进行配置,并make,命令如下:
cd protobuf-3.12.0
./configure
make && make install
# 4.make成功后再进行安装,进入刚解压的protobuf下的python目录进行安装(若安装VirtualEnv注意切换Python环境)
cd python
python setup.py build
python setup.py install
python setup.py test
# 5.完成
# ===Windows 系统===
'''注意:系统上要先装好python
windows系统需下载两个包,分别为:
(1) potoc-3.12.0-win64.zip(包含编译工具protoc.exe,protoc.exe需放在C:\protobuf-python-3.12.0\protobuf-3.12.0\src 和 C:\Windows\System32下,据说这样能包治百bug)
(2) protobuf-python-3.12.0.zip(包含protobuf与语言python之间的protobuf运行时的库,转换时需要,相当于protobuf与各语言之间的协定格式。解压放在C盘根目录下)
'''
# 1.进入官网,下载对应系统的两个压缩包(参考上面的说明)
# 2.解压,根据路径配置好文件
protobuf-python-3.12.0.zip 解压---> C:\
potoc-3.12.0-win64.zip 解压---> 任意
复制potoc-3.12.0-win64.zip解压出来的protoc.exe 到 C:\protobuf-python-3.12.0\protobuf-3.12.0\src 和 C:\Windows\System32下
# 3.打开cmd开始安装,cmd切换到C:\protobuf-python-3.12.0\protobuf-3.12.0\Python目录下,依次执行下列命令
python setup.py build
python setup.py test
python setup.py install
# 4.完成
四、使用
接下来我们通过一个大栗子来演示一遍上面枯燥的文字。还是文章开头的模板。
1. 创建一个addressbook.proto
文件
// proto2的语法声明,可加可不加,目前已到proto3版本
syntax = "proto2";
// 包名称
package tutorial;
// 定义message字段类型
message Person {
// 基本信息
required string name = 1;
required int32 id = 2;
optional string email = 3;
// 枚举类型
enum PhoneType {
MOBILE = 0;
HOME = 1;
WORK = 2;
}
// 定义嵌套的message
message PhoneNumber {
required string number = 1;
optional PhoneType type =2[default = HOME];
}
//多笔PhoneNumber
repeated PhoneNumber phones = 4;
}
message AddressBook {
// 多笔Person
repeated Person people = 1;
}
2. 编译addressbook.proto
写好.proto
文件后下来就可以进行编译,以便在python上使用。
Windows是cmd命令行,linux和mac直接用命令行输入,编译完成后会生成一个叫addressbook.bp2.py的文件。
命令如下:
protoc -I=$SRC_DIR --python_out=$DST_DIR $SRC_DIR/addressbook.proto
# -I :大写的I是input,意为需要编译的.proto文件路径,可以是当前路径(. 或 ./)或绝对路径
# --python_out:编译生成的xxx.bp2.py文件的保存路径。
# $SRC_DIR/addressbook.proto :最后写需要用源目标的哪个proto文件进行编译。
addressbook.bp2.py文件内容比较多,我们就不展示了,可以打开进去看看。
无论生成的是java或是C++的protocol buffer代码,python protocol buffer编译器都不会生成可以直接访问数据的代码。就是你看到的addressbook_pb2.py里的内容那样。它会为你的message、枚举、字段生成指定的描述符,还有一些不好理解的空类,其中一段为:
class Person(message.Message):
__metaclass__ = reflection.GeneratedProtocolMessageType
class PhoneNumber(message.Message):
__metaclass__ = reflection.GeneratedProtocolMessageType
DESCRIPTOR = _PERSON_PHONENUMBER
DESCRIPTOR = _PERSON
class AddressBook(message.Message):
__metaclass__ = reflection.GeneratedProtocolMessageType
DESCRIPTOR = _ADDRESSBOOK
每个类中都有一些重要的语句:__metaclass__ = reflection.GeneratedProtocolMessageType
。可以看做是创建类的模板。加载时,GeneratedProtocolMessageType metaclass
会用python的方式指定描述符创建时需要用的message类型和添加和这些方法相关的类。然后就可以在代码中使用这些类。
3. 调用 addressbook.bp2.py文件
3.1 Pycharm的使用方法之一:
把addressbook.bp2.py文件直接复制或鼠标拖到pycharm的当下project目录下,然后鼠标放在pycharm当前project目录上右键依次:make_directory as ---> sources path ,将当前工作的文件夹加入source_path,就可以使用了。
3.2 数据的序列化与解析
- 调用Protocol buffer序列化数据
#!/usr/bin/env python3
# coding:utf-8
import addressbook_pb2
import sys
# 定义 Protocol Buffer 文件
my_pb_file = "my_addr_book.pb"
# 创建 Addressbook
address_book = addressbook_pb2.AddressBook()
# 增加一条person信息
person = address_book.people.add()
# 设定person基本信息
person.id = 123
person.name = "G.T.Wang"
person.email = "GTWANG@gamil.com"
# 增加第一个类型的电话
phone_number = person.phones.add()
phone_number.number = "0912=345678"
phone_number.type = addressbook_pb2.Person.MOBILE
# 增加第二个类型的电话
phone_number = person.phones.add()
phone_number.number = "06-1234567"
phone_number.type = addressbook_pb2.Person.WORK
# 写入AddressBook
with open(my_pb_file,'wb') as f:
f.write(address_book.SerializeToString())
注意:通过protocol buffer生成的是二进制文档,所以上面例子最后写入文件的时候要用bytes类型。
2)读取和解析已序列化的数据
import addressbook_pb2
import sys
# 指定 Protocol Buffer 文件
my_pb_file = "my_addr_book.pb"
# 建立 AddressBook
address_book = addressbook_pb2.AddressBook()
# 写入 AddressBook
with open(my_pb_file, "rb") as f:
address_book.ParseFromString(f.read())
# 打印数据
for person in address_book.people:
print("Person ID:", person.id)
print(" Name:", person.name)
if person.HasField('email'):
print(" E-mail address:", person.email)
for phone_number in person.phones:
if phone_number.type == addressbook_pb2.Person.MOBILE:
print(" Mobile phone #:", phone_number.number)
elif phone_number.type == addressbook_pb2.Person.HOME:
print(" Home phone #:", phone_number.number)
elif phone_number.type == addressbook_pb2.Person.WORK:
print(" Work phone #:", phone_number.number)
五、结束语
一个简单的序列化数据就完成了,仅需制作一次自定义的格式化的数据,就可以使用Protbuf编译器生成各种编程语言的源码,而且更小、更快、更简单。
这只是一个Python版本的简单的栗子,还有更多功能等着你发现哦。