ProtoBuf简要介绍与快速上手使用(C++版)

文章目录

  • 一、 初识ProtoBuf
    • 1. 序列化和反序列化概念
    • 2. ProtoBuf是什么
    • 3. ProtoBuf的使用特点
  • 二、 讲解说明
  • 三、 快速上手
    • 1. 创建 .proto 文件
    • 2. 编译 contacts.proto 文件,生成C++文件
    • 3. 序列化与反序列化的使用
    • 4. 小结 ProtoBuf 使用流程


一、 初识ProtoBuf

1. 序列化和反序列化概念

序列化和反序列化

  • 序列化:把对象转换为字节序列的过程 称为对象的序列化。
  • 反序列化:把字节序列恢复为对象的过程 称为对象的反序列化。

什么情况下需要序列化

  • 存储数据:当你想把的内存中的对象状态保存到一个文件中或者存到数据库中时。
  • 网络传输:网络直接传输数据,但是无法直接传输对象,所以要在传输前序列化,传输完成后反序列化成对象。例如我们之前学习过 socket 编程中发送与接收数据。

如何实现序列化

  • xml、json、 protobu

2. ProtoBuf是什么

我们先来看看官方给出的描述:

Protocol buffers are Google’s language-neutral, platform-neutral, extensible mechanism for serializing structured data ‒ think XML, but smaller, faster, and simpler. You define how you want your data to be structured once, then you can use special generated source code to easily write and read your structured data to and from a variety of data streams and using a variety of languages.

翻译过来的意思就是说

  • Protocol Buffers 是 Google 的一种语言无关、平台无关、可扩展的序列化结构数据的方法,它可用于(数据)通信协议、数据存储等。
  • Protocol Buffers 类比于XML,是一种灵活,高效,自动化机制的结构数据序列化方法,但是比XML 更小、更快、更为简单。
  • 你可以定义数据的结构,然后使用特殊生成的源代码轻松的在各种数据流中使用各种语言进行编写和读取结构数据。你甚至可以更新数据结构,而不破坏由旧数据结构编译的已部署程序。

简单来讲,ProtoBuf(全称为 ProtocolBuffer)是让结构数据序列化的方法,其具有以下特点

  • 语言无关、平台无关:即 ProtoBuf支持 Java、C++、Python 等多种语言,支持多个平台。
  • 高效:即比 XML 更小、更快、更为简单。
  • 扩展性、兼容性好:你可以更新数据结构,而不影响和破坏原有的旧程序。

3. ProtoBuf的使用特点

在这里插入图片描述

  1. 编写 .proto 文件,目的是为了定义结构对象(message)及属性内容。
  2. 使用 protoc 编译器编译.proto 文件,生成一系列接口代码,存放在新生成头文件和源文件中。
  3. 依赖生成的接口,将编译生成的头文件包含进我们的代码中,实现对.proto 文件中定义的字段进行设置和获取,和对 message 对象进行序列化和反序列化。

总的来说:ProtoBuf是需要依赖通过编译生成的头文件和源文件来使用的。有了这种代码生成机制,开发人员再也不用吭哧吭哧地编写那些协议解析的代码了(干这种活是典型的吃力不讨好)。

二、 讲解说明

对 ProtoBuf的完整学习,将使用项目推进 的方式完成教学:即对于ProtoBuf知识内容的展开,会对一个项目进行一个版本一个版本的升级去讲解 ProtoBuf 对应的知识点。
在后续的内容中,将会实现一个通讯录项目。对通讯录大家应该都不陌生,一般,通讯录中包含了一批的联系人,每个联系人又会有很多的属性,例如姓名、电话等等。随着对通讯录项目的升级,我们对 ProtoBuf的学习与使用就越深入。

三、 快速上手

在快速上手中,会编写第一版本的通讯录 1.0。在通讯录 1.0 版本中,将实现:

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

通过通讯录 1.0,我们便能了解使用 ProtoBuf初步要掌握的内容,以及体验到 ProtoBuf的完整使用流程。

1. 创建 .proto 文件

文件规范

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

我们为通讯录 1.0 新建文件:contacts.proto

添加注释

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

指定proto3语法

Protocol Buffers 语言版本3,简称 proto3,是.proto 文件最新的语法版本。proto3 简化了 Protoco Buffers 语言,既易于使用,又可以在更广泛的编程语言中使用。它允许你使用 Java,C++,Python等多种语言生成 protocol buffer 代码。
在 .proto 文件中,要使用 syntax=“proto3”;来指定文件语法为 proto3,并且必须写在除去注释内容的第一行。如果没有指定,编译器会使用proto2语法。
在通讯录 1.0 的 contacts.proto 文件中,可以为文件指定 proto3 语法,内容如下:

syntax "proto3";

package声明符

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

package contacts;

在这里插入图片描述

定义消息message

消息(message):要定义的结构化对象,我们可以给这个结构化对象中定义其对应的属性内容。
这里再提一下为什么要定义消息?

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

所以 ProtoBuf 就是以 message 的方式来支持我们定制协议字段,后期帮助我们形成类和方法来使用。
在通讯录 1.0 中我们就需要为 联系人 定义一个 message。

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

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

为 contacts.proto(通讯录 1.0)新增联系⼈message,内容如下:

syntax = "proto3";
package contacts;//定义联系人消息
message PeopleInfo
{}

定义消息字段

在 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。string
bytes可包含任意的字节序列但长度不能超过2^32。string

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

更新 contacts.proto (通讯录 1.0),新增姓名、年龄字段:

syntax = "proto3";
package contacts;//定义联系人消息
message PeopleInfo
{string name = 1;int32 age = 2;
}

在这里插入图片描述

在这里还要特别说明一下字段唯一编号的范围:
1 - 536,870,911(2^29-1),其中19000~19999不可⽤。
19000 - 19999 不可用是因为:在 Protobuf 协议的实现中,对这些数进行了预留。如果非要在.proto文件中使用这些预留标识号,例如将 name 字段的编号设置为19000,编译时就会报警:

// 消息中定义了如下编号,代码会告警: // Field numbers 19,000 through 19,999 are reserved for the protobuf implementation
string name = 19000;

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

2. 编译 contacts.proto 文件,生成C++文件

编译命令

编译命令行格式为:

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文件后会生成什么
编译 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 中,提供了读写消息实例的方法,包括序列化方法和反序列化方法。

class MessageLite {
public://序列化: bool SerializeToOstream(ostream* output) const; // 将序列化后数据写⼊⽂件流 bool SerializeToArray(void *data, int size) const;bool SerializeToString(string* output) const;//反序列化: bool ParseFromIstream(istream* input); // 从流中读取数据,再进⾏反序列化动作 bool ParseFromArray(const void* data, int size);bool ParseFromString(const string& data);
};

注意:
序列化的结果为二进制字节序列,而非文本格式。

  • 以上三种序列化的方法没有本质上的区别,只是序列化后输出的格式不同,可以供不同的应用场景使用。
  • 序列化的 API函数均为const成员函数,因为序列化不会改变类对象的内容,而是将序列化的结果保存到函数入参指定的地址中。
  • 详细 message API 可以参见 完整列表。

3. 序列化与反序列化的使用

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

main.cc

#include<iostream>
#include "contacts.pb.h" // 引⼊编译⽣成的头⽂件
using namespace std;int main()
{// .proto⽂件声明的package,通过protoc编译后,会为编译⽣成的C++代码声明同名的命名空间 // 其范围是在.proto ⽂件中定义的内容 string people_str;{contacts::PeopleInfo people;people.set_age(20);people.set_name("张三");// 调⽤序列化⽅法,将序列化后的⼆进制序列存⼊string中if(!people.SerializeToString(&people_str)){cerr<<"序列化联系人失败"<<endl;}// 打印序列化后的结果cout<<"序列化成功,序列化后的people_str:"<<people_str<<endl;}{// 调⽤反序列化⽅法,读取string中存放的⼆进制序列,并反序列化出对象contacts::PeopleInfo people;if(!people.ParseFromString(people_str)){cerr<<"反序列化联系人失败"<<endl;}// 打印结果cout<<"姓名:"<<people.name()<<endl;cout<<"年龄:"<<people.age()<<endl;}return 0;
}

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

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

• -lprotobuf:必加,不然会有链接错误。
• -std=c++11:必加,使用C++11语法。

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

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

4. 小结 ProtoBuf 使用流程

在这里插入图片描述

  1. 编写 .proto 文件,目的是为了定义结构对象(message)及属性内容。
  2. 使用 protoc编译器编译 .proto 文件,生成一系列接口代码,存放在新生成头文件和源文件中。
  3. 依赖生成的接口,将编译生成的头文件包含进我们的代码中,实现对.proto 文件中定义的字段进行设置和获取,和对 message 对象进行序列化和反序列化。

总的来说:ProtoBuf是需要依赖通过编译生成的头文件和源文件来使用的。有了这种代码生成机制,开发人员再也不用吭哧吭哧地编写那些协议解析的代码了(干这种活是典型的吃力不讨好)。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/408151.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Linux权限维持实战

目录 介绍步骤 介绍 攻击者在获取服务器权限后&#xff0c;会通过一些技巧来隐藏自己的踪迹和后门文件 查看/tmp目录下的flag文件 查看/root目录下具有特殊文件属性的文件 操作机中共有几个SUID文件 操作机中共有几个SGID文件 查看操作机中ssh公私钥免密登陆 查看strace后门 …

Web3链上聚合器声呐已全球上线,开启区块链数据洞察新时代

在全球区块链技术高速发展的浪潮中&#xff0c;在创新发展理念的驱动下&#xff0c;区块链领域的工具类应用备受资本青睐。 2024年8月20日&#xff0c;由生纳&#xff08;香港&#xff09;国际集团倾力打造的一款链上应用工具——“声呐链上聚合器”&#xff0c;即“声呐链上数…

24暑假算法刷题 | Day39 | 动态规划 VII | LeetCode 198. 打家劫舍,213. 打家劫舍 II,337. 打家劫舍 III

目录 198. 打家劫舍题目描述题解 213. 打家劫舍 II题目描述题解 337. 打家劫舍 III题目描述题解 打家劫舍的一天 &#x1f608; 198. 打家劫舍 点此跳转题目链接 题目描述 你是一个专业的小偷&#xff0c;计划偷窃沿街的房屋。每间房内都藏有一定的现金&#xff0c;影响你偷…

贪心+栈。。

前言&#xff1a;这个题目一开始我没想通的就是如果s当前的一个字符或者之后的一个字符和当前t的尾巴是一样的&#xff0c;那么优先选哪一个&#xff0c;其实这个就要优先选t的 class Solution { public:string robotWithString(string s) {string ans;int cnt[26]{}, min 0; …

<C++> 二叉搜索树

目录 二叉搜索树 1. 概念 2. 二叉搜索树操作 2.1 基础结构 2.2 非递归版 1. 查找 2. 插入 3. 删除 2.3 递归版 1. 查找 2. 插入 3. 删除 2.4 拷贝构造函数 2.5 赋值运算符重载 2.6 析构函数 2.7 完整代码 3. 二叉搜索树的应用 4. 二叉搜索树的性能 二叉搜索树 1. 概念 二叉搜索…

SpringBoot+Vue3整合minio,实现分布式文件存储

文章目录 几种常用的文件存储安装和使用minioSpringBoot整合minio 基本所有的软件项目都会需要文件存储功能&#xff0c;图片、视频存储。 几种常用的文件存储 经常用的几种方案&#xff0c;直接存在本地文件夹&#xff0c;开发一个简单的系统当然没有问题。随机系统所需的资源…

微服务多个模块启动,端口被占用,yml配置文件读不到

刚刚提交到gitee自己的仓库&#xff0c;拉下来还是报错&#xff0c;然后看到一个解决方法&#xff1a; <build><resources><resource><directory>src/main/java</directory><includes><include>**/*.yml</include><includ…

推荐一个java低代码开发平台-橙单

文章目录 前言一、项目介绍二、技术选型三、项目特点四、基础功能介绍五、源码下载六、官方文档总结 前言 大家好&#xff0c;今天为大家推荐一个开箱即用&#xff0c;快速开发的低代码平台。项目采用 Boot3 Flowable7 Sa-Token Vue3技术栈。 一、项目介绍 橙单中台化低代…

Datawhale AI 夏令营(第五期) 李宏毅苹果书 Task 1 《深度学习详解(入门)》- 1.1 通过案例了解机器学习

预测本频道观看人数&#xff08;上&#xff09; - 机器学习基本概念简介_哔哩哔哩_bilibili 1 隐藏任务&#xff1a;找出本篇中形如回归&#xff08;regression&#xff09;加粗字体的术语&#xff0c;并用自己的话进行解释&#xff0c;列成表格 术语解释机器学习&#xff08;…

服务器数据恢复—重建RAID失败导致数据丢失的数据恢复案例

服务器数据恢复环境&#xff1a; 某品牌服务器中有一组由4块SAS磁盘做的RAID5磁盘阵列。该服务器操作系统为windows server&#xff0c;运行了一个单节点Oracle&#xff0c;数据存储为文件系统&#xff0c;无归档。该oracle数据库的数据量不大&#xff0c;oracle数据库内只有一…

WPF——动态排名图表实现

开发环境 VS2022 .NET 8.0 MVVM Toolkit 8.2.2 需求 开发中需要实现按照成绩动态指名&#xff0c;以展示当前的竞赛成绩的一个实时情况及变化。 即如下效果&#xff1a; 需求分析 按照接收到的信息&#xff0c;就是要将获取到的集合排序&#xff0c;并且要将排序前后的变…

【AI绘画】Midjourney前置指令/settings设置详解

文章目录 &#x1f4af;Midjourney前置指令/settings设置详解&#x1f4af;Use the default model&#xff08;AI绘画所使用的大模型&#xff09;Midjourney Model&#xff08;Midjourney 模型&#xff09;Niji Model&#xff08;Niji模型&#xff09; &#x1f4af;Midjourney…

外网爆火的LLM应用手册来了!内行人都在学的大模型黑书,豆瓣评分高达9.9!!!

Transformer模型介绍 Transformer 是工业化、同质化的后深度学习模型&#xff0c;其设计目标是能够在高性能计算机(超级计算机)上以并行方式进行计算。通过同质化&#xff0c;一个Transformer 模型可以执行各种任务&#xff0c;而不需要微调。Transformer 使用数十亿参数在数…

【Java数据结构】---二叉树OJ

乐观学习&#xff0c;乐观生活&#xff0c;才能不断前进啊&#xff01;&#xff01;&#xff01; 我的主页&#xff1a;optimistic_chen 我的专栏&#xff1a;c语言 &#xff0c;Java 欢迎大家访问~ 创作不易&#xff0c;大佬们点赞鼓励下吧~ 文章目录 相同的树另一颗树的子树翻…

ES 模糊查询 wildcard 的替代方案探索

一、Wildcard 概述 Wildcard 是一种支持通配符的模糊检索方式。在 Elasticsearch 中&#xff0c;它使用星号 * 代表零个或多个字符&#xff0c;问号 ? 代表单个字符。 其使用方式多样&#xff0c;例如可以通过 {"wildcard": {"field_name": "value&…

docker-compose示例:nacos单机部署

前面咱们完成了docker基本环境搭建&#xff0c;下面就趁热打铁来练习下nacos的单机部署。 参考官方文档&#xff1a;Nacos Docker 快速开始。考虑到官方搭建教程过于精炼&#xff0c;笔者把搭建过程分享给大家。 文章目录 下载最新部署源码解决网络导致的sql文件下不下来docke…

unity AssetBundle 使用_什么是AssetBundle_导入必要的插件_创建AssetBundles_AB包资源下载_大文件下载

一、什么是AssetBundle&#xff1f; 定义AssetBundle。 AssetBundle 是一个存档文件&#xff0c;包含可在运行时由 Unity 加载的特定于平台的非代码资源&#xff08;比如模型、纹理、预制件、音频剪辑甚至整个场景&#xff09;。AssetBundle 可以表示彼此之间的依赖关系&…

防范小程序隐私合规风险,筑牢用户信任防线

随着国内APP软件生态的成熟&#xff0c;依托于头部APP的小程序逐渐成为零售、娱乐、出行等行业必选的获客渠道之一。较低的开发成本和成熟的用户营销功能&#xff0c;令小程序的数量在过去几年呈指数级增长。截止2023年&#xff0c;头部APP内集成的小程序总量已超千万。然而&am…

OpenCV(开源计算机视觉库)

OpenCV&#xff08;开源计算机视觉库&#xff09;是一个专注于实时计算机视觉的全面库&#xff0c;包含了丰富的工具和功能。以下是 OpenCV 中一些关键知识点的详细列表&#xff1a; 核心功能 基本结构&#xff1a;Mat、Scalar、Point、Size、Rect 等。 图像 I/O&#xff1a;读…

Latex 插入图片或表格导致页面空白过多

如图所示&#xff1a; Latex 插入图片或表格导致页面空白过多 我们可以采用这个方式来减少空白。 \documentclass{article} \usepackage{graphicx} % 包含图形支持 \usepackage{caption} % 提供更多对caption的控制% 设置标题上方和下方的间距 \setlength{\abovecaptionskip}{…