protobuf是Google开源的一个跨平台的结构化数据存储格式。可用于通讯协议、数据存储等领域的语言无关、平台无关、可扩展的序列化结构数据格式。
前言
说起python版本的proto的用法,可能最熟悉的莫过于以下两句:
obj.ParseFromString(data) data = obj.SerializeToString()
其中,obj是proto中message的实例对象,data是序列化之后的二进制数据。
ParseFromString()将二进制数据反序列化,最终保存在obj中;SerializeToString()则将obj进行序列化,赋值给data。
这样可以将“一帧”的数据进行序列化和反序列化,这部分网络上的资料很多。但是如果有很多“帧”的数据,甚至还有一些不能proto序列化的二进制内容想要一起保存。这部分的资料却很少。
比如:我们有一个相机,我们可以得到每一帧的图片,然后我们对每张图片进行处理,得到一些处理结果。我们想要把图片和处理结果一起保存起来。这该怎么办呢?
本文就这部分进行讲解,其中代码部分已经开源,有需求的可以到以下链接获取,包含C++和Python版本:
GitHub:https://github.com/yngzMiao/protobuf-parser-tool
下文主要以Python版本为例,进行介绍。
如何保存
同样以保存相机的数据为例。首先分析一下需要保存的内容:图像信息可以用其二进制内容进行保存,处理结果可以通过proto的文件定义进行保存。这部分合并起来就是一帧的内容。
但是有一个问题,每帧内的图像信息不一定大小都一样!图片信息和处理结果之间怎么区分?每帧之间怎么区分?
最简单的办法就是在每块内容之间加上一个固定长度的tag,该tag存放的是下面一块内容的大小长度。那么就可以通过这个tag,进行块与块之间的跳转了。
即采用记录二进制数据大小的方式,即:
在每段序列化的二进制数据前,都放置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文件生成的。
接口说明
为了通用性,Python版本提供了GeneralProtoReader和GeneralProtoWriter类,需要序列化的类名可以通过参数传递进去。由于C++无法将类名作为参数传递,采用模板编程的方法,来指定类名。
Python版本的主要内容包含在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 = # 生成writer,需要指定三个参数: # 生成的二进制文件的路径,proto结构中的基本message,版本号(可忽略,默认值) writer = PersonWrite.GeneralProtoWriter(protofile, GeneralProto.Person, version) person1 = GeneralProto.Person() person1.id = person1.name = "zhangsan" person1.age = 20 person1.email.append("@.com") person1.email.append("@.com") phone1 = person1.phone.add() phone2 = person1.phone.add() phone1.number = "" phone1.type = GeneralProto.PhoneType.MOBILE phone2.number = "" 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":,"email":["@.com","@.com"],"phone":[{"type":1,"number":""},{"type":2,"number":""}],"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)
而C++版本的主要内容也类似:
#include
#include "General_buf_read.h" #include "General_buf_write.h" #include "Person.pb.h" namespace PersonProto {
class Person; }; namespace GeneralBuf {
template <typename T> class GeneralProtoWriter; typedef GeneralProtoWriter<PersonProto::Person> PersonProtoWriter; template <typename T> class GeneralProtoReader; typedef GeneralProtoReader<PersonProto::Person> PersonProtoReader; }; int main(int argc, char const *argv[]) {
GeneralBuf::PersonProtoWriter writer; std::string filename = "Person_test.proto"; int64_t version = ; writer.startWriter(filename, version); PersonProto::Person *person1 = new PersonProto::Person(); person1->set_id(); person1->set_name("zhangsan"); person1->set_age(20); person1->add_email("@.com"); person1->add_email("@.com"); PersonProto::PhoneNumber *phone1 = person1->add_phone(); PersonProto::PhoneNumber *phone2 = person1->add_phone(); phone1->set_number(""); phone1->set_type(PersonProto::PhoneType::MOBILE); phone2->set_number(""); phone2->set_type(PersonProto::PhoneType::HOME); PersonProto::Address *addr = person1->mutable_address(); addr->set_country("china"); addr->set_detail("beijing"); writer.write(person1); writer.stopWriter(); GeneralBuf::PersonProtoReader reader; reader.startReader(filename); std::cout << "version : " << reader.getVersion() << "\n"; std::cout << "cnt : " << reader.getFrameCount() << "\n"; reader.setFrameIndex(0); PersonProto::Person *person = new PersonProto::Person(); reader.read(person); person->PrintDebugString(); delete person1; delete person; return 0; }
后言
如果喜欢本篇内容,就到GitHub点赞,谢谢。
如果有什么BUG或者反馈,欢迎提出。
发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/212667.html原文链接:https://javaforall.net
