当前位置:   article > 正文

【ProtoBuf】ProtoBuf 快速上手

【ProtoBuf】ProtoBuf 快速上手

在通讯录 1.0 版本中,将实现以下功能:

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

一、步骤 1:创建 .proto 文件

1、文件规范

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

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


2、添加注释

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


3、指定 proto3 语法

Protocol Buffers 语言版本 3,简称 proto3,是 .proto 文件最新的语法版本。proto3 简化了 Protocol Buffers 语言,既易于使用,又可以在更⼴泛的编程语⾔中使⽤。它允许我们使用 Java、C++、Python 等多种语言生成 protocol buffer 代码。

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

在通讯录 1.0 的 contacts.proto 文件中,可以为文件指定 proto3 语法,内容如下:


4、package 声明符

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

在通讯录 1.0 的 contacts.proto 文件中,可以声明其命名空间,内容如下:


5、定义消息(message)

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

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

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

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

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

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


6、定义消息字段

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

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

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

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

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

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

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


二、步骤 2:编译 contacts.proto 文件,生成 C++ 文件

1、编译命令

编译命令行格式为: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 文件命令如下:


2、编译 contacts.proto 文件后会生成什么

通过上图可以看到:编译 contacts.proto 文件后,会生成所选择语言的代码,我们选择的是 C++,所以编译后生成了两个文件:contacts.pb.h 和 contacts.pb.cc

对于编译生成的 C++ 代码,包含了以下内容:

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

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

上述的例子中:
  • 每个字段都有设置和获取的方法, getter 的名称与小写字段完全相同,setter 方法以 set_ 开头。
  • 每个字段都有⼀个 clear_ 方法,可以将字段重新设置回 empty 状态。

contacts.pb.cc 中的代码就是对类声明⽅法的⼀些实现(展示部分):

之前提到的序列化和反序列化方法在哪里呢?
在消息类的父类 MessageLite 中,提供了读写消息实例的方法,包括序列化方法和反序列化方法。

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

三、步骤 3:序列化与反序列化的使用

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

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

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

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

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

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Li_阴宅/article/detail/868260
推荐阅读
相关标签
  

闽ICP备14008679号