全栈工程师开发手册 (作者:栾鹏)
架构系列文章
Protobuf?
-
是什么?
Google Protocol Buffer(简称 Protobuf)是一种轻便高效的结构化数据存储格式,平台无关、语言无关、可扩展,可用于通讯协议和数据存储等领域。 -
为什么要用?
- 平台无关,语言无关,可扩展;
- 提供了友好的动态库,使用简单;
- 解析速度快,比对应的XML快约20-100倍;
- 序列化数据非常简洁、紧凑,与XML相比,其序列化之后的数据量约为1/3到1/10。
首先我们要知道protobuf是如何使用的.我们需要先编写一个.proto的文件,这个文件用来定义数据格式,和json文件一样.下一步,我们需要proto的编译器将这个文件编译成py文件,或者h/cpp文件.最后我们在通过proto提供的python的api接口访问生成的py文件.
定义.proto文件
首先需要一个proto文件,其中定义了我们程序中需要处理的结构化数据:下面是一个.proto文件的示例(按照protobuf3的语法格式书写的)
/**
* 测试数据格式
*/
syntax = "proto3"; //proto3必须在文件首页加这一句
package people; //包名,相当于定义了python文件的名称
message Person { //自定义类
string name = 1; // 类型,变量,分配标识号
int32 id = 2; //类型,变量,分配标识号
string email = 3;
enum PhoneType { // 设置枚举类型
MOBILE = 0;
HOME = 1;
WORK = 2;
}
message PhoneNumber { //设置子类型
string number = 1; // 类型,变量,分配标识号
PhoneType type = 2; // 类型,变量,分配标识号
}
repeated PhoneNumber phones = 4; // 设置可重复类型,也就是列表
}
message AddressBook { // 自定义类
repeated Person people = 1; //定义列表
}
文件的第一行指定了你正在使用proto3语法:如果你没有指定这个,编译器会使用proto2。这个指定语法行必须是文件的非空非注释的第一个行。
向.proto文件添加注释,可以使用C/C++/Java风格的双斜杠(//) 语法格式
protobuf3语法可以参考https://blog.csdn/u011518120/article/details/54604615
Protobuf2的语法
1 *.proto文件中数据类型可以分为两大类:
复合数据类型 + 标准数据类型
复合数据类型包括:枚举和message类型
标准数据类型包含:整型,浮点,字符串等
2 数据类型前面修饰词:
①required: 必须赋值,不能为空,否则该条message会被认为是“uninitialized”。build一个“uninitialized” message会抛出一个RuntimeException异常,解析一条“uninitialized” message会抛出一条IOException异常。除此之外,“required”字段跟“optional”字段并无差别。
②optional:字段可以赋值,也可以不赋值。假如没有赋值的话,会被赋上默认值。
③repeated: 该字段可以重复任意次数,包括0次。重复数据的顺序将会保存在protocol buffer中,将这个字段想象成一个可以自动设置size的数组就可以了。
3.每个字段要给数字:
该Number是用来标记该字段在序列化后的二进制数据中所在的field,每个字段的Number在message内部都是独一无二的。也不能进行改变,否则数据就不能正确的解包。
4 数据类型
数据类型这里可以去看Protobuf支持的基本数据类型的表
5 默认值
当一个消息被解析的时候,如果被编码的信息不包含一个特定的singular元素,被解析的对象锁对应的域被设置位一个默认值,对于不同类型指定如下:
对于strings,默认是一个空string
对于bytes,默认是一个空的bytes
对于bools,默认是false
对于数值类型,默认是0
对于枚举,默认是第一个定义的枚举值,必须为0;
对于消息类型(message),域没有被设置,确切的消息是根据语言确定的,详见generated code guide
对于可重复域的默认值是空(通常情况下是对应语言中空列表)。
注:对于标量消息域,一旦消息被解析,就无法判断域释放被设置为默认值(例如,例如boolean值是否被设置为false)还是根本没有被设置。你应该在定义你的消息类型时非常注意。例如,比如你不应该定义boolean的默认值false作为任何行为的触发方式。也应该注意如果一个标量消息域被设置为标志位,这个值不应该被序列化传输。
Proto3的语法变化
protobuf3的语法相较于protobuf2的语法变化较大
(1)语法标记
这个版本的protoc的protobuf编译器已经可以支持proto2语法和proto3的语法
如果你的proto文件没有添加syntax说明的话, 用这个版本的编译器会报错, 提示你默认proto2支持, 请添加语法标记
syntax = “proto2”;
(2)optional不需要了
只保留repeated标记数组类型, optional和required都被去掉了
实际使用证明, required的设计确实是蛋疼, C++的调试版会弹出assert,release版和optional也没啥区别
(3)map支持
map编写格式为
map<key_type, value_type> map_field = N;
例如:
map<string, Project> projects = 3;
代码生成确认支持map, 这对于很多语言来说又可以偷懒了
(4)字段default标记不能使用了
位于proto2语法的字段number后的[default=XX]
这个东西不能用了, 理由是:
对于同一段序列化后的数据, 如果序列化端的default和反序列化端的default描述不一样会导致最终结果完全不一致
即: 同一个数据两个结果, 这是不可预测的结果, 因此干掉这个特性
不过本人觉得, 对于游戏来说, 这个功能本身可以压缩很多数据,虽然会有隐患
(5)枚举默认值一定是0
proto2里的默认值是枚举的第一个value对应的值, 不一定为0
proto3在你定义value时, 强制要求第一个值必须为0
这个修改为避免隐患还是有帮助的
(6)泛型描述支持
any类型, 可以代表任何类型, 可以先读进来, 再进行解析, 没具体用, 步子跨大了怕扯到蛋
(7)支持json序列化
这个极好, json再次被同化了
(8)增加了多种语言支持
js, objc, ruby, C#等等
然而, C#版本的基础runtime库是用C# 6.0的语法写的,这对于Unity mono祖传2.0来说, 确实扯到蛋了,没法用
(9)Protobuf现在使用CMAKE做配置系统
编译起来稍微麻烦, 还要下个被墙掉的cmake…
定义服务(Service)
如果想要将消息类型用在RPC(远程方法调用)系统中,可以在.proto文件中定义一个RPC服务接口,protocol buffer编译器将会根据所选择的不同语言生成服务接口代码及存根。如,想要定义一个RPC服务并具有一个方法,该方法能够接收 SearchRequest并返回一个SearchResponse,此时可以在.proto文件中进行如下定义:
参考:https://blog.csdn/u014066037/article/details/72845802
编译.proto文件产出Python代码(生成xxxx_pb2.py)
我们要安装proto编译工具,能将proto文件编译成py文件或h/cpp文件.
编译工具直接在github上下载即可,地址: https://github/google/protobuf/releases
window下编译proto文件生成py文件
利用protoc.exe编译proto文件,cmd切换到当前目录,执行以下命令:
protoc -I=. --python_out=./ people.proto
或者直接默认在当前文件夹下寻找
protoc --python_out=./ people.proto
-I=源文件目录,–python_out=编译生成的文件的路径 ,people.proto是要编译的协议文件
编译好之后你就会在目标目录里面看到输出的结果文件,如下:people_pb2.py
在ubuntu下编译proto文件生成py文件
ubuntu安装protobuf编译器
sudo apt install protobuf-compiler
不过通过官网安装的并不一定是最新版本的.所以建议还是通过github下载安装.
我这里现在的是protobuf-python-3.6.0.tar.gz
tar -zxvf protobuf-3.6.0.tar.gz
cd protobuf-3.6.0/
./configure
make
make check
sudo make install
protoc --version 检查是否安装成功
最后我安装完成,用上述命令检查版本号时出现如下问题
protoc: error while loading shared libraries: libprotocbuf.so.16: cannot open shared
错误原因
protobuf的默认安装路径是/usr/local/lib,而/usr/local/lib不在ubuntu体系默认的LD_LIBRARY_PATH里,所以就找不到lib
解决办法
1 在 /etc/ld.so.conf.d/目录下创建文件 bprotobuf.conf文件,文件内容如下
/usr/local/lib
2 输入命令
sudo ldconfig
这时,再输入protoc --version就可以正常看到版本号了
使用protobuf编译器编译proto文件.进入你的proto文件所在的目录.
protoc -I=. --python_out=. people.proto
表示将当前目录下的people.proto文件编译成py文件.
当然如果你不是使用python开发,你也可以讲proto编译成其他的语言所需要的格式,如成c++语言的h文件和cpp文件.
python调用protobuf生成的py模块
我们要先安装protobuf库
pip install protobuf
在自己的python文件中引入这个数据进行调用
from people_pb2 import Person
alluser=[]
def write_test():
person = Person()
person.name='name1'
person.id=1
person.email='111@aa'
alluser.append(person)
def read_test():
print(alluser[0])
if __name__ == "__main__":
write_test()
read_test()
# 创建对象
item = arctern_pb2.Item() #Item为proto文件中创建的类型
# 将对象转化为字符串
str_data= item.SerializeToString()
# 将字符串转化为对象
item.ParseFromString(str_data) # str_data必须为同类proto对象序列化以后的字符串
#遍历proto中定义的可重复对象
for item in arr: # arr为proto中定义的repeated对象
pass
# 以下标索引的方式读取可重复元素
item = arr[0]
# 可重复对象添加元素
item1=arr.add() # 为arr对象添加一个元素,并返回元素的索引, arr为proto中定义的repeated对象
arr.append(item2) # 将一个元素添加到arr中,arr为proto中定义的repeated对象
# 将一个实例对象的数据 复制给另一个对象
item1.MergeFrom(item2) # item1和item2必须是相同类型的
# 对象清除某个属性
item.ClearField('attr1')
对于map元素,例如在proto中
message HelloRequired {
map<string, string> args = 1;
}
在python中
import hellorequired_pb2
test = hellorequired_pb2.HelloRequired()
test.args["key1"] = "value1"
test.args["key2"] = "value2"
更多推荐
protobuf序列化协议python教程
发布评论