ProtoBuf
1: ProtoBuf
网络通信和通用数据交换等应用场景中经常使用的技术是 JSON 或 XML,而在最近的开发中接触到了 Google 的 ProtoBuf
ProtoBuf效率、兼容性等方面非常出色,网络通信、通用数据交换等场景应该会优先选择 ProtoBuf
protocol buffers 是一种语言无关、平台无关、可扩展的序列化结构数据的方法,它可用于(数据)通信协议、数据存储等。
Protocol Buffers 是一种灵活,高效,自动化机制的结构数据序列化方法-可类比 XML,但是比 XML 更小(3 ~ 10倍)、更快(20 ~ 100倍)、更为简单
你可以定义数据的结构,然后使用特殊生成的源代码轻松的在各种数据流中使用各种语言进行编写和读取结构数据。
你甚至可以更新数据结构,而不破坏由旧数据结构编译的已部署程序
ProtoBuf 是结构数据序列化方法
2:序列化
序列化:将结构数据或对象转换成能够被存储和传输(例如网络传输)的格式,
同时应当要保证这个序列化结果在之后(可能在另一个计算环境中)能够被重建回原来的结构数据或对象。
类比于 XML:这里主要指在数据通信和数据存储应用场景中序列化方面的类比,但个人认为 XML 作为一种扩展标记语言和 ProtoBuf 还是有着本质区别的。
3:ProtoBuf数据格式
创建 .proto 文件,定义数据结构 // 例1: 在 xxx.proto 文件中定义 Example1 message message Example1 { optional string stringVal = 1; optional bytes bytesVal = 2; message EmbeddedMessage { int32 int32Val = 1; string stringVal = 2; } optional EmbeddedMessage embeddedExample1 = 3; repeated int32 repeatedInt32Val = 4; repeated string repeatedStringVal = 5; } 定义了一个名为 Example1 的 消息,语法很简单,message 关键字后跟上消息名称: 之后我们在其中定义了 message 具有的字段,形式为: message xxx { // 字段规则:required -> 字段只能也必须出现 1 次 // 字段规则:optional -> 字段可出现 0 次或1次 // 字段规则:repeated -> 字段可出现任意多次(包括 0) // 类型:int32、int64、sint32、sint64、string、32-bit .... // 字段编号:0 ~ 536870911(除去 19000 到 19999 之间的数字) 字段规则 类型 名称 = 字段编号; }
在上例中,我们定义了: 类型 string,名为 stringVal 的 optional 可选字段,字段编号为 1,此字段可出现 0 或 1 次 类型 bytes,名为 bytesVal 的 optional 可选字段,字段编号为 2,此字段可出现 0 或 1 次 类型 EmbeddedMessage(自定义的内嵌 message 类型),
名为 embeddedExample1 的 optional 可选字段,字段编号为 3,此字段可出现 0 或 1 次 类型 int32,名为 repeatedInt32Val 的 repeated 可重复字段,字段编号为 4,此字段可出现 任意多次(包括 0) 类型 string,名为 repeatedStringVal 的 repeated 可重复字段,字段编号为 5,此字段可出现 任意多次(包括 0)
4:protoc 编译 .proto 文件生成读写接口
我们在 .proto 文件中定义了数据结构,这些数据结构是面向开发者和业务程序的,并不面向存储和传输
// $SRC_DIR: .proto 所在的源目录
// --cpp_out: 生成 c++ 代码
// $DST_DIR: 生成代码的目标目录
// xxx.proto: 要针对哪个 proto 文件生成接口代码
protoc -I=$SRC_DIR --cpp_out=$DST_DIR $SRC_DIR/xxx.proto
5:最终生成的代码将提供类似如下的接口
例子-序列化和解析接口.png
例子-protoc 生成接口.png
你只需要定义如何组织你的结构化数据一次,然后就可以使用protoc轻松的根据这个定义生成语言相关的源代码(支持多种语言),以便于读写结构化数据。
8:proto buffer 使用说明
1.下载protobuf,直接选择win64版本即可,解压后记得将文件路径加入环境变量https://github.com/google/protobuf/releases
2.编译proto文件,生成py文件
protoc --python_out=./ example.proto
3.从产生的py文件中导入message对象,可以进行序列化和反序列操作
from proto.MessageProBuf_pb2 import PingMessageProBuf, MessageHead from google.protobuf.json_format import MessageToJson pb = PingMessageProBuf() pb_head = pb.messageHead pb_head.to = '11' setattr(pb_head, 'from', '11') data = pb.SerializeToString() # 序列化 pb = PingMessageProBuf() pb.ParseFromString(data) # 反序列化 print(pb.messageHead.to) json_string_request = MessageToJson(req, preserving_proto_field_name=True) # 反序列化后再调用MessageToJson,直接转换为json字符串
9:protobuf关键字
required
顾名思义,就是必须的意思,数据发送方和接收方都必须处理这个字段,不然还怎么通讯呢
optional
字面意思是可选的意思,具体protobuf里面怎么处理这个字段呢,就是protobuf处理的时候另外加了一个bool的变量,
用来标记这个optional字段是否有值,发送方在发送的时候,如果这个字段有值,那么就给bool变量标记为true,否则就标记为false,
接收方在收到这个字段的同时,也会收到发送方同时发送的bool变量,拿着bool变量就知道这个字段是否有值了,这就是option的意思。
这也就是他们说的所谓平滑升级,无非就是个兼容的意思。
其实和传输参数的时候,给出数组地址和数组数量是一个道理。
repeated
字面意思大概是重复的意思,其实protobuf处理这个字段的时候,也是optional字段一样,
另外加了一个count计数变量,用于标明这个字段有多少个,这样发送方发送的时候,
同时发送了count计数变量和这个字段的起始地址,接收方在接受到数据之后,按照count来解析对应的数据即可。
10:repeated和自定义类型关键字的编码和解码
1.自定义关键字
假设有一个数据结构如下:
message CommonSuccessProBuf{ MessageHead messageHead = 1; } message MessageHead{ string from = 1; string to = 2; string messageId = 3; int32 chatType = 4; bool offline = 5; }
编码的时候应该这样:
pb = CommonSuccessProBuf() message_head_pb = pb.messageHead # 重点 message_head_pb.from = 'seg54y46htj8' message_head_pb.to = 'afef34y' message_head_pb.messageId = 'asd4y5hdfs' message_head_pb.chatType = 4 message_head_pb.offline = True data = pb.SerializeToString() # message_head_pb不用再序列化,直接pb序列化就可以了
2.repeated关键字
上面讲的是自定义类型,假如是repeated关键字:
message CommonSuccessProBuf{ repeated messageHead = 1; } message MessageHead{ string from = 1; string to = 2; string messageId = 3; int32 chatType = 4; bool offline = 5; }
那么:message_head_pb
=
pb.messageHead
应该换为:message_head_pb
=
pb.messageHead.add()
# 重点
关于解码,以自定义数据类型为例
pb = PingMessageProBuf() pb.ParseFromString(data) # data是proto序列化数据 message_head = pb.messageHead print(message_head.to)
如果是repeated结构,那么message_head则是一个列表,列表的每个元素都是MessageHead结构