百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术分析 > 正文

谷歌最流行的序列化格式:Protobuf 语言指南

liebian365 2024-10-24 14:38 7 浏览 0 评论

什么是 Protobuf

Protobuf是Protocol Buffers的简称,它是Google公司开发的一种数据描述语言,用于描述一种轻便高效的结构化数据存储格式,并于2008年对外开源。Protobuf可以用于结构化数据串行化,或者说序列化。它的设计非常适用于在网络通讯中的数据载体,很适合做数据存储或 RPC 数据交换格式,它序列化出来的数据量少再加上以 K-V 的方式来存储数据,对消息的版本兼容性非常强,可用于通讯协议、数据存储等领域的语言无关、平台无关、可扩展的序列化结构数据格式。开发者可以通过Protobuf附带的工具生成代码并实现将结构化数据序列化的功能。

Protobuf中最基本的数据单元是message,是类似Go语言中结构体的存在。在message中可以嵌套message或其它的基础数据类型的成员。

教程中将描述如何用protocol buffer语言构造你的protocol buffer数据,包括.proto文件的语法以及如何通过.proto文件生成数据访问类。教程中使用的是proto3版本的protocol buffer语言。

定义Message

首先看一个简单的例子,比如说你定义一个搜索请求的message,每一个搜索请求会包含一个搜索的字符串,返回第几页的结果,以及结果集的大小。在.proto文件中定义如下:

  • .proto文件的第一行指定了使用proto3语法。如果省略protocol buffer编译器默认使用proto2语法。他必须是文件中非空非注释行的第一行。
  • SearchRequest定义中指定了三个字段(name/value键值对),每个字段都会有名称和类型。

指定字段类型

上面的例子中,所有的字段都是标量类型的两个整型(page_number和result_per_page)和一个字符串型(query)。不过你还可以给字段指定复合类型,包括枚举类型和其他message类型

指定字段编号

在message定义中每个字段都有一个唯一的编号,这些编号被用来在二进制消息体中识别你定义的这些字段,一旦你的message类型被用到后就不应该在修改这些编号了。注意在将message编码成二进制消息体时字段编号1-15将会占用1个字节,16-2047将占用两个字节。所以在一些频繁使用用的message中,你应该总是先使用前面1-15字段编号。

你可以指定的最小编号是1,最大是2E29 - 1(536,870,911)。其中19000到19999是给protocol buffers实现保留的字段标号,定义message时不能使用。同样的你也不能重复使用任何当前message定义里已经使用过和预留的字段编号。

定义字段的规则

message的字段必须符合以下规则:

  • singular:一个遵循singular规则的字段,在一个结构良好的message消息体(编码后的message)可以有0或1个该字段(但是不可以有多个)。这是proto3语法的默认字段规则。(这个理解起来有些晦涩,举例来说上面例子中三个字段都是singular类型的字段,在编码后的消息体中可以有0或者1个query字段,但不会有多个。)
  • repeated:遵循repeated规则的字段在消息体重可以有任意多个该字段值,这些值的顺序在消息体重可以保持(就是数组类型的字段)

添加更多消息类型

在单个.proto文件中可以定义多个message,这在定义多个相关message时非常有用。比如说,我们定义SearchRequest对应的响应message SearchResponse ,把它加到之前的.proto文件中。

添加注释

.proto文件中的注释和C,C++的注释风格相同,使用// 和 / ... /

/* SearchRequest represents a search query, with pagination options to
* indicate which results to include in the response. */
message SearchRequest {
string query = 1;
int32 page_number = 2; // Which page number do we want?
int32 result_per_page = 3; // Number of results to return per page.
}

保留字段

当你删掉或者注释掉message中的一个字段时,未来其他开发者在更新message定义时就可以重用之前的字段编号。如果他们意外载入了老版本的.proto文件将会导致严重的问题,比如数据损坏、隐私泄露等。一种避免问题发生的方式是指定保留的字段编号和字段名称。如果未来有人用了这些字段标识那么在编译时protocol buffer的编译器会报错。

proto会生成什么代码

当使用protocol buffer编译器编译.proto文件时,编译器会根据你在.proto文件中定义的message类型生成指定编程语言的代码。生成的代码包括访问和设置字段值、格式化message类型到输出流,从输入流解析出message等。

  • For C++, the compiler generates a .h and .cc file from each .proto, with a class for each message type described in your file.
  • For Java, the compiler generates a .java file with a class for each message type, as well as a special Builderclasses for creating message class instances.
  • Python is a little different – the Python compiler generates a module with a static descriptor of each message type in your .proto, which is then used with a metaclass to create the necessary Python data access class at runtime.
  • For Go, the compiler generates a .pb.go file with a type for each message type in your file.
  • For Ruby, the compiler generates a .rb file with a Ruby module containing your message types.
  • For Objective-C, the compiler generates a pbobjc.h and pbobjc.m file from each .proto, with a class for each message type described in your file.
  • For C#, the compiler generates a .cs file from each .proto, with a class for each message type described in your file.
  • For Dart, the compiler generates a .pb.dart file with a class for each message type in your file.

标量类型

默认值

当时一个被编码的message体中不存在某个message定义中的singular字段时,在message体解析成的对象中,相应字段会被设置为message定义中该字段的默认值。默认值依类型而定:

  • 对于字符串,默认值为空字符串。
  • 对于字节,默认值为空字节。
  • 对于bools,默认值为false。
  • 对于数字类型,默认值为零。
  • 对于枚举,默认值是第一个定义的枚举值,该值必须为0。
  • 对于消息字段,未设置该字段。它的确切值取决于语言。有关详细信息,请参阅代码生成指南。

枚举类型

在定义消息类型时,您可能希望其中一个字段只有一个预定义的值列表中的值。例如,假设您要为每个SearchRequest添加corpus字段,其中corpus可以是UNIVERSAL,WEB,IMAGES,LOCAL,NEWS,PRODUCTS或VIDEO。您可以非常简单地通过向消息定义添加枚举,并为每个可能的枚举值值添加常量来实现。

在下面的例子中,我们添加了一个名为Corpus的枚举类型,和一个Corpus类型的字段:

如你所见,Corpus枚举的第一个常量映射到了0:所有枚举定义都需要包含一个常量映射到0并且作为定义的首行,这是因为:

  • 必须有0值,这样我们就可以将0作为枚举的默认值。
  • proto2语法中首行的枚举值总是默认值,为了兼容0值必须作为定义的首行。

使用其他Message类型

可以使用其他message类型作为字段的类型,假设你想在每个SearchResponse消息中携带类型为Result的消息,

你可以在同一个.proto文件中定义一个Result消息类型,然后在SearchResponse中指定一个Result类型的字段。

导入消息定义

在上面的示例中,Result消息类型在与SearchResponse相同的文件中定义 - 如果要用作字段类型的消息类型已在另一个.proto文件中定义,该怎么办?

您可以通过导入来使用其他.proto文件中的定义。要导入另一个.proto的定义,请在文件顶部添加一个import语句:

import "myproject/other_protos.proto";

默认情况下,您只能使用直接导入的.proto文件中的定义。但是,有时你可能需要将.proto文件移动到新位置。现在,你可以在旧位置放置一个虚拟.proto文件,在文件中使用import public语法将所有导入转发到新位置,而不是直接移动.proto文件并在一次更改中更新所有调用点。任何导入包含import public语句的proto文件的人都可以传递依赖导入公共依赖项。例如

编译器会在通过命令行参数-I或者--proto-path中指定的文件夹中搜索.proto文件,如果没有提供编译器会在唤其编译器的目录中进行搜索。通常来说你应该将--proto-path的值设置为你项目的根目录,并对所有导入使用完全限定名称。

使用proto2的消息类型

可以导入proto2版本的消息类型到proto3的消息类型中使用,当然也可以在proto2消息类型中导入proto3的消息类型。但是proto2的枚举类型不能直接应用到proto3的语法中。

嵌套消息类型

消息类型可以被定义和使用在其他消息类型中,下面的例子里Result消息被定义在SearchResponse消息中

如果你想在外部使用定义在父消息中的子消息,使用Parent.Type引用他们

你可以嵌套任意多层消息

更新Message

如果一个现存的消息类型不再满足你当前的需求--比如说你希望在消息中增加一个额外的字段--但是仍想使用由旧版的消息格式生成的代码,不用担心!只要记住下面的规则,在更新消息定义的同时又不破坏现有的代码就非常简单。

  • 不要更改任何已存字段的字段编号。
  • 如果添加了新字段,任何由旧版消息格式生成的代码所序列化的消息,仍能被依据新消息格式生成的代码所解析。你应该记住这些元素的默认值这些新生成的代码就能够正确地与由旧代码序列化创建的消息交互了。类似的,新代码创建的消息也能由旧版代码解析:旧版消息(二进制)在解析时简单地忽略了新增的字段,查看下面的未知字段章节了解更多。
  • 只要在更新后的消息类型中不再重用字段编号,就可以删除该字段。你也可以重命名字段,比如说添加OBSOLETE_前缀或者将字段编号设置为reserved,这些未来其他用户就不会意外地重用该字段编号了。

未知字段

未知字段是格式良好的协议缓冲区序列化数据,表示解析器无法识别的字段。例如,当旧二进制文件解析具有新字段的新二进制文件发送的数据时,这些新字段将成为旧二进制文件中的未知字段。

最初,proto3消息在解析期间总是丢弃未知字段,但在3.5版本中,我们重新引入了未知字段的保留以匹配proto2行为。在版本3.5及更高版本中,未知字段在解析期间保留,并包含在序列化输出中。

映射类型

如果你想创建一个映射作为message定义的一部分,protocol buffers提供了一个简易便利的语法

map<key_type, value_type> map_field = N;

key_type可以是任意整数或者字符串(除了浮点数和bytes以外的所有标量类型)。注意enum不是一个有效的key_type。value_type可以是除了映射以外的任意类型(意思是protocol buffers的消息体中不允许有嵌套map)。

举例来说,假如你想创建一个名为projects的映射,每一个Project消息关联一个字符串键,你可以像如下来定义:

map<string, Project> projects = 3;
  • 映射里的字段不能是follow repeated规则的(意思是映射里字段的值不能是数组)。
  • 映射里的值是无序的,所以不能依赖映射里元素的顺序。
  • 生成.proto的文本格式时,映射按键排序。数字键按数字排序。
  • 从线路解析或合并时,如果有重复的映射键,则使用最后看到的键。从文本格式解析映射时,如果存在重复键,则解析可能会失败。
  • 如果未给映射的字段指定值,字段被序列化时的行为依语言而定。在C++, Java和Python中字段类型的默认值会被序列化作为字段值,而其他语言则不会。

给Message加包名

你可以在.proto文件中添加一个可选的package符来防止消息类型之前的名称冲突。

在定义message的字段时像如下这样使用package名称

package符对生成代码的影响视编程语言而定

定义服务

如果想消息类型与RPC(远程过程调用)系统一起使用,你可以在.proto文件中定义一个RPC服务接口,然后protocol buffer编译器将会根据你选择的编程语言生成服务接口代码和stub,加入你要定义一个服务,它的一个方法接受SearchRequest消息返回SearchResponse消息,你可以在.proto文件中像如下示例这样定义它:

与protocol buffer 一起使用的最简单的RPC系统是gRPC:一种由Google开发的语言和平台中立的开源RPC系统。 gRPC特别适用于protocol buffer,并允许您使用特殊的protocol buffer编译器插件直接从.proto文件生成相关的RPC代码。

如果你不想使用gRPC,可以使用自己实现的RPC系统,更多关于实现RPC系统的细节可以在Proto2 Language Guide中找到。

JSON编解码

Proto3支持JSON中的规范编码,使得在系统之间共享数据变得更加容易。在下表中逐个类型地列出了编码规则。

如果JSON编码数据中缺少某个值,或者其值为null,则在解析为protocol buffer时,它将被解释为相应的默认值。如果字段在protocol buffer中具有默认值,则默认情况下将在JSON编码的数据中省略该字段以节省空间。编写编解码实现可以覆盖这个默认行为在JSON编码的输出中保留具有默认值的字段的选项。

生成代码

要生成Java,Python,C ++,Go,Ruby,Objective-C或C#代码,你需要使用.proto文件中定义的消息类型,你需要在.proto上运行protocol buffer编译器protoc。如果尚未安装编译器,请下载该软件包并按照README文件中的说明进行操作。对于Go,还需要为编译器安装一个特殊的代码生成器插件:你可以在GitHub上的golang/protobuf项目中找到这个插件和安装说明。

编译器像下面这样唤起:

  • IMPORT_PATH指定了在解析import命令时去哪里搜索.proto文件,如果忽略将在当前工作目录进行查找,可以通过传递多次--proto-path参数来指定多个import目录,他们将会按顺序被编译器搜索。-I=IMPORT_PATH是--proto_path的简短形式。
  • 你可以提供一个或多个输出命令:
  • --cpp_out generates C++ code in DST_DIR. See the C++ generated code referencefor more.
  • --java_out generates Java code in DST_DIR. See the Java generated code referencefor more.
  • --python_out generates Python code in DST_DIR. See the Python generated code reference for more.
  • --go_out generates Go code in DST_DIR. See the Go generated code reference for more.
  • --ruby_out generates Ruby code in DST_DIR. Ruby generated code reference is coming soon!
  • --objc_out generates Objective-C code in DST_DIR. See the Objective-C generated code reference for more.
  • --csharp_out generates C# code in DST_DIR. See the C# generated code referencefor more.
  • --php_out generates PHP code in DST_DIR. See the PHP generated code referencefor more.
  • 必须提供一个或多个.proto文件作为输入。可以一次指定多个.proto文件。虽然文件是相对于当前目录命名的,但每个文件必须存在于其中一个IMPORT_PATH中,以便编译器可以确定其规范名称。

原文链接:https://segmentfault.com/a/1190000020386857

本文作者:KevinYan,原创授权发布

相关推荐

快递查询教程,批量查询物流,一键管理快递

作为商家,每天需要查询许许多多的快递单号,面对不同的快递公司,有没有简单一点的物流查询方法呢?小编的回答当然是有的,下面随小编一起来试试这个新技巧。需要哪些工具?安装一个快递批量查询高手快递单号怎么快...

一键自动查询所有快递的物流信息 支持圆通、韵达等多家快递

对于各位商家来说拥有一个好的快递软件,能够有效的提高自己的工作效率,在管理快递单号的时候都需要对单号进行表格整理,那怎么样能够快速的查询所有单号信息,并自动生成表格呢?1、其实方法很简单,我们不需要一...

快递查询单号查询,怎么查物流到哪了

输入单号怎么查快递到哪里去了呢?今天小编给大家分享一个新的技巧,它支持多家快递,一次能查询多个单号物流,还可对查询到的物流进行分析、筛选以及导出,下面一起来试试。需要哪些工具?安装一个快递批量查询高手...

3分钟查询物流,教你一键批量查询全部物流信息

很多朋友在问,如何在短时间内把单号的物流信息查询出来,查询完成后筛选已签收件、筛选未签收件,今天小编就分享一款物流查询神器,感兴趣的朋友接着往下看。第一步,运行【快递批量查询高手】在主界面中点击【添...

快递单号查询,一次性查询全部物流信息

现在各种快递的查询方式,各有各的好,各有各的劣,总的来说,还是有比较方便的。今天小编就给大家分享一个新的技巧,支持多家快递,一次能查询多个单号的物流,还能对查询到的物流进行分析、筛选以及导出,下面一起...

快递查询工具,批量查询多个快递快递单号的物流状态、签收时间

最近有朋友在问,怎么快速查询单号的物流信息呢?除了官网,还有没有更简单的方法呢?小编的回答当然是有的,下面一起来看看。需要哪些工具?安装一个快递批量查询高手多个京东的快递单号怎么快速查询?进入快递批量...

快递查询软件,自动识别查询快递单号查询方法

当你拥有多个快递单号的时候,该如何快速查询物流信息?比如单号没有快递公司时,又该如何自动识别再去查询呢?不知道如何操作的宝贝们,下面随小编一起来试试。需要哪些工具?安装一个快递批量查询高手快递单号若干...

教你怎样查询快递查询单号并保存物流信息

商家发货,快递揽收后,一般会直接手动复制到官网上一个个查询物流,那么久而久之,就会觉得查询变得特别繁琐,今天小编给大家分享一个新的技巧,下面一起来试试。教程之前,我们来预览一下用快递批量查询高手...

简单几步骤查询所有快递物流信息

在高峰期订单量大的时候,可能需要一双手当十双手去查询快递物流,但是由于逐一去查询,效率极低,追踪困难。那么今天小编给大家分享一个新的技巧,一次能查询多个快递单号的物流,下面一起来学习一下,希望能给大家...

物流单号查询,如何查询快递信息,按最后更新时间搜索需要的单号

最近有很多朋友在问,如何通过快递单号查询物流信息,并按最后更新时间搜索出需要的单号呢?下面随小编一起来试试吧。需要哪些工具?安装一个快递批量查询高手快递单号若干怎么快速查询?运行【快递批量查询高手】...

连续保存新单号功能解析,导入单号查询并自动识别批量查快递信息

快递查询已经成为我们日常生活中不可或缺的一部分。然而,面对海量的快递单号,如何高效、准确地查询每一个快递的物流信息,成为了许多人头疼的问题。幸运的是,随着科技的进步,一款名为“快递批量查询高手”的软件...

快递查询教程,快递单号查询,筛选更新量为1的单号

最近有很多朋友在问,怎么快速查询快递单号的物流,并筛选出更新量为1的单号呢?今天小编给大家分享一个新方法,一起来试试吧。需要哪些工具?安装一个快递批量查询高手多个快递单号怎么快速查询?运行【快递批量查...

掌握批量查询快递动态的技巧,一键查找无信息记录的两种方法解析

在快节奏的商业环境中,高效的物流查询是确保业务顺畅运行的关键。作为快递查询达人,我深知时间的宝贵,因此,今天我将向大家介绍一款强大的工具——快递批量查询高手软件。这款软件能够帮助你批量查询快递动态,一...

从复杂到简单的单号查询,一键清除单号中的符号并批量查快递信息

在繁忙的商务与日常生活中,快递查询已成为不可或缺的一环。然而,面对海量的单号,逐一查询不仅耗时费力,还容易出错。现在,有了快递批量查询高手软件,一切变得简单明了。只需一键,即可搞定单号查询,一键处理单...

物流单号查询,在哪里查询快递

如果在快递单号多的情况,你还在一个个复制粘贴到官网上手动查询,是一件非常麻烦的事情。于是乎今天小编给大家分享一个新的技巧,下面一起来试试。需要哪些工具?安装一个快递批量查询高手快递单号怎么快速查询?...

取消回复欢迎 发表评论: