【Protobuf】proto二进制文件的生成与解析(附完整源码)

protobuf是Google开源的一个跨平台的结构化数据存储格式。可用于通讯协议、数据存储等领域的语言无关、平台无关、可扩展的序列化结构数据格式。


前言

说起proto的用法,可能最熟悉的莫过于以下两句:

obj.ParseFromString(data)
data = obj.SerializeToString()

其中,obj是proto中message的实例对象,data是序列化之后的二进制数据。

ParseFromString()将二进制数据反序列化,最终保存在obj中;SerializeToString()则将obj进行序列化,赋值给data

这部分网络上的资料很多,但是如何将这些数据在文件中保存起来,却很少有涉及。

本文就这部分进行讲解,其中代码部分已经开源,有需求的可以到以下链接获取:

码云:https://gitee.com/yngzMiao/protobuf-parser-tool

GitHub:https://github.com/yngzMiao/protobuf-parser-tool


如何保存

将序列化后的二进制数据保存成文件,很简单:

fw = open("xxx.proto", "wb")
fw.write(...)
fw.close()

但是,如果单纯这样写进去,如何读取呢?如何才能获取一块完整的序列化的二进制数据呢?

本文采用记录二进制数据大小的方式,即:

在每段序列化的二进制数据前,都放置4个字节大小的内容,这块内容用来保存接下来的二进制数据的字节长度

字节长度通过以下方法获得:

proto_len = obj.ByteSize()

这块内容写入proto的方式:

temp_data = [0, 0, 0, 0]
temp_data[3] = proto_len & 0x00FF
temp_data[2] = (proto_len >> 8) & 0x00FF
temp_data[1] = (proto_len >> 16) & 0x00FF
temp_data[0] = (proto_len >> 24) & 0x00FF

for i in [0, 1, 2, 3]:
  if temp_data[i] > 127:
    temp_data[i] = temp_data[i] - 256

bin_size0 = struct.pack('b', temp_data[0])
bin_size1 = struct.pack('b', temp_data[1])
bin_size2 = struct.pack('b', temp_data[2])
bin_size3 = struct.pack('b', temp_data[3])

读取这块内容的方式:

binval = [int(struct.unpack('b', temp_len[0])[0]),
          int(struct.unpack('b', temp_len[1])[0]),
          int(struct.unpack('b', temp_len[2])[0]),
          int(struct.unpack('b', temp_len[3])[0])]
          
re = (binval[0] << 24) & 0xFF000000
re = ((binval[1] << 16) & 0x00FF0000) | re
re = ((binval[2] << 8) & 0x0000FF00) | re
re = ((binval[3]) & 0x000000FF) | re

除此之外,我还添加了版本的内容。所谓版本,就是在proto的最开始的4字节,设置这个内容的本意在于:

由于proto更新频率快,添加字段或者删除字段可能也是常有的事,最好需要对每个生成的二进制文件进行标注,标明是按哪个版本的proto文件生成的。


接口说明

本文的常用的接口基本包含在example_person.py中,就对该文件进行讲解:

# -*- coding:UTF-8 -*-

import sys
import os
import proto_pb2.Person_pb2 as GeneralProto
import proto_buf.Person_buf_read as PersonRead
import proto_buf.Person_buf_write as PersonWrite
import simplejson


if __name__ == "__main__":
  if not os.path.exists(os.path.join(os.getcwd(), "data")):
    os.mkdir("data")
  
  protofile = os.path.join(os.getcwd(), "data/Person_test.proto")
  version = 20191001
  # 生成writer,需要指定三个参数:
  # 生成的二进制文件的路径,proto结构中的基本message,版本号(可忽略,默认值20191001)
  writer = PersonWrite.GeneralProtoWriter(protofile, GeneralProto.Person, version)

  person1 = GeneralProto.Person()
  person1.id = 100000
  person1.name = "zhangsan"
  person1.age = 20

  person1.email.append("[email protected]")
  person1.email.append("[email protected]")
  phone1 = person1.phone.add()
  phone2 = person1.phone.add()
  phone1.number = "987654"
  phone1.type = GeneralProto.PhoneType.MOBILE
  phone2.number = "876543"
  phone2.type = GeneralProto.PhoneType.HOME

  addr = person1.address
  addr.country = "china"
  addr.detail = "beijing"
  
  # 将obj对象直接写入到二进制文件中
  writer.writeFrameData_general(person1)

  person2 = '{"name":"lisi","age":22,"id":200000,"email":["[email protected]","[email protected]"],"phone":[{"type":1,"number":"765432"},{"type":2,"number":"654321"}],"address":{"country":"china","detail":"nanjing"}}'

  # 将json字符串写入到二进制文件中
  writer.writeFrameData_json(person2)
  # 关闭writer
  writer.stopWriter()

  # 生成reader,需要指定两个参数:
  # 需要解析的二进制文件的路径,proto结构中的基本message
  reader = PersonRead.GeneralProtoReader(protofile, GeneralProto.Person)
  # 获得版本号
  version_read = reader.getVersion()
  # 获得二进制数据的个数(帧数)
  frame_count = reader.getFrameCount()
  
  # 定位帧数(必须)
  reader.setFrameIndex(0)
  # 获得该帧的字段内容
  print(reader.getFrameData_general("id"))
  print(reader.getFrameData_general("name"))
  print(reader.getFrameData_general("age"))
  print(reader.getFrameData_general("email"))
  print(reader.getFrameData_general("phone"))
  print(reader.getFrameData_general("address.country"))
  print(reader.getFrameData_general("address.detail"))
  
  reader.setFrameIndex(1)
  # 将某帧的二进制内容解析,并转换成json字符串
  json = reader.getFrameData_json()
  print(json)

后言

如果喜欢本篇内容,就到码云或者GitHub点赞,谢谢。

如果有什么BUG或者反馈,欢迎提出。

你可能感兴趣的:(《开发》ProtoBuf)