hello 大家好我是Monday,今天给大家带来一篇关于简单介绍Protobuf协议以及如何使用Protobuf来实现序列化与反序列化
的文章。
前言: Protobuf即Protocol Buffers,是Google公司开发的一种跨语言和平台的序列化数据结构的方式,是一个灵活的、高效的用于序列化数据的协议。
与XML和JSON格式相比,Protobuf更小、更快、更便捷。Protobuf是跨语言的,并且自带一个编译器(protoc),只需要用protoc进行编译,就可以编译成Java、Python、C++、C#、Go等多种语言代码,然后可以直接使用,不需要再写其它代码,自带有解析的代码。
只需要将要被序列化的结构化数据定义一次(在.proto文件定义),便可以使用特别生成的源代码(使用Protobuf提供的生成工具)轻松的使用不同的数据流完成对结构数据的读写操作。甚至可以更新.proto文件中对数据结构的定义而不会破坏依赖旧格式编译出来的程序。
Protobuf的优点如下:
性能号,效率高。序列化后字节占用空间比XML少3-10倍,序列化的时间效率比XML快20-100倍。
有代码生成机制。将对结构化数据的操作封装成一个类,便于使用。
支持向后和向前兼容。当客户端和服务器同时使用一块协议的时候, 当客户端在协议中增加一个字节,并不会影响客户端的使用。
支持多种编程语言。Protobuf目前已经支持Java,C++,Python、Go、Ruby等多种语言。
Protobuf的缺点如下:
二进制格式导致可读性差
缺乏自描述
应用不是很广泛
(以上文字来源于网络)
Protobuf安装: (1)安装第三方库
1 pip install protobuf==3.20.1
(2)下载proto编译器
https://github.com/protocolbuffers/protobuf/releases/
1 一个exe程序,把 proto.exe 加入到环境变量里
下载对应的版本,小编使用的是windows+python语言,所以下载protoc-3.20.1-win64
注意:
电脑安装的编译器版本和 python包版本一定要相对应
Protobuf 正向流程:
.proto文件格式说明: 在前面的简介部分已经说过,Protobuf在使用时定义序列化结构的文件为后缀是.proto的文件。
.proto文件有专门的语法结构,ProtoBuf有两个语法版本:v2与v3。message 用来定义一个数据结构。
我们先来看一个简单的.proto文件的例子:
1 2 3 4 5 6 7 syntax = "proto3" ; message Person { int64 id = 1 ; string name = 2 ; repeated string skills = 3 ; // 这里表示skills可以接受多个string类型的值 }
文件的首行生命该语法使用Protobuf3语法,同时在文件后面定义了Person消息,该消息有三个字段:id, name, skill。
每个字段的定义格式为 指定字段规则 数据类型 变量名称=数字标识符
。
指定字段规则
在Protobuf3语法中只有repeated、singular两种类型,
其中singular类型(默认类型,不需要声明)表示有0个或者1个这种字段(但是不能超过1个);
repeated类型表示该字段可以重复任意多次(包括0次),重复值的顺序会被保留。
数据类型
常见的有double、float、int32、string、bytes、bool等,也可以是枚举、嵌套消息类型、Any、oneof等。
正如你所见,在消息定义中,每个字段都有唯一的一个数字标识符。这些标识符是用来在消息的二进制格式中识别各个字段的,一旦开始使用就不能够再改变。注:[1,15]之内的标识号在编码的时候会占用一个字节。[16,2047]之内的标识号则占用2个字节。所以应该为那些频繁出现的消息元素保留 [1,15]之内的标识号。切记:要为将来有可能添加的、频繁出现的标识号预留一些标识号。
最小的标识号可以从1开始,最大到2^29 - 1, or 536,870,911。不可以使用其中的[19000-19999]( (从FieldDescriptor::kFirstReservedNumber 到 FieldDescriptor::kLastReservedNumber))的标识号, Protobuf协议实现中对这些进行了预留。如果非要在.proto文件中使用这些预留标识号,编译时就会报警。同样你也不能使用早期保留的标识号。
从.proto文件生成了什么? 当用protocol buffer编译器来运行.proto文件时,编译器将生成所选择语言的代码,这些代码可以操作在.proto文件中定义的消息类型,包括获取、设置字段值,将消息序列化到一个输出流中,以及从一个输入流中解析消息。
对C++来说,编译器会为每个.proto文件生成一个.h文件和一个.cc文件,.proto文件中的每一个消息有一个对应的类。
对Java来说,编译器为每一个消息类型生成了一个.java文件,以及一个特殊的Builder类(该类是用来创建消息类接口的)。
对Python来说,有点不太一样——Python编译器为.proto文件中的每个消息类型生成一个含有静态描述符的模块,,该模块与一个元类(metaclass)在运行时(runtime)被用来创建所需的Python数据访问类。
对go来说,编译器会位每个消息类型生成了一个.pd.go文件。
对于Ruby来说,编译器会为每个消息类型生成了一个.rb文件。
javaNano来说,编译器输出类似域java但是没有Builder类
对于Objective-C来说,编译器会为每个消息类型生成了一个pbobjc.h文件和pbobjcm文件,.proto文件中的每一个消息有一个对应的类。
对于C#来说,编译器会为每个消息类型生成了一个.cs文件,.proto文件中的每一个消息有一个对应的类。 你可以从如下的文档链接中获取每种语言更多API(proto3版本的内容很快就公布)。API Reference
下面我们开始实际操作起来: 序列化与反序列化 下面将通过一个简单的里来介绍如何使用Protobuf来实现序列化与反序列化。
定义数据结构文件(person_and_book.proto
)如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 syntax = "proto3"; message Book { // 书籍信息 string name = 1; float price = 2; string press = 3; repeated Person people = 4; } message Person { // 人物信息 int32 id = 1; string name = 2; int32 age = 3; string email = 4; string job = 5; bool work_status = 6; string city = 7; MyAddress maps = 8; } message MyAddress { // 地址信息,字段类型为map map<string, string> tell_address = 1; }
使用protoc编译person_and_book.proto文件, 命令行如下:
1 protoc.exe ./person_and_book.proto --python_out=./
编译完毕,会自动生成person_and_book_pb2.py文件。在命令行中,./person_and_book.proto为需要编译的.proto文件所在路径,python_out为输出python脚本路径,./表示为当前路径。
接着我们使用一个新的脚本(add_person.py)针对该数据结构进行序列化与反序列化。完整代码如下:
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 import person_and_book_pb2book = person_and_book_pb2.Book() book.name = "菜鸟童靴" book.price = 8.5 book.press = "NY Press" person = book.people.add() person.id = 1 person.name = "Monday" person.age = 25 person.email = "Monday@163.com" person.job = "college professor" person.work_status = True person.city = "北京" address_maps = person.maps address_maps.tell_address["born place" ] = "内蒙古" address_maps.tell_address["living place" ] = "北京" address_maps.tell_address["visited place" ] = "内蒙古, 北京, 长春" print("===================序列化======================================" ) serializeToString = book.SerializeToString() print(type(serializeToString), serializeToString) print("===================反序列化======================================" ) parsed_book = person_and_book_pb2.Book() parsed_book.ParseFromString(serializeToString) print(type(parsed_book),parsed_book) print("=======================输出书籍信息==================================" ) print("book_name: %s, book_price: %s, book_press: %s" % (parsed_book.name, parsed_book.price, parsed_book.press)) print("======================输出人物信息===================================" ) for person in parsed_book.people: print("p_id: %s, p_name: %s, p_age: %s, p_email: %s, p_job: %s, p_work_status: %s, p_city: %s" % (person.id, person.name, person.age, person.email, person.job, person.work_status, person.city)) for key in person.maps.tell_address: print(key, person.maps.tell_address[key])
输出结果如下:
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 32 33 34 35 36 37 ===================序列化====================================== <class 'bytes'> b'\n\x0c\xe8\x8f\x9c\xe9\xb8\x9f\xe7\xab\xa5\xe9\x9d\xb4\x15\x00\x00\x08A\x1a\x08NY Press"\x98\x01\x08\x01\x12\x06Monday\x18\x19"\x0eMonday@163.com*\x11college professor0\x01:\x06\xe5\x8c\x97\xe4\xba\xacB]\n\x16\n\x0cliving place\x12\x06\xe5\x8c\x97\xe4\xba\xac\n*\n\rvisited place\x12\x19\xe5\x86\x85\xe8\x92\x99\xe5\x8f\xa4, \xe5\x8c\x97\xe4\xba\xac, \xe9\x95\xbf\xe6\x98\xa5\n\x17\n\nborn place\x12\t\xe5\x86\x85\xe8\x92\x99\xe5\x8f\xa4' ===================反序列化====================================== <class 'person_and_book_pb2.Book'> name: "\350\217\234\351\270\237\347\253\245\351\235\264" price: 8.5 press: "NY Press" people { id: 1 name: "Monday" age: 25 email: "Monday@163.com" job: "college professor" work_status: true city: "\345\214\227\344\272\254" maps { tell_address { key: "born place" value: "\345\206\205\350\222\231\345\217\244" } tell_address { key: "living place" value: "\345\214\227\344\272\254" } tell_address { key: "visited place" value: "\345\206\205\350\222\231\345\217\244, \345\214\227\344\272\254, \351\225\277\346\230\245" } } } =======================输出书籍信息================================== book_name: 菜鸟童靴, book_price: 8.5, book_press: NY Press ======================输出人物信息=================================== p_id: 1, p_name: Monday, p_age: 25, p_email: Monday@163.com, p_job: college professor, p_work_status: True, p_city: 北京 living place 北京 born place 内蒙古 visited place 内蒙古, 北京, 长春
参考网址:
Protobuf3 语法指南 : https://colobu.com/2017/03/16/Protobuf3-language-guide/
Protobuf协议逆向和仿真&举个栗子 : https://mp.weixin.qq.com/s/_Na2hjLKvcgVzRx5u_zbLg
https://mp.weixin.qq.com/s/QbLq5gVKjaHyoaY2Vv5MRQ
https://zhuanlan.zhihu.com/p/38601419
结束语 :
今天的分享就到这里了,欢迎大家关注微信公众号”菜鸟童靴 “