当前位置: 首页 > news >正文

Protobuf的介绍及使用

ProtoBuf是什么
ProtoBuf(全称 Protocol Buffer)是数据结构序列化和反序列化框架,它具有以下特点:
• 语言无关、平台无关:即 ProtoBuf 支持 Java、C++、Python 等多种语言,支持多个平台。
• 高效:即比 XML 更小、更快、更为简单。
• 扩展性、兼容性好:你可以更新数据结构,而不影响和破坏原有的旧程序。
Protobuf 使用流程介绍

1.编写 .proto 文件,目的是为了定义结构对象(message)及属性内容
2.使用 protoc 编译器编译 .proto 文件,生成一系列接口代码,存放在新生成头文件和源文件中
3.依赖生成的接口,将编译生成的头文件包含进我们的代码中,实现对 .proto 文件中定义的字段进行设置和获取,和对 message 对象进行序列化和反序列化。
我们以一个简单通讯录的实现来驱动对 Protobuf 的学习。在通讯录 demo 中,我们将实现:
• 对一个联系人的信息使用 Protobuf 进行序列化,并将结果打印出来
• 对序列化后的内容使用 Protobuf 进行反序列,解析出联系人信息并打印出来
• 联系人包含以下信息: 姓名、年龄
通过通讯录 demo,我们能快速的了解 ProtoBuf 的使用流程。
创建.proto文件
.proto文件规范
文件名应该使用全小写字母命名,多个字母之间用_连接。例如:lower_snake_case.proto
书写.proto代码时,应使用2个空格的缩进。
我们为通讯录demo新建文件:contacts.proto
向文件添加注释,可使用// 或者 /* … */
指定proto3语法
Protocol Buffers 语言版本 3,简称 proto3,是 .proto 文件最新的语法版本。proto3 简
化了 Protocol Buffers 语言,既易于使用,又可以在更广泛的编程语言中使用。它允许
你使用 Java,C++,Python 等多种语言生成 protocol buffer 代码。在 .proto 文件中,
要使用 syntax = “proto3”; 来指定文件语法为 proto3,并且必须写在除去注释内容
的第一行。 如果没有指定,编译器会使用 proto2 语法。
**package 声明符 **
package 是一个可选的声明符,能表示 .proto 文件的命名空间,在项目中要有唯一性。
它的作用是为了避免我们定义的消息出现冲突。
在通讯录 demo 的 contacts.proto 文件中,可以声明其命名空间,内容如下:

syntax="proto3"; package contacts;

定义消息
消息(message): 要定义的结构化对象,我们可以给这个结构化对象中定义其对应的
属性内容。在网络传输中,我们需要为传输双方定制协议。定制协议说白了就是定义
结构体或者结构化数据,比如,tcp,udp 报文就是结构化的。再比如将数据持久化存
储到数据库时,会将一系列元数据统一用对象组织起来,再进行存储。ProtoBuf 就是
以 message 的方式来支持我们定制协议字段,后期帮助我们形成类和方法来使用。
在通讯录 demo 中我们就需要为 联系人 定义一个 message:
.proto 文件中定义一个消息类型的格式为:

message 消息类型名 { } 消息类型命名规范:使用驼峰命名法,首字母大写。

为 contacts.proto(通讯录 demo)新增联系人 message :

message PeopleInfo { }

定义消息字段
在 message 中我们可以定义其属性字段,字段定义格式为:字段类型 字段名 = 字段唯一编号;
• 字段名称命名规范:全小写字母,多个字母之间用 _ 连接。
• 字段类型分为:标量数据类型 和 特殊类型(包括枚举、其他消息类型等)。
• 字段唯一编号:用来标识字段,一旦开始使用就不能够再改变。
该表格展示了定义于消息体中的标量数据类型,以及编译 .proto 文件之后自动生成的类中与之对应的字段类型。在这里展示了与 C++ 语言对应的类型。

变长编码是指:经过 protobuf 编码后,原本 4 字节或 8 字节的数可能会被变为其他字节数。

更新 contacts.proto, 新增姓名、年龄字段:

message PeopleInfo { string name = 1; int32 age = 2; }

注:这里还要特别讲解一下字段唯一编号的范围: 1 ~ 536,870,911 (2^29 - 1) ,其中 19000 ~ 19999 不可用。 这些数不可用是因为:在 Protobuf 协议的实现中,对这些数进行了预留。如果非要在.proto 文件中使用这些预留标识号,例如将 name 字段的编号设置为 19000,
编译时就会报警。
值得一提的是,范围为 1 ~ 15 的字段编号需要一个字节进行编码, 16 ~ 2047 内的数字需要两个字节进行编码。编码后的字节不仅只包含了编号,还包含了字段类型。所以== 1 ~ 15 要用来标记出现非常频繁的字段,要为将来有可能添加的、频繁出现的字段预留一些出来==。
编译 contacts.proto 文件
编译命令行格式为:

protoc [--proto_path=IMPORT_PATH] --cpp_out=DST_DIR path/to/file.proto protoc 是 Protocol Buffer 提供的命令行编译工具。 --proto_path 指定 被编译的.proto 文件所在目录,可多次指定。可简写成 -I IMPORT_PATH 。如不指 定该参数,则在当前目录进行搜索。当某个.proto 文件 import 其他 .proto 文件时, 或需要编译的 .proto 文件不在当前目录下,这时就要用-I 来指定搜索目录。 --cpp_out= 指编译后的文件为 C++ 文件。 OUT_DIR 编译后生成文件的目标路径。 path/to/file.proto 要编译的.proto 文件。

编译 contacts.proto 文件命令如下:

protoc --cpp_out=. contacts.proto

编译 contacts.proto 文件后,会生成所选择语言的代码,我们选择的是 C++,所以编译后生成了两个文件:contacts.pb.h contacts.pb.cc。
对于编译生成的 C++ 代码,包含了以下内容 :
• 对于每个 message ,都会生成一个对应的消息类 。
• 在消息类中,编译器为每个字段提供了获取和设置方法,以及一下其他能够操作字段的方法 。
• 编辑器会针对于每个 .proto 文件生成.h 和 .cc 文件,分别用来存放类的声明与类的实现 。
contacts.pb.h 部分代码展示 :

class PeopleInfo final : public ::PROTOBUF_NAMESPACE_ID::Message { public: using ::PROTOBUF_NAMESPACE_ID::Message::CopyFrom; void CopyFrom(const PeopleInfo& from); using ::PROTOBUF_NAMESPACE_ID::Message::MergeFrom; void MergeFrom( const PeopleInfo& from) { PeopleInfo::MergeImpl(*this, from); } static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { return "PeopleInfo"; } // string name = 1; void clear_name(); const std::string& name() const; template <typename ArgT0 = const std::string&, typename... ArgT> void set_name(ArgT0&& arg0, ArgT... args); std::string* mutable_name(); PROTOBUF_NODISCARD std::string* release_name(); void set_allocated_name(std::string* name); // int32 age = 2; void clear_age(); int32_t age() const; void set_age(int32_t value); };

上述的例子中:
• 每个字段都有设置和获取的方法, getter 的名称与小写字段完全相同,setter 方法以 set_ 开头。
• 每个字段都有一个 clear_ 方法,可以将字段重新设置回 empty 状态。
contacts.pb.cc 中的代码就是对类声明方法的一些实现,在这里就不展开了 。
到这里有人可能就有疑惑了,那之前提到的序列化和反序列化方法在哪里呢?在消息类的父类 MessageLite 中,提供了读写消息实例的方法,包括序列化方法和反序列化方法。

classMessageLite{public://序列化:boolSerializeToOstream(ostream*output)const;// 将序列化后数据写入文件流boolSerializeToArray(void*data,intsize)const;boolSerializeToString(string*output)const;//反序列化:boolParseFromIstream(istream*input);// 从流中读取数据,再进行反序列化动作boolParseFromArray(constvoid*data,intsize);boolParseFromString(conststring&data);};

注意:
• 序列化的结果为二进制字节序列,而非文本格式。
• 以上三种序列化的方法没有本质上的区别,只是序列化后输出的格式不同,可以供不同的应用场景使用
• 序列化的 API 函数均为 const 成员函数,因为序列化不会改变类对象的内容, 而是将序列化的结果保存到函数入参指定的地址中 。

序列化与反序列化的使用

创建一个测试文件 info.cc,方法中我们实现:
对一个联系人的信息使用 进行序列化,并将序列化结果打印出来
对序列化后的内容使进行反序列,解析出联系人信息并打印出来

#include<iostream>#include"contacts.pb.h"// 引入编译生成的头文件 using namespace std;intmain(){string people_str;// 序列化{// .proto 文件声明的 package,通过 protoc 编译后,会为编译生成的C++代码声明同名的命名空间// 其范围是在.proto 文件中定义的内容contacts::PeopleInfo people;people.set_age(20);people.set_name("张珊");// 调用序列化方法,将序列化后的二进制序列存入 string 中if(!people.SerializeToString(&people_str)){cout<<"序列化联系人失败."<<endl;}// 打印序列化结果cout<<"序列化后的 people_str: "<<people_str.size()<<endl;}// 反序列化{contacts::PeopleInfo people;// 调用反序列化方法,读取 string 中存放的二进制序列,并反序列化出对象if(!people.ParseFromString(people_str)){cout<<"反序列化出联系人失败."<<endl;}// 打印结果cout<<"Parse age: "<<people.age()<<endl;cout<<"Parse name: "<<people.name()<<endl;}return0;}

代码书写完成后,编译 info.cc,生成可执行程序 :

g++ info.cc contacts.pb.cc -o info -std=c++11 -lprotobuf

执行可执行程序,可以看见 people 经过序列化和反序列化后的结果 :

序列化后的 people_str: 10 Parse age: 20 Parse name: 张珊

由于 ProtoBuf 是把联系人对象序列化成了二进制序列,这里用 string 来作为接收二进制序列的容器。所以在终端打印的时候会有换行等一些乱码显示。另外相对于 xml 和 JSON 来说,因为 PB 被编码成二进制,破解成本增大,ProtoBuf 编码是相对安全的。

http://www.jsqmd.com/news/1128612/

相关文章:

  • 【信息科学与工程学】【制造工程】第八十七篇 制造工程中的热学01
  • 私有化 AI 智能体 OpenClaw 2.7.5 升级 2.7.9 完整安装排错手册
  • 抖店一件代发怎么做?抖掌柜一键下单保姆级实操教程
  • 锐捷ACL单向TCP互通组网-使用TCP三次握手SYN包置位为1实现
  • Android 高级工程师面试:JVM 内存与 GC 近1年高频追问 22 题
  • 神经肿瘤免疫研究如何设计空间蛋白组课题?从Cell案例看PCF80应用
  • ComfyUI IPAdapter Plus终极指南:多模态控制与AI图像生成技术深度解析
  • Auto mode 的回退机制,Claude Code 为什么会从自动执行退回人工确认
  • 【每天认识一个国家 | 摩洛哥】
  • VS1053B的非阻塞式播放
  • Fate/Grand Automata:终极Android自动化工具,告别FGO重复刷本
  • 鸿蒙物理 108 篇 第六十八篇 五行反向相克机理
  • Power BI Report Builder实战指南:快速生成合规分页报表
  • leecodecode【面试150】【2026.7.2打卡-java版本】
  • YOLO11改进 - C3k2融合 | C3k2融合CKconv中国结卷积(Chinese Knot Convolution)增强网络对暗弱小目标的特征表达能力 | TGRS 2026
  • 微服务架构下API网关的4种安全方案对比,哪种最适合你的系统?
  • 拼多多推广全攻略高效引流玩法
  • Windows经典游戏联机救星:5分钟让红警、星际、暗黑等老游戏重获新生
  • VLC Android电视版深度配置:打造专业级智能电视媒体中心的7个关键步骤
  • UI自动化测试方案调研:从概念到落地的完整决策指南
  • 为什么内向者会“话题终结者”?
  • 外贸独立站不是门面工程,而是获客引擎
  • 从《中国统计年鉴》到可比数据:手把手教你计算不变价GDP
  • Java程序设计(第3版)第四章——静态代码块
  • Codex + Figma:从零构建高保真 UI 的终极指南
  • Devin工程化落地:AI协作者如何嵌入CI/CD与测试流水线
  • vs调试技巧+宏定义+动态内存
  • 一线老师傅经验谈:选对海绵喷胶源头厂家,粘接寿命延长8年
  • linux x_86_64动态链接,gdb理解link_map参数
  • 内向者和别人聊天缺少共同话题的庖丁解牛