news 2026/2/24 19:11:38

【PB#3】Protobuf 文件 创建 | 编译 | 实现序列化反序列化

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【PB#3】Protobuf 文件 创建 | 编译 | 实现序列化反序列化

📃个人主页:island1314

⛺️ 欢迎关注:👍点赞 👂🏽留言 😍收藏 💞 💞 💞

  • 生活总是不会一帆风顺,前进的道路也不会永远一马平川,如何面对挫折影响人生走向 – 《人民日报》

🔥 目录

    • 前言
    • 一、创建 .proto 文件
      • 1. 文件规范及注释
      • 2. 指定 proto3 语法
      • 3. package 声明符
      • 4. 定义消息(message)
      • 6. 定义消息字段
    • 二、编译 .proto 文件
    • 三、实现序列化与反序列化操作

前言

这里其实主要就是关于之前说过 PB 的特点

  1. 编写 .proto 文件:首先需要 编写 .proto 文件,用于定义结构化的对象(即 message)及其属性内容。
  2. 使用 protoc 编译器编译 .proto 文件:接下来,使用 protoc 编译器编译 .proto 文件,这会生成 一系列接口代码,并存储在新生成的头文件和源文件中。
  3. 依赖生成的接口:将编译生成的头文件包含进我们的代码中,以便实现对 .proto 文件中定义的字段进行设置和获取,以及对 message 对象进行序列化和反序列化。

注意:直接使用 vs code 写 proto 文件没有注释 可以安装一下这个插件 !

我们这篇文章为了快速上手熟悉,因此这里主要做了一个通讯录 1.0 版本,实现如下功能:

  • 对⼀个联系人的信息使用 PB 进行序列化,并将结果打印出来。
  • 对序列化后的内容使用 PB 进行反序列,解析出联系人信息并打印出来。
  • 联系人包含以下信息:姓名、年龄。

一、创建 .proto 文件

1. 文件规范及注释

  • 创建 .proto 文件时,文件命名应该使用全小写字母命名,多个字母之间用 _ 连接。 例如:lower_snake_case.proto 。
  • 书写 .proto 文件代码时,应使用 2 个空格的缩进。

向文件添加注释,可使用://或者/* ... */

现创建如下文件:contacts.proto

2. 指定 proto3 语法

Protocol Buffers 语言版本 3,简称 proto3,是 .proto 文件最新的语法版本。

  • proto3 简化了 Protocol Buffers 语言,既易于使用,又可以在更广泛的编程语言中使用。
  • 它允许我们使用 Java、C++、Python 等多种语言生成 protocol buffer 代码。

在 .proto 文件中,要使用syntax = "proto3"; 来指定文件语法为 proto3,并且必须写在除去注释内容的第一行。

如果没有指定,编译器会使用 proto2 语法。

3. package 声明符

package 是一个可选的声明符,能表示 .proto 文件的命名空间,在项目中要有唯一性。它的作用是为了避免我们定义的消息出现冲突。

举例如下:

// 版本号: 版本号行 package contacts

4. 定义消息(message)

消息(message) :要定义的结构化对象,我们可以给这个结构化对象中定义其对应的属性内容。

为什么要定义消息?

  • 在网络传输中,我们需要为传输双方定制协议。
  • 定制协议说白了就是定义结构体或者结构化数据,比如:TCP,UDP 报文就是结构化的。
  • 再比如将数据持久化存储到数据库时,会将一系列元数据统一用对象组织起来,再进行存储。

所以,ProtoBuf 就是以 message 的方式来支持我们定制协议字段,后期帮助我们形成类和方法来使用

.proto 文件中定义一个消息类型的格式为:

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

6. 定义消息字段

在 message 中可以定义其属性字段,字段定义格式为:字段类型 字段名 = 字段唯一编号;

  • 字段名称命名规范:全小写字母,多个字母之间用_连接
  • 字段类型分为:标量数据类型和特殊类型(包括枚举、其他消息类型等)
  • 字段唯⼀编号:用来标识字段,⼀旦开始使用就不能够再改变(不同字段不能使用相同字段编号)

下面这个表格展示了定义于消息体中的标量数据类型,以及编译 .proto 文件之后自动生成的类中与之对应的字段类型(主要展示了与 C++ 语言对应的类型)

.proto TypeNotesC++ Type
doubledouble
floatfloat
int32使用变长编码[1]。负数的编码效率较低——若字段可能为负值,应用 sint32 代替int32
int64使用变长编码[1]。负数的编码效率较低——若字段可能为负值,应用 sint64 代替int64
uint32使用变长编码[1]uint32
uint64使用变长编码[1]uint64
sint32使用变长编码[1]。符号整型。负数的编码效率高于常规的 int32 类型int32
sint64使用变长编码[1]。符号整型。负数的编码效率高于常规的 int64 类型int64
fixed32定长 4 字节。若值常大于 (2^{28}) 则会比 uint32 更高效uint32
fixed64定长 8 字节。若值常大于 (2^{56}) 则会比 uint64 更高效uint64
sfixed32定长 4 字节int32
sfixed64定长 8 字节int64
boolbool
string包含 UTF-8 和 ASCII 编码的字符串,长度不能超过 (2 32 2^{32}232)string
bytes可包含任意的字节序列但长度不能超过 (2 32 2^{32}232)string
  • 这里[1] 变长编码是指:经过protobuf编码后,原本 4 字节或 8 字节的数可能会被变为其他字节数,但是值不变。

在 contacts.proto 新增联系人,内容如下:

// 定义联系人 message Person{ string name = 1; // 姓名 int32 age = 2; // 年龄 注意 这里name 的标识已经设为 1 了, 这里就不能设为 1 }

在这里还要特别说明一下字段唯一编号的范围:1 ~ 536,870,911(2 29 − 1 2^{29} - 12291

  • 其中 19000 ~ 19999 不可用;原因:在 Protobuf 协议的实现中,对这些数进行了预留。

如果非要在 .proto 文件中使用这些预留标识号,例如将 name 字段的编号设置为 19000,编译时就会报警:

  • 值得一提的是,范围为 1 ~ 15 的字段编号需要一个字节进行编码,16 ~ 2047 内的数字需要两个字节进行编码。
  • 编码后的字节不仅只包含了编号,还包含了字段类型。
  • 所以,1 ~ 15 要用来标记出现非常频繁的字段,要为将来有可能添加的、频繁出现的字段预留一些出来。

二、编译 .proto 文件

编译命令行格式如下:

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

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

lighthouse@VM-8-10-ubuntu:fast_start$ protoc --cpp_out=. contacts.proto# 方式一 . 表示在当前目录下生成lighthouse@VM-8-10-ubuntu:protobuf$ protoc -I fast_start/ --cpp_out=fast_start/ contacts.proto# 方式二lighthouse@VM-8-10-ubuntu:protobuf$lsfast_start contacts.pb.cc contacts.pb.h contacts.proto

此时编译生成了 两个文件,对于 编译生成的 C++ 代码,包含了以下内容:

  • 对于每个message,都会生成一个对应的消息类在消息类中
  • 编译器为每个字段提供了获取和设置方法,以及一下其他能够操作字段的方法。
  • 编辑器会针对于每个 .proto 文件生成 h和 .cc 文件,分别用来存放类的声明与类的实现。

contacts.pb.h部分代码展示如下:

class Person final : public ::PROTOBUF_NAMESPACE_ID::Message /* @@protoc_insertion_point(class_definition:contacts.Person) */ { public: inline Person() : Person(nullptr) {} ~Person() override; explicit PROTOBUF_CONSTEXPR Person(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized); Person(const Person& from); Person(Person&& from) noexcept : Person() { *this = ::std::move(from); } // implements Message ---------------------------------------------- Person* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final { return CreateMaybeMessage<Person>(arena); } using ::PROTOBUF_NAMESPACE_ID::Message::CopyFrom; void CopyFrom(const Person& from); using ::PROTOBUF_NAMESPACE_ID::Message::MergeFrom; void MergeFrom( const Person& from) { Person::MergeImpl(*this, from); } // 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); private: const std::string& _internal_name() const; inline PROTOBUF_ALWAYS_INLINE void _internal_set_name(const std::string& value); std::string* _internal_mutable_name(); public: // int32 age = 2; void clear_age(); int32_t age() const; void set_age(int32_t value); private: int32_t _internal_age() const; void _internal_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 成员函数,因为序列化不会改变类对象的内容,而是将序列化的结果保存到函数入参指定的地址中。
  • 详细 messageAPI 可以参见完整列表,

三、实现序列化与反序列化操作

创建一个测试文件main.cc,方法实现如下:

  • 对一个联系人的信息使用PB进行序列化,并将结果打印出来
  • 对序列化后的内容使用PB进行反序列,解析出联系人信息并打印出来

代码实现如下

#include<iostream>#include"contacts.pb.h"usingnamespacestd;intmain(){string person_str;// 对一个联系人信息使用 PB 进行序列化{// Create a Person object and set its fieldscontacts::Person person;person.set_name("Island");person.set_age(18);if(!person.SerializeToString(&person_str)){cerr<<"Failed to serialize person."<<endl;return1;}cout<<"Serialized person: "<<person_str<<endl;}// 对序列化的内容使用 PB 进行反序列化{contacts::Person person;if(!person.ParseFromString(person_str)){cerr<<"Failed to parse person."<<endl;return1;}cout<<"Parsed person: "<<person.name()<<", age: "<<person.age()<<endl;}return0;}

代码编写完之后,编译main.cc生成可执行文件TestPBperson序列化反序列化结果如下:

lighthouse@VM-8-10-ubuntu:fast_start$ g++ main.cc contacts.pb.cc -o TestPB -std=c++11 -lprotobuf# `lprotobuf`:必须加,避免链接错误lighthouse@VM-8-10-ubuntu:fast_start$ ./TestPB Serialized person: Island Parsed person: Island, age:18

由于ProtoBuf是把联系人对象序列化成了二进制序列,这里用string来作为接收二进制序列的容器。所以在终端打印的时候会有换行等一些乱码显示。

所以相对于xml和JSON来说,因为被编码成二进制,破解成本增大,ProtoBuf编码是相对安全的。

★,°:.☆( ̄▽ ̄)/$:.°★】那么本篇到此就结束啦,如果有不懂 和 发现问题的小伙伴可以在评论区说出来哦,同时我还会继续更新关于【Protobuf】的内容,请持续关注我 !!

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/2/21 16:06:36

Windows更新修复神器:彻底告别系统更新故障的终极方案

Windows更新修复神器&#xff1a;彻底告别系统更新故障的终极方案 【免费下载链接】Reset-Windows-Update-Tool Troubleshooting Tool with Windows Updates (Developed in Dev-C). 项目地址: https://gitcode.com/gh_mirrors/re/Reset-Windows-Update-Tool 当Windows更…

作者头像 李华
网站建设 2026/2/24 2:31:12

B站漫画离线阅读神器:一键下载畅享海量漫画资源

B站漫画离线阅读神器&#xff1a;一键下载畅享海量漫画资源 【免费下载链接】BiliBili-Manga-Downloader 一个好用的哔哩哔哩漫画下载器&#xff0c;拥有图形界面&#xff0c;支持关键词搜索漫画和二维码登入&#xff0c;黑科技下载未解锁章节&#xff0c;多线程下载&#xff0…

作者头像 李华
网站建设 2026/2/24 17:50:18

Windows苹果驱动智能连接突破性方案:告别设备识别难题

你是否曾在Windows系统上连接iPhone时&#xff0c;发现USB网络共享功能完全失效&#xff1f;或者设备只能被识别为简单的媒体播放器&#xff1f;这并非个案&#xff0c;而是数百万苹果用户在Windows环境下的共同困扰。今天&#xff0c;我们将为你揭示一套全新的Windows苹果驱动…

作者头像 李华
网站建设 2026/2/23 18:34:29

Windows 11 LTSC微软商店高效部署实战指南

Windows 11 LTSC微软商店高效部署实战指南 【免费下载链接】LTSC-Add-MicrosoftStore Add Windows Store to Windows 11 24H2 LTSC 项目地址: https://gitcode.com/gh_mirrors/ltscad/LTSC-Add-MicrosoftStore Windows 11 LTSC版本因其稳定性和精简特性受到众多企业用户…

作者头像 李华
网站建设 2026/2/22 14:25:06

CircuitJS1桌面版:零基础玩转离线电路模拟

还在为网络不稳定而影响电路实验进度发愁吗&#xff1f;CircuitJS1桌面版来拯救你的学习效率&#xff01;这款基于NW.js开发的离线电路模拟器&#xff0c;让你彻底摆脱网络依赖&#xff0c;随时随地开启电路探索之旅。 【免费下载链接】circuitjs1 Standalone (offline) versio…

作者头像 李华
网站建设 2026/2/24 3:51:14

终极免费歌词同步方案:快速搞定本地音乐库管理

终极免费歌词同步方案&#xff1a;快速搞定本地音乐库管理 【免费下载链接】lrcget Utility for mass-downloading LRC synced lyrics for your offline music library. 项目地址: https://gitcode.com/gh_mirrors/lr/lrcget 还在为本地音乐缺少同步歌词而烦恼&#xff…

作者头像 李华