语言指南(proto3)
本指南介绍了如何使用协议缓冲区语言来构造协议缓冲区数据,包括.proto
文件语法以及如何从.proto
文件生成数据访问类。它涵盖了协议缓冲区语言的proto3版本:有关proto2语法的信息,请参见《Proto2语言指南》。
这是参考指南–有关使用本文档中描述的许多功能的分步示例,请参见所选择语言的教程(当前仅适用于proto2;更多proto3文档即将发布)。
定义消息类型
首先,让我们看一个非常简单的示例。假设您要定义一个搜索请求消息格式,其中每个搜索请求都有一个查询字符串,您感兴趣的特定页面结果以及每页结果数。这是.proto
用于定义消息类型的文件。
syntax = "proto3";
message SearchRequest {
string query = 1;
int32 page_number = 2;
int32 result_per_page = 3;
}
- 文件的第一行指定您正在使用
proto3
语法:如果不这样做,则协议缓冲区编译器将假定您正在使用proto2。这必须是文件的第一行非空,非注释行。 - 所述
SearchRequest
消息定义指定了三个字段(名称/值对),一个用于每条数据要在此类型的消息包括。每个字段都有一个名称和类型。
指定字段类型
在上面的示例中,所有字段都是标量类型:两个整数(page_number
和result_per_page
)和一个字符串(query
)。但是,您也可以为字段指定复合类型,包括枚举和其他消息类型。
分配字段编号
如您所见,消息定义中的每个字段都有一个唯一的编号。这些字段号用于标识消息二进制格式的字段,一旦使用了消息类型,就不应更改它们。请注意,范围为1到15的字段号需要一个字节来编码,包括字段号和字段的类型(您可以在Protocol Buffer Encoding中找到有关此内容的更多信息)。16到2047之间的字段编号占用两个字节。因此,您应该为经常出现的消息元素保留数字1到15。记住要留出一些空间,以便将来可能会添加一些经常出现的元素。
您可以指定最小的场数是1,最大为2 29日- 1,或536870911。您也不能使用数字19000到19999(FieldDescriptor::kFirstReservedNumber
至FieldDescriptor::kLastReservedNumber
),因为它们是为Protocol Buffers实现保留的-如果您在中使用这些保留数之一,则协议缓冲区编译器会抱怨.proto
。同样,您不能使用任何以前保留的字段号。
指定字段规则
消息字段可以是以下之一:
- 单数:格式正确的邮件可以包含零个或一个此字段(但不能超过一个)。这是proto3语法的默认字段规则。
repeated
:在格式正确的消息中,此字段可以重复任意次(包括零次)。重复值的顺序将保留。
在proto3中,repeated
标量数字类型的字段packed
默认情况下使用编码。
您可以packed
在协议缓冲区编码中找到有关编码的更多信息。
添加更多消息类型
可以在单个.proto
文件中定义多种消息类型。如果您要定义多个相关消息,这将非常有用–例如,如果您想定义与您的SearchResponse
消息类型相对应的回复消息格式,则可以将其添加到相同的消息中.proto
:
message SearchRequest {
string query = 1;
int32 page_number = 2;
int32 result_per_page = 3;
}
message SearchResponse {
...
}
添加评论
要将注释添加到.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.
}
保留字段
如果您通过完全删除字段或将其注释掉来更新消息类型,将来的用户可以在自己对类型进行更新时重用该字段号。如果他们以后加载相同版本的旧版本,可能会导致严重的问题.proto
,包括数据损坏,隐私错误等。确保不会发生这种情况的一种方法是,将已删除字段的字段编号(和/或名称,也可能导致JSON序列化的问题)指定为reserved
。如果将来有任何用户尝试使用这些字段标识符,则协议缓冲区编译器会抱怨。
message Foo {
reserved 2, 15, 9 to 11;
reserved "foo", "bar";
}
请注意,您不能在同reserved
一条语句中混用字段名称和字段编号。
您产生了什么.proto
?
在上运行协议缓冲区编译器时.proto
,编译器会以您选择的语言生成代码,您将需要使用文件中描述的消息类型,包括获取和设置字段值,将消息序列化为输出流,并从输入流中解析消息。
- 对于C ++,编译器从每个生成一个
.h
和.cc
文件.proto
,并为文件中描述的每种消息类型提供一个类。 - 对于Java,编译器会生成一个
.java
文件,其中包含每种消息类型的类以及Builder
用于创建消息类实例的特殊类。 - Python稍有不同-Python编译器会在您的中生成一个模块,其中包含每种消息类型的静态描述符,
.proto
然后将该模块与元类一起使用,以在运行时创建必要的Python数据访问类。 - 对于Go,编译器会为
.pb.go
文件中的每种消息类型生成一个具有相应类型的文件。 - 对于Ruby,编译器将
.rb
使用包含您的消息类型的Ruby模块生成一个文件。 - 对于Objective-C,编译器会从每个生成一个
pbobjc.h
和pbobjc.m
文件.proto
,并为文件中描述的每种消息类型提供一个类。 - 对于C#,编译器会
.cs
从each生成一个文件.proto
,并为文件中描述的每种消息类型提供一个类。 - 对于Dart,编译器会为
.pb.dart
文件中的每种消息类型生成一个带有类的文件。
您可以按照所选语言的教程(即将推出proto3版本)找到有关每种语言使用API的更多信息。有关API的更多详细信息,请参见相关的API参考(proto3版本也即将推出)。
标量值类型
标量消息字段可以具有以下类型之一-该表显示.proto
文件中指定的类型,以及自动生成的类中的相应类型:
.proto类型 | 笔记 | C ++类型 | Java类型 | Python类型[2] | 去类型 | 红宝石类型 | C#类型 | PHP类型 | 飞镖类型 |
---|---|---|---|---|---|---|---|---|---|
双 | 双 | 双 | 浮动 | float64 | 浮动 | 双 | 浮动 | 双 | |
浮动 | 浮动 | 浮动 | 浮动 | float32 | 浮动 | 浮动 | 浮动 | 双 | |
int32 | 使用可变长度编码。负数编码效率低下–如果您的字段可能具有负值,请改用sint32。 | int32 | 整型 | 整型 | int32 | Fixnum或Bignum(根据需要) | 整型 | 整数 | 整型 |
int64 | 使用可变长度编码。负数编码效率低下–如果您的字段可能具有负值,请改用sint64。 | int64 | 长 | int / long [3] | int64 | 比格纳姆 | 长 | 整数/字符串[5] | 整数64 |
uint32 | 使用可变长度编码。 | uint32 | 整数[1] | int / long [3] | uint32 | Fixnum或Bignum(根据需要) | int | 整数 | 整型 |
uint64 | 使用可变长度编码。 | uint64 | 长[1] | int / long [3] | uint64 | 比格纳姆 | 乌龙 | 整数/字符串[5] | 整数64 |
sint32 | 使用可变长度编码。有符号的int值。与常规int32相比,它们更有效地编码负数。 | int32 | 整型 | 整型 | int32 | Fixnum或Bignum(根据需要) | 整型 | 整数 | 整型 |
sint64 | 使用可变长度编码。有符号的int值。与常规int64相比,它们更有效地编码负数。 | int64 | 长 | int / long [3] | int64 | 比格纳姆 | 长 | 整数/字符串[5] | 整数64 |
固定的 | 始终为四个字节。如果值通常大于2 28,则比uint32更有效。 | uint32 | 整数[1] | int / long [3] | uint32 | Fixnum或Bignum(根据需要) | int | 整数 | 整型 |
固定64 | 始终为八个字节。如果值通常大于2 56,则比uint64更有效。 | uint64 | 长[1] | int / long [3] | uint64 | 比格纳姆 | 乌龙 | 整数/字符串[5] | 整数64 |
固定32 | 始终为四个字节。 | int32 | 整型 | 整型 | int32 | Fixnum或Bignum(根据需要) | 整型 | 整数 | 整型 |
固定的 | 始终为八个字节。 | int64 | 长 | int / long [3] | int64 | 比格纳姆 | 长 | 整数/字符串[5] | 整数64 |
布尔 | 布尔 | 布尔值 | 布尔 | 布尔 | TrueClass / FalseClass | 布尔 | 布尔值 | 布尔 | |
串 | 字符串必须始终包含UTF-8编码或7位ASCII文本,并且不能超过2 32。 | 串 | 串 | str / unicode [4] | 串 | 字串(UTF-8) | 串 | 串 | 串 |
个字节 | 可以包含不超过2 32的任意字节序列。 | 串 | 字节串 | 力量 | []字节 | 字串(ASCII-8BIT) | 字节串 | 串 | 清单 |
当在Protocol Buffer Encoding中对消息进行序列化时,您可以找到更多有关如何编码这些类型的信息。
[1]在Java中,无符号的32位和64位整数使用带符号的对等体表示,最高位仅存储在符号位中。
[2]在所有情况下,将值设置为字段都会执行类型检查以确保其有效。
[3] 64位或无符号32位整数在解码时始终表示为long,但如果在设置字段时给出int,则可以为int。在所有情况下,该值都必须适合设置时表示的类型。参见[2]。
[4] Python字符串在解码时表示为unicode,但如果给出了ASCII字符串,则可以为str(此字符串可能会发生变化)。
[5]在64位计算机上使用Integer,在32位计算机上使用string。
默认值
解析消息时,如果编码的消息不包含特定的单数元素,则已解析对象中的相应字段将设置为该字段的默认值。这些默认值是特定于类型的:
- 对于字符串,默认值为空字符串。
- 对于字节,默认值为空字节。
- 对于布尔值,默认值为false。
- 对于数字类型,默认值为零。
- 对于枚举,默认值为第一个定义的枚举值,必须为0。
- 对于消息字段,未设置该字段。它的确切值取决于语言。有关详细信息,请参见生成的代码指南。
重复字段的默认值为空(通常为相应语言的空列表)。
请注意,对于标量消息字段,一旦解析了一条消息,就无法告诉该字段是显式设置为默认值(例如,布尔值是否设置为false
)还是根本没有设置:您应该牢记这一点在定义消息类型时。例如,false
如果您不希望默认情况下也发生这种行为,则在设置为时,没有布尔值会打开某些行为。还要注意的是,如果一个标消息字段被设置为默认值,该值将不会在电线上连载。
有关默认值在生成的代码中如何工作的更多详细信息,请参见所选语言的生成的代码指南。
枚举
在定义消息类型时,您可能希望其一个字段仅具有一个预定义的值列表之一。例如,假设你想添加一个corpus
字段每个SearchRequest
,其中语料库可以UNIVERSAL
,WEB
,IMAGES
,LOCAL
,NEWS
,PRODUCTS
或VIDEO
。您可以通过enum
在消息定义中为每个可能的值添加一个常量来非常简单地完成此操作。
在下面的示例中,我们添加了一个带有所有可能值的enum
被叫项Corpus
,以及一个type字段Corpus
:
message SearchRequest {
string query = 1;
int32 page_number = 2;
int32 result_per_page = 3;
enum Corpus {
UNIVERSAL = 0;
WEB = 1;
IMAGES = 2;
LOCAL = 3;
NEWS = 4;
PRODUCTS = 5;
VIDEO = 6;
}
Corpus corpus = 4;
}
如您所见,Corpus
枚举的第一个常量映射为零:每个枚举定义必须包含一个映射为零的常量作为其第一个元素。这是因为:
您可以通过将相同的值分配给不同的枚举常量来定义别名。为此,您需要将该allow_alias
选项设置为true
,否则协议别名会在找到别名时生成一条错误消息。
message MyMessage1 {
enum EnumAllowingAlias {
option allow_alias = true;
UNKNOWN = 0;
STARTED = 1;
RUNNING = 1;
}
}
message MyMessage2 {
enum EnumNotAllowingAlias {
UNKNOWN = 0;
STARTED = 1;
// RUNNING = 1; // Uncommenting this line will cause a compile error inside Google and a warning message outside.
}
}
枚举器常量必须在32位整数范围内。由于enum
值在电线上使用varint编码,因此负值效率低下,因此不建议使用。您可以enum
在消息定义内定义,如上面的示例所示,enum
也可以在外部定义-这些s可以在.proto
文件中的任何消息定义中重复使用。您还可以使用enum
语法将一条消息中声明的类型用作另一条消息中字段的类型_MessageType_._EnumType_
。
当运行在所述协议缓冲编译器.proto
,它使用一个enum
,生成的代码将具有对应enum
于Java或C ++,一个特殊EnumDescriptor
的是被用于创建一组与在所述运行时生成的类的整数值的符号常数的Python类。
**注意:**生成的代码可能会受到特定于语言的枚举数限制(一种语言的成千上万个)。请查看您计划使用的语言的限制。
在反序列化期间,无法识别的枚举值将保留在消息中,尽管在反序列化消息时如何表示该值取决于语言。在支持具有超出指定符号范围的值的开放式枚举类型的语言(例如C ++和Go)中,未知的枚举值仅存储为其基础整数表示形式。在具有封闭枚举类型的语言(例如Java)中,枚举中的大小写用于表示无法识别的值,并且可以使用特殊的访问器访问基础整数。无论哪种情况,如果消息被序列化,则无法识别的值仍将与消息一起序列化。
有关如何enum
在应用程序中使用message的更多信息,请参见针对所选语言生成的代码指南。
保留值
如果通过完全删除枚举条目或将其注释掉来更新枚举类型,则将来的用户在自己对类型进行更新时可以重用数值。如果他们以后加载相同版本的旧版本,可能会导致严重的问题.proto
,包括数据损坏,隐私错误等。确保不会发生这种情况的一种方法是,将已删除的条目的数字值(和/或名称,也可能导致JSON序列化的问题)指定为reserved
。如果将来有用户尝试使用这些标识符,则协议缓冲区编译器会抱怨。您可以使用max
关键字指定保留的数值范围达到最大可能值。
enum Foo {
reserved 2, 15, 9 to 11, 40 to max;
reserved "FOO", "BAR";
}
请注意,您不能在同reserved
一条语句中混合使用字段名和数字值。
使用其他消息类型
您可以使用其他消息类型作为字段类型。例如,假设你想包括Result
每个消息的SearchResponse
消息-要做到这一点,你可以定义一个Result
在同一个消息类型.proto
,然后指定类型的字段Result
中SearchResponse
:
message SearchResponse {
repeated Result results = 1;
}
message Result {
string url = 1;
string title = 2;
repeated string snippets = 3;
}
导入定义
在上面的示例中,Result
消息类型与以下文件定义在同一文件中SearchResponse
–如果要用作字段类型的消息类型已在另一个.proto
文件中定义,该怎么办?
您可以.proto
通过导入其他文件来使用它们的定义。要导入another.proto
的定义,请在文件顶部添加一个import语句:
import "myproject/other_protos.proto";
默认情况下,您只能使用直接导入.proto
文件中的定义。但是,有时您可能需要将.proto
文件移动到新位置。.proto
现在,您可以直接.proto
在原位置放置一个虚拟文件,以使用该import public
概念将所有导入转发到新位置,而不是直接移动文件并一次更改所有呼叫站点。import public
任何导入包含该import public
语句的原型的人都可以过渡地依赖依赖项。例如:
// new.proto
// All definitions are moved here
// old.proto
// This is the proto that all clients are importing.
import public "new.proto";
import "other.proto";
// client.proto
import "old.proto";
// You use definitions from old.proto and new.proto, but not other.proto
协议编译器使用-I
/--proto_path
标志在协议编译器命令行上指定的一组目录中搜索导入的文件。如果未给出标志,它将在调用编译器的目录中查找。通常,应将--proto_path
标志设置为项目的根目录,并对所有导入使用完全限定的名称。
使用proto2消息类型
可以导入proto2消息类型并在您的proto3消息中使用它们,反之亦然。但是,不能直接在proto3语法中使用proto2枚举(如果导入的proto2消息使用它们,也可以)。
嵌套类型
您可以在其他消息类型内定义和使用消息类型,如以下示例所示–在此处,Result
消息是在消息内定义的SearchResponse
:
message SearchResponse {
message Result {
string url = 1;
string title = 2;
repeated string snippets = 3;
}
repeated Result results = 1;
}
如果要在其父消息类型之外重用此消息类型,则将其称为_Parent_._Type_
:
message SomeOtherMessage {
SearchResponse.Result result = 1;
}
您可以根据需要深度嵌套消息:
message Outer { // Level 0
message MiddleAA { // Level 1
message Inner { // Level 2
int64 ival = 1;
bool booly = 2;
}
}
message MiddleBB { // Level 1
message Inner { // Level 2
int32 ival = 1;
bool booly = 2;
}
}
}
更新消息类型
如果现有消息类型不再满足您的所有需求(例如,您希望消息格式具有一个额外的字段),但是您仍然希望使用以旧格式创建的代码,请不要担心!在不破坏任何现有代码的情况下更新消息类型非常简单。只要记住以下规则:
- 不要更改任何现有字段的字段编号。
- 如果添加新字段,则仍可以使用新生成的代码来解析使用“旧”消息格式通过代码序列化的任何消息。您应记住这些元素的默认值,以便新代码可以与旧代码生成的消息正确交互。同样,新代码创建的消息也可以由旧代码解析:旧的二进制文件在解析时只会忽略新字段。有关详细信息,请参见“未知字段”部分。
- 只要在更新后的消息类型中不再使用字段号,就可以删除字段。您可能想要重命名该字段,或者添加前缀“ OBSOLETE_”,或者将字段编号保留为,以便将来的用户
.proto
不会意外地重用该编号。 int32
,uint32
,int64
,uint64
,和bool
都是兼容的-这意味着你可以在现场从这些类型到另一种改变不破坏forwards-或向后兼容。如果从对应的类型不适合的导线中解析出一个数字,则将获得与在C ++中将数字强制转换为该类型一样的效果(例如,如果将64位数字读为int32,它将被截断为32位)。sint32
并且sint64
彼此兼容,但与其他整数类型不兼容。string
并且bytes
只要字节是有效的UTF-8即可兼容。- 嵌入式消息与
bytes
字节是否包含消息的编码版本兼容。 fixed32
与兼容sfixed32
,并fixed64
用sfixed64
。- 对于
string
,bytes
和消息字段,optional
与兼容repeated
。给定重复字段的序列化数据作为输入,如果期望此字段optional
是原始类型字段,则期望该字段的客户端将采用最后一个输入值;如果是消息类型字段,则将合并所有输入元素。请注意,这不是一般的数值类型,包括布尔变量和枚举安全。重复的数字类型字段可以以打包格式序列化,当需要一个optional
字段时,将无法正确解析该格式。 enum
与兼容int32
,uint32
,int64
,和uint64
电线格式条款(请注意,如果他们不适合的值将被截断)。但是请注意,客户端代码在反序列化消息时可能会以不同的方式对待它们:例如,无法识别的proto3enum
类型将保留在消息中,但是在反序列化消息时如何表示这取决于语言。Int字段始终只是保留其值。- 将单个值更改为新 值的成员
oneof
是安全且二进制兼容的。oneof
如果您确定没有代码一次设置多个字段,那么将多个字段移动到新字段中可能是安全的。将任何字段移到现有字段中oneof
都是不安全的。
未知字段
未知字段是格式正确的协议缓冲区序列化数据,表示解析器无法识别的字段。例如,当旧二进制文件使用新字段解析新二进制文件发送的数据时,这些新字段将成为旧二进制文件中的未知字段。
最初,proto3消息在解析过程中始终丢弃未知字段,但是在版本3.5中,我们重新引入了保留未知字段以匹配proto2行为的功能。在版本3.5和更高版本中,未知字段将在解析期间保留并包含在序列化输出中。
任何
该Any
消息类型,可以使用邮件作为嵌入式类型,而不必自己.proto定义。一个Any
含有任意的序列化消息bytes
,以充当一个全局唯一标识符和解析为消息的类型的URL一起。要使用该Any
类型,您需要导入 google/protobuf/any.proto
。
import "google/protobuf/any.proto";
message ErrorStatus {
string message = 1;
repeated google.protobuf.Any details = 2;
}
给定消息类型的默认类型URL为type.googleapis.com/_packagename_._messagename_
。
不同的语言实现将支持运行时库佣工类型安全的方式打包和解包的任何值-例如,在Java中,任何类型都会有特殊pack()
和unpack()
存取,而在C ++中有PackFrom()
和UnpackTo()
方法:
// Storing an arbitrary message type in Any.
NetworkErrorDetails details = ...;
ErrorStatus status;
status.add_details()->PackFrom(details);
// Reading an arbitrary message from Any.
ErrorStatus status = ...;
for (const Any& detail : status.details()) {
if (detail.Is<NetworkErrorDetails>()) {
NetworkErrorDetails network_error;
detail.UnpackTo(&network_error);
... processing network_error ...
}
}
当前,正在开发用于任何类型的运行时库。
如果您已经熟悉proto2语法,则Any
可以容纳任意proto3消息,类似于可以允许扩展的proto2消息。
一个
如果您有一条消息包含多个字段,并且最多可以同时设置一个字段,则可以使用oneof功能强制执行此行为并节省内存。
一个字段类似于常规字段,但一个共享内存中的所有字段除外,并且最多可以同时设置一个字段。设置oneof中的任何成员会自动清除所有其他成员。您可以根据所选择的语言,使用特殊case()
或WhichOneof()
方法来检查其中一个设置的值(如果有)。
使用Oneof
要在您中定义一个oneof,请.proto
使用oneof
关键字,后跟您的oneof名称,在这种情况下test_oneof
:
message SampleMessage {
oneof test_oneof {
string name = 4;
SubMessage sub_message = 9;
}
}
然后,将oneof字段添加到oneof定义。您可以添加任何类型的map
字段,但字段和repeated
字段除外。
在您生成的代码中,ofof字段具有与常规字段相同的getter和setter。您还将获得一种特殊的方法来检查oneof中的哪个值(如果有)。您可以在相关的API参考中找到有关所选语言的oneof API的更多信息。
功能之一
-
设置oneof字段将自动清除oneof的所有其他成员。因此,如果您设置了多个字段,则只有您设置的最后一个字段仍具有值。
SampleMessage message; message.set_name("name"); CHECK(message.has_name()); message.mutable_sub_message(); // Will clear name field. CHECK(!message.has_name());
-
如果解析器在线路上遇到同一个对象的多个成员,则在解析的消息中仅使用最后看到的成员。
-
一个不能是
repeated
。 -
反射API适用于其中一个字段。
-
如果将oneof字段设置为默认值(例如将int32 oneof字段设置为0),则将设置该oneof字段的“大小写”,并且该值将在线路上序列化。
-
如果您使用的是C ++,请确保您的代码不会导致内存崩溃。以下示例代码将崩溃,因为
sub_message
已通过调用该set_name()
方法将其删除。SampleMessage message; SubMessage* sub_message = message.mutable_sub_message(); message.set_name("name"); // Will delete sub_message sub_message->set_... // Crashes here
-
再次在C ++中,如果
Swap()
与oneofs两个消息,每个消息将结束与对方的oneof情况下:在下面的例子中,msg1
将具有sub_message
与msg2
将有一个name
。SampleMessage msg1; msg1.set_name("name"); SampleMessage msg2; msg2.mutable_sub_message(); msg1.swap(&msg2); CHECK(msg1.has_sub_message()); CHECK(msg2.has_name());
向后兼容问题
添加或删除字段之一时请多加注意。如果检查oneof的值返回None
/ NOT_SET
,则可能意味着尚未设置oneof或已将其设置为不同版本的oneof中的字段。由于无法知道导线上的未知字段是否是oneof的成员,因此无法分辨出差异。
标签重用问题
- 将字段移入或移出一个:在对消息进行序列化和解析之后,您可能会丢失一些信息(某些字段将被清除)。但是,您可以安全地将单个字段移动到新字段中,并且如果知道只设置了一个字段,则可以移动多个字段。
- 删除一个oneof字段并将其添加回:序列化并解析邮件后,这可能会清除您当前设置的oneof字段。
- 拆分或合并其中一个:与移动常规字段有类似的问题。
地图
如果要在数据定义中创建关联映射,则协议缓冲区提供了方便的快捷方式语法:
map<key_type, value_type> map_field = N;
…其中key_type
可以是任何整数或字符串类型(因此,除浮点类型和以外的任何标量类型bytes
)。请注意,enum无效key_type
。的value_type
可以是任何类型的除另一地图。
因此,例如,如果您想创建一个项目地图,其中每个Project
消息都与一个字符串键相关联,则可以这样定义它:
map<string, Project> projects = 3;
- 地图字段不能为
repeated
。 - 地图值的线格式排序和地图迭代排序是不确定的,因此您不能依赖于地图项的特定顺序。
- 为生成文本格式时
.proto
,地图会按键排序。数字键按数字排序。 - 从导线解析或合并时,如果存在重复的映射键,则使用最后看到的键。从文本格式解析地图时,如果键重复,则解析可能会失败。
- 如果为映射字段提供键但没有值,则序列化字段时的行为取决于语言。在C ++,Java和Python中,类型的默认值是序列化的,而在其他语言中,则没有序列化的值。
生成的地图API当前可用于所有proto3支持的语言。您可以在相关API参考中找到有关所选语言的map API的更多信息。
向后兼容
映射语法与网上的以下语法等效,因此不支持映射的协议缓冲区实现仍可以处理您的数据:
message MapFieldEntry {
key_type key = 1;
value_type value = 2;
}
repeated MapFieldEntry map_field = N;
任何支持映射的协议缓冲区实现都必须产生并接受上述定义可以接受的数据。
配套
您可以package
向.proto
文件添加可选的说明符,以防止协议消息类型之间的名称冲突。
package foo.bar;
message Open { ... }
然后,您可以在定义消息类型的字段时使用包说明符:
message Foo {
...
foo.bar.Open open = 1;
...
}
包说明符影响生成的代码的方式取决于您选择的语言:
- 在C ++中,生成的类包装在C ++名称空间中。例如,
Open
将在命名空间中foo::bar
。 - 在Java中,除非您
option java_package
在.proto
文件中明确提供了,否则该包将用作Java包。 - 在Python中,package指令将被忽略,因为Python模块是根据其在文件系统中的位置进行组织的。
- 在Go中,除非您
option go_package
在.proto
文件中明确提供,否则该包将用作Go包名称。 - 在Ruby中,生成的类包装在嵌套的Ruby名称空间中,并转换为所需的Ruby大写样式(首字母大写;如果首字符不是字母,
PB_
则为前缀)。例如,Open
将在命名空间中Foo::Bar
。 - 在C#中,除非您
option csharp_namespace
在.proto
文件中明确提供,否则在转换为PascalCase后,该程序包将用作命名空间。例如,Open
将在命名空间中Foo.Bar
。
软件包和名称解析
协议缓冲语言中的类型名称解析类似于C ++:首先搜索最里面的作用域,然后搜索最里面的作用域,依此类推,每个包都被视为其父包“内部”。领先的“。” (例如,.foo.bar.Baz
)表示从最外面的范围开始。
协议缓冲区编译器通过解析导入的.proto
文件来解析所有类型名称。每种语言的代码生成器都知道如何引用该语言中的每种类型,即使它具有不同的范围规则。
定义服务
如果要将消息类型与RPC(远程过程调用)系统一起使用,则可以在.proto
文件中定义RPC服务接口,并且协议缓冲区编译器将以您选择的语言生成服务接口代码和存根。因此,例如,如果您想使用一种方法来定义RPC服务,该方法接受您的方法SearchRequest
并返回SearchResponse
,则可以在.proto
文件中按以下方式对其进行定义:
service SearchService {
rpc Search(SearchRequest) returns (SearchResponse);
}
与协议缓冲区一起使用的最简单的RPC系统是gRPC:这是Google开发的与语言和平台无关的开源RPC系统。gRPC与协议缓冲区配合使用特别好,并允许您.proto
使用特殊的协议缓冲区编译器插件直接从文件中生成相关的RPC代码。
如果您不想使用gRPC,也可以在自己的RPC实现中使用协议缓冲区。您可以在《Proto2语言指南》中找到有关此内容的更多信息。
还有许多正在进行的第三方项目正在为协议缓冲区开发RPC实现。有关我们了解的项目的链接列表,请参阅第三方加载项Wiki页面。
JSON对应
Proto3支持JSON中的规范编码,从而使在系统之间共享数据更加容易。下表按类型对编码进行了描述。
如果JSON编码的数据中缺少某个值,或者该值为null
,则在解析为协议缓冲区时,它将被解释为适当的默认值。如果字段在协议缓冲区中具有默认值,则默认情况下会在JSON编码数据中将其省略以节省空间。一个实现可以提供选项,以在JSON编码的输出中发出具有默认值的字段。
原型3 | JSON格式 | JSON范例 | 笔记 |
---|---|---|---|
信息 | 目的 | {{fooBar“:v,” g“:null,…}` | 生成JSON对象。消息字段名称被映射到lowerCamelCase并成为JSON对象键。如果指定了json_name字段选项,则将指定的值用作键。解析器接受LowerCamelCase名称(或由json_name选项指定的名称)和原始原型字段名称。null是所有字段类型的可接受值,并被视为相应字段类型的默认值。 |
枚举 | 串 | “ FOO_BAR” | 使用在proto中指定的枚举值的名称。解析器接受枚举名称和整数值。 |
映射<K,V> | 目的 | {“ k”:v,…} | 所有键都转换为字符串。 |
重复的V | 数组 | [v,…] | null为空列表[]。 |
布尔 | 真假 | 真假 | |
串 | 串 | ““你好,世界!” | |
个字节 | base64字符串 | ““ YWJjMTIzIT8kKiYoKSctPUB +”” | JSON值将是使用带有填充的标准base64编码编码为字符串的数据。接受带/不带填充的标准或URL安全base64编码。 |
int32,fixed32,uint32 | 数 | 1,-10、0 | JSON值为十进制数字。可以接受数字或字符串。 |
int64,fixed64,uint64 | 串 | “ 1”,“ -10” | JSON值将是一个十进制字符串。可以接受数字或字符串。 |
浮点双 | 数 | 1.1,-10.0、0,“ NaN”,“ Infinity” | JSON值将是数字或特殊字符串值“ NaN”,“ Infinity”和“ -Infinity”之一。可以接受数字或字符串。指数表示法也被接受。-0被认为等效于0。 |
任何 | 对象 | {“ @type”:“ url”,“ f”:v,…} | 如果Any包含具有特殊JSON映射的值,则将其转换如下:{“ @type”:xxx,“ value”:yyy} 。否则,该值将转换为JSON对象,并且将插入“ @type”字段以指示实际的数据类型。 |
时间戳记 | 串 | “ 1972-01-01T10:00:20.021Z” | 使用RFC 3339,其中生成的输出将始终进行Z归一化,并使用0、3、6或9个小数位。也可以接受“ Z”以外的偏移。 |
持续时间 | 串 | “ 1.000340012s”,“ 1s” | 生成的输出始终包含0、3、6或9个小数位数,具体取决于所需的精度,后跟后缀“ s”。可接受的任何小数位数(也无),只要它们适合纳秒精度,并且后缀“ s”是必需的。 |
结构 | 对象 | {…} | 任何JSON对象。参见struct.proto 。 |
包装类型 | 各种类型 | 2,“ 2”,“ foo”,true,“ true”,null,0,… | 包装器在JSON中使用与包装后的原始类型相同的表示形式,不同之处在于在数据转换和传输期间允许并保留`null’。 |
现场面具 | 串 | “ f.fooBar,h” | 参见field_mask.proto 。 |
ListValue | 数组 | [foo,bar,…] | |
值 | 值 | 任何JSON值。查看google.protobuf.Value了解详细信息。 | |
空值 | 空值 | JSON空 | |
空的 | 目的 | {} | 空的JSON对象 |
JSON选项
一个proto3 JSON实现可以提供以下选项:
- 发出具有默认值的字段:默认情况下,proto3 JSON输出中会省略具有默认值的字段。一个实现可以提供一个选项,以使用其默认值覆盖此行为和输出字段。
- 忽略未知字段:默认情况下,Proto3 JSON解析器应拒绝未知字段,但可以提供在解析时忽略未知字段的选项。
- 使用proto字段名称而不是lowerCamelCase名称:默认情况下,proto3 JSON打印机应将字段名称转换为lowerCamelCase并将其用作JSON名称。一个实现可以提供一个选项,改为使用原型字段名称作为JSON名称。Proto3 JSON解析器必须接受转换后的lowerCamelCase名称和原型字段名称。
- 将枚举值作为整数而不是字符串发送:缺省情况下,JSON输出中使用枚举值的名称。可以提供一个选项来代替使用枚举值的数字值。
选件
.proto
文件中的各个声明可以使用许多选项来注释。选项不会改变声明的整体含义,但可能会影响在特定上下文中处理声明的方式。可用选项的完整列表在中定义google/protobuf/descriptor.proto
。
一些选项是文件级选项,这意味着它们应在顶级范围内编写,而不是在任何消息,枚举或服务定义内。一些选项是消息级别的选项,这意味着它们应该写在消息定义中。一些选项是字段级选项,这意味着它们应在字段定义中编写。选项也可以写在枚举类型,枚举值,字段,服务类型和服务方法中;但是,目前对于这些功能都不存在有用的选项。
以下是一些最常用的选项:
-
java_package
(文件选项):您要用于生成的Java类的包。如果java_package
在.proto
文件中未指定任何显式选项,则默认情况下将使用proto软件包(在.proto
文件中使用“ package”关键字指定)。但是,proto软件包通常不能成为良好的Java软件包,因为proto软件包不应以反向域名开头。如果未生成Java代码,则此选项无效。option java_package = "com.example.foo";
-
java_multiple_files
(文件选项):导致在包级别定义顶级消息,枚举和服务,而不是在以.proto文件命名的外部类中定义。option java_multiple_files = true;
-
java_outer_classname
(文件选项):您要生成的最外层Java类的类名(以及文件名)。如果java_outer_classname
在.proto
文件中未指定任何显式名称,则通过将.proto
文件名转换为驼峰大小写来构造类名(因此foo_bar.proto
变为FooBar.java
)。如果未生成Java代码,则此选项无效。option java_outer_classname = "Ponycopter";
-
optimize_for
(文件选项):可以设置为SPEED
,CODE_SIZE
或LITE_RUNTIME
。这会通过以下方式影响C ++和Java代码生成器(可能还有第三方生成器):SPEED
(默认值):协议缓冲区编译器将生成代码,用于对消息类型进行序列化,解析和执行其他常见操作。此代码已高度优化。CODE_SIZE
:协议缓冲区编译器将生成最少的类,并将依赖于基于反射的共享代码来实现序列化,解析和其他各种操作。因此,生成的代码将比使用的代码小得多SPEED
,但操作会更慢。类仍将实现与SPEED
模式下完全相同的公共API 。此模式在包含大量.proto
文件的应用程序中最有用,并且不需要所有文件都可以使人眼花fast乱。LITE_RUNTIME
:协议缓冲区编译器将生成仅依赖于“精简版”运行时库(libprotobuf-lite
而不是libprotobuf
)的类。精简版运行时比完整库要小得多(大约小一个数量级),但省略了某些功能,例如描述符和反射。这对于在受限平台(例如手机)上运行的应用程序特别有用。编译器仍将像在SPEED
模式下一样生成所有方法的快速实现。生成的类将仅以MessageLite
每种语言实现该接口,该接口仅提供完整Message
接口方法的一部分。
option optimize_for = CODE_SIZE;
-
cc_enable_arenas
(文件选项):启用C ++生成代码的舞台分配。 -
objc_class_prefix
(文件选项):设置Objective-C类的前缀,该前缀将附加到所有Objective-C生成的类以及此.proto枚举。没有默认值。您应该使用Apple推荐的3-5个大写字符之间的前缀。请注意,Apple保留所有2个字母前缀。 -
deprecated
(字段选项):如果设置为true
,则表明该字段已弃用,并且不应被新代码使用。在大多数语言中,这没有实际效果。在Java中,这成为@Deprecated
注释。将来,其他特定于语言的代码生成器可能会在字段的访问器上生成弃用注释,这反过来将导致在编译尝试使用该字段的代码时发出警告。如果该字段未被任何人使用,并且您想阻止新用户使用该字段,请考虑使用保留语句替换该字段声明。int32 old_field = 6 [deprecated = true];
自订选项
协议缓冲区还允许您定义和使用自己的选项。这是大多数人不需要的高级功能。如果您确实需要创建自己的选项,请参阅《Proto2语言指南》以了解详细信息。请注意,创建自定义选项使用扩展名,只有proto3中的自定义选项才允许使用扩展名。
生成课程
要生成,你需要工作,在规定的消息类型的使用Java,Python,C ++,围棋,红宝石,Objective-C的,或C#代码.proto
文件,你需要运行协议缓冲编译器protoc
上.proto
。如果尚未安装编译器,请下载软件包并按照README中的说明进行操作。对于Go,您还需要为编译器安装一个特殊的代码生成器插件:您可以在GitHub上的golang / protobuf存储库中找到此代码和安装说明。
协议编译器的调用如下:
protoc --proto_path=IMPORT_PATH --cpp_out=DST_DIR --java_out=DST_DIR --python_out=DST_DIR --go_out=DST_DIR --ruby_out=DST_DIR --objc_out=DST_DIR --csharp_out=DST_DIR path/to/file.proto
-
IMPORT_PATH
指定.proto
解析import
指令时要在其中查找文件的目录。如果省略,则使用当前目录。可以通过--proto_path
多次传递选项来指定多个导入目录。将按顺序搜索它们。-I=_IMPORT_PATH_
可以用作的简写形式--proto_path
。 -
您可以提供一个或多个
输出指令
:
--cpp_out
在中生成C ++代码DST_DIR
。有关更多信息,请参见C ++生成的代码参考。--java_out
在中生成Java代码DST_DIR
。有关更多信息,请参见Java生成的代码参考。--python_out
在中生成Python代码DST_DIR
。有关更多信息,请参见Python生成的代码参考。--go_out
在中生成Go代码DST_DIR
。有关更多信息,请参见Go生成的代码参考。--ruby_out
在中生成Ruby代码DST_DIR
。Ruby生成的代码参考即将推出!--objc_out
在中生成Objective-C代码DST_DIR
。有关更多信息,请参见Objective-C生成的代码参考。--csharp_out
在中生成C#代码DST_DIR
。有关更多信息,请参见C#生成的代码参考。--php_out
在中生成PHP代码DST_DIR
。参见PHP生成的代码参考以获取更多信息。为方便起见,如果DST_DIR
结尾为.zip
或.jar
,编译器会将输出写入具有给定名称的单个ZIP格式的存档文件。.jar
根据Java JAR规范的要求,还将为输出提供清单文件。注意,如果输出存档已经存在,它将被覆盖;编译器不够智能,无法将文件添加到现有存档中。
-
您必须提供一个或多个
.proto
文件作为输入。.proto
可以一次指定多个文件。尽管这些文件是相对于当前目录命名的,但是每个文件都必须位于IMPORT_PATH
s之一中,以便编译器可以确定其规范名称。