【Protobuf】proto通用二进制文件的生成与解析(C++/Python,附完整源码)

【Protobuf】proto通用二进制文件的生成与解析(C++/Python,附完整源码)protobuf 是 Google 开源的一个跨平台的结构化数据存储格式 可用于通讯协议 数据存储等领域的语言无关 平台无关 可扩展的序列化结构数据格式 前言说起 proto 的用法 可能最熟悉的莫过于以下两句 obj ParseFromStr data data obj SerializeToS 其中 obj 是 proto 中 message 的实例对象 data 是序列化之后的

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版本提供了GeneralProtoReaderGeneralProtoWriter类,需要序列化的类名可以通过参数传递进去。由于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或者反馈,欢迎提出。


【Protobuf】proto通用二进制文件的生成与解析(C++/Python,附完整源码)








版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/212667.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月18日 下午7:32
下一篇 2026年3月18日 下午7:33


相关推荐

  • 外链式样式表_引入CSS样式表(书写位置)

    外链式样式表_引入CSS样式表(书写位置)CSS初识CSS(CascadingStyleSheets)美化样式CSS通常称为CSS样式表或层叠样式表(级联样式表),主要用于设置HTML页面中的文本内容(字体、大小、对齐方式等)、图片的外形(宽高、边框样式、边距等)以及版面的布局等外观显示样式。CSS以HTML为基础,提供了丰富的功能,如字体、颜色、背景的控制及整体排版等,而且还可以针对不同的浏览器设置不同的样式。引入CSS样式表(书…

    2022年7月14日
    20
  • UNet以ResNet34为backbone in keras

    UNet以ResNet34为backbone in keras参考 https www kaggle com meaninglessl unet resnet34 in keras

    2026年3月16日
    2
  • 计算机代码编程知识,编程基础知识

    计算机代码编程知识,编程基础知识编程基础知识创建一个易应用程序只需要短短几分钟的时间 通过在设计窗口上 绘制 诸如编辑框和按钮等组件来创建用户界面 然后 为窗口和组件设置属性以规定诸如标题 位置 尺寸等的值 最后 编写处理程序将生命真正赋于程序 组件及事件驱动组件及其事件驱动是使用易语言在 Windows 环境下编程的基础知识 所谓 组件 即用作组成用户图形界面的基本成员 譬如 窗口 编辑框 图片框等等 组件按可否容纳其它组件划分

    2026年3月17日
    2
  • docker mysql 启动失败_mysql启动1067错误

    docker mysql 启动失败_mysql启动1067错误docker启动mysql失败,报错dockerexec-itmysqlmysql-uroot-prootErrorresponsefromdaemon:Container0f83eee59a75595deedecbd40b384333e6db35edd90c5d4c3a0eb3212f2e4665isrestarting,waituntilthecontainerisrunning一直显示正在重启使用dockerlogs–tail50–follow-

    2022年10月6日
    5
  • Manus 爆火,能否引领中国 AI 新变革?

    Manus 爆火,能否引领中国 AI 新变革?

    2026年3月15日
    3
  • 轻量级MVVM框架Stylet介绍:(3)关于Bootstrapper「建议收藏」

    轻量级MVVM框架Stylet介绍:(3)关于Bootstrapper「建议收藏」Bootstrapper负责引导应用程序,用于配置IoC容器,创建根ViewModel的新实例,并使用显示WindowManager出来。它还提供了各种其他功能,如下所述。引导程序有两种风格

    2022年7月4日
    37

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号