本指南介绍如何使用协议缓冲区语言来构建协议缓冲区数据,包括.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
消息定义指定三个字段(名/值对),一个此类型消息中要包含的每条数据。每个字段都有一个名字和一个类型。
指定字段类型
在上述示例中,所有字段都是scalar类型:两个整数(page_number
和result_per_page
)和一个字符串(查询)。您也可以为字段指定复合类型,包括枚举和其他消息类型。
分配标签
如你所见,消息定义中的每个字段都有唯一的编号标签。这些标签用于以消息二进制格式标识您的字段,一旦您的消息类型被使用该tag就不应该再更改。
请注意,值范围为1到15的标签需要一个字节进行编码,包括标识号和字段的类型(您可以在协议缓冲区编码中找到更多信息)。16到2047范围内的标签需要两个字节。
因此,您应该为非常频繁出现的消息元素预留标签1到15。记住要为将来可能添加的频繁出现的元素预留空间。
您可以指定的最小标签号码是1,最大的标签号码是1,073,741,823或536,870,911。您也不能使用号码19000到19999(FieldDescriptor::kFirstReservedNumber
到FieldDescriptor::kLastReservedNumber
),因为它们被保留用于协议缓冲区实现 -
如果您在.proto
中使用这些保留号码之一,则协议缓冲区编译器会抱怨。同样,您也不能使用任何以前Reserved的标签。
指定字段规则
消息字段可以是以下之一:
- 单数:一个格式正确的消息可以具有零个或一个这个的字段(但不超过一个)。
repeated
:该字段可以在格式正确的消息中重复任意次数(包括零)。重复值的顺序将被保留。
在proto3中,标量数字类型的repeated
字段默认使用压缩编码。
您可以在协议缓冲区编码中找到有关打包编码的更多信息。
添加更多消息类型
多个消息类型可以在单个.proto
文件中定义。如果您要定义多个相关的消息,这很有用 - 例如,如果要定义与您的SearchResponse
消息类型相对应的回复消息格式,则可以将其添加到相同的.proto
文件:
message SearchRequest {
string query = 1;
int32 page_number = 2;
int32 result_per_page = 3;
}
message SearchResponse {
...
}
添加注释
要向.proto
文件添加注释,请使用C/C++ 风格的//
语法。
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
的旧版本,这可能会导致严重的问题, 包括数据损坏,隐私错误等。
确保不会发生这种情况的一种方法是通过reserved
指定已删除的字段的字段标签(或名称, 这也可能导致JSON序列化的问题)被保留。如果将来的用户尝试使用这些字段标识符,协议缓冲区编译器将会抱怨。
message Foo {
reserved 2, 15, 9 to 11;
reserved "foo", "bar";
}
请注意,您不能在相同的reserved
语句中混合字段名称和标签号。
你的.proto产生了什么?
当您在.proto上运行协议缓冲区编译器时,编译器将以您选择的语言生成代码,处理文件中描述的消息类型,包括获取和设置字段值,将消息序列化为输出流,并从输入流解析您的消息。
- 对于Java,编译器会为每个消息类型生成一个包含类的
.java
文件,以及一个用于创建消息类实例的特殊Builder
类。
您可以通过所选语言的教程(proto3版本即将推出),了解有关为每种语言使用API的更多信息。有关更多API详细信息,请参阅相关API参考(proto3版本即将推出)。
标量值类型
标量消息字段可以具有以下类型之一:该表显示.proto
文件中指定的类型以及自动生成的类中的相应类型:
.proto Type | Notes | C++ Type | Java Type | Python Type[2] | Go Type | Ruby Type | C# Type | PHP Type |
---|---|---|---|---|---|---|---|---|
double | double | double | float | float64 | Float | double | float | |
float | float | float | float | float32 | Float | float | float | |
int32 | 使用可变长度编码。编码负数的效率不高 - 如果你的字段要使用负数值,请改用sint32 | int32 | int | int | int32 | Fixnum or Bignum (as required) | int | integer |
int64 | 使用可变长度编码。编码负数的效率不高 – 如果你的字段要使用负数值,请改用sint64 | int64 | long | int/long[3] | int64 | Bignum | long | integer/string[5] |
uint32 | 使用可变长度编码。 | uint32 | int[1] | int/long[3] | uint32 | Fixnum or Bignum (as required) | uint | integer |
uint64 | 使用可变长度编码。 | uint64 | long[1] | int/long[3] | uint64 | Bignum | ulong | integer/string[5] |
sint32 | 使用可变长度编码,这些在编码负值时比int32高效的多 | int32 | int | int | int32 | Fixnum or Bignum (as required) | int | integer |
sint64 | 使用可变长度编码,有符号的整型值。编码时比通常的int64高效 | int64 | long | int/long[3] | int64 | Bignum | long | integer/string[5] |
fixed32 | 总是4个字节,如果数值总是比228大的话,这个类型会比uint32高效 | uint32 | int[1] | int | uint32 | Fixnum or Bignum (as required) | uint | integer |
fixed64 | 总是8个字节,如果数值总是比256大的话,这个类型会比uint64高效 | uint64 | long[1] | int/long[3] | uint64 | Bignum | ulong | integer/string[5] |
sfixed32 | 总是4个字节 | int32 | int | int | int32 | Fixnum or Bignum (as required) | int | integer |
sfixed64 | 总是8个字节 | int64 | long | int/long[3] | int64 | Bignum | long | integer/string[5] |
bool | bool | boolean | bool | bool | TrueClass/FalseClass | bool | boolean | |
string | 一个字符串必须是UTF-8编码或者7-bit ASCII编码的文本 | string | String | str/unicode[4] | string | String (UTF-8) | string | string |
bytes | 可能包含任意顺序的字节数据 | string | ByteString | str | []byte | String (ASCII-8BIT) | ByteString | string |
您可以在协议缓冲区编码中,了解序列化消息时更多关于这些类型的编码方式。
默认值
当消息被解析时,如果编码的消息不包含特定的单个元素,则解析对象中的相应字段将被设置为该字段的默认值。这些默认值是特定于类型的:
- 对于字符串,默认值为空字符串。
- 对于字节,默认值为空字节。
- 对于布尔,默认值为false。
- 对于数值类型,默认值为零。
- 对于枚举,默认值是第一个定义的枚举值,它必须为0。
- 对于消息字段,该字段不设置。其确切的值取决于语言。有关详细信息,请参阅代码生成指南。
重复字段的默认值为空(通常为语言对应的空列表)。
请注意,对于标量消息字段,一旦消息被解析,就无法告知字段是否被明确地设置为默认值(例如布尔值是否设置为false),或者根本不设置:在定义消息类型时要牢记。
例如,如果您不希望默认情况下也会发生这种行为,那么在设置为false
时,不要使用布尔值来切换某些行为。另请注意,如果标量消息字段设置为默认值,该值将不会被序列化。
枚举
当您定义消息类型时,您可能希望其中一个字段只有一个预定义的值列表。例如,假设您要为每个SearchRequest
添加语料库字段,其中语料库可以是UNIVERSAL
,WEB
,IMAGES
,LOCAL
,NEWS
,PRODUCTS
或VIDEO
。
您可以通过为每个可能的值添加一个常量来为消息定义添加一个枚举。
在下面的示例中,我们添加了一个名为Corpus
的枚举,其中包含所有可能的值,以及一个类型为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;
}
您可以看到,语料库枚举的第一个常量映射为零:每个枚举定义必须包含一个常量,将其定义为零作为其第一个元素。这是因为:
- 必须有一个零值,所以我们可以使用0作为枚举默认值。
- 零值需要是第一个元素,用于与第一个枚举值始终为默认值的proto2语义兼容。
您可以通过为不同的枚举常量分配相同的值来定义别名。为此,您需要将allow_alias
选项设置为true
,否则协议编译器将在找到别名时生成错误信息。
enum EnumAllowingAlias {
option allow_alias = true;
UNKNOWN = 0;
STARTED = 1;
RUNNING = 1;
}
enum EnumNotAllowingAlias {
UNKNOWN = 0;
STARTED = 1;
// RUNNING = 1; // Uncommenting this line will cause a compile error inside Google and a warning message outside.
}
枚举器常量必须在32位整数的范围内。由于枚举值在线上使用varint编码,所以负值无效,因此不推荐使用。
您可以在消息内部或外部定义枚举,如上述示例 - 这些枚举可以在您的.proto
文件中的任何消息定义中重用。您还可以使用MessageType.EnumType
语法在一个消息中声明的枚举类型作为不同消息中的字段的类型。
当对一个使用了枚举的.proto
文件运行protocol buffer
编译器的时候,生成的代码中将有一个对应的enum
类(对Java或C++来说),或者一个特殊的EnumDescriptor
类(对 Python来说),
它被用来在运行时生成的类中创建一系列的整型符号常量。
在反序列化的过程中,无法识别的枚举值会被保存在消息中,虽然这种表示方式需要依据所使用语言而定。在那些支持开放枚举类型超出指定范围之外的语言中(例如C++和Go),未识别的值会被表示成所支持的整型。
在使用封闭枚举类型的语言中(Java),使用枚举中的特殊值来表示未识别的值,并且可以使用特殊访问器访问其基础整数。在其他情况下,如果消息被序列化,则无法识别的值也仍将被序列化。
有关如何在应用程序中使用消息枚举的更多信息,请参阅所选语言的代码生成指南。
使用其他消息类型
您可以使用其他消息类型作为字段类型。例如,假设您想在每个SearchResponse
消息中包含Result
消息 - 为此,您可以在同一个.proto
中定义一个Result
消息类型,然后在SearchResponse
中
指定一个Result
类型的字段:
message SearchResponse {
repeated Result results = 1;
}
message Result {
string url = 1;
string title = 2;
repeated string snippets = 3;
}
导入定义
在上述示例中,Result
消息类型与SearchResponse
相同的文件中定义 - 如果要用作字段类型的消息类型已经在另一个.proto
文件中定义了呢?
您可以导入来自其他.proto
文件的定义。要导入另一个.proto
的定义,您需要在文件的顶部添加一个import语句:
import "myproject/other_protos.proto";
默认情况下你只能使用直接导入的.proto
文件中的定义. 然而, 有时候你需要移动一个老.proto
文件到一个新的位置, 可以不直接移动老.proto
文件,
只需在老的位置放入一个伪.proto
文件, 然后使用import public
转向新的位置即可。import public
依赖性会通过任意导入包含import public
声明的proto
文件传递。例如:
// 这是new.proto
// 所有相关定义被移到这里
// 这是old.proto
// 这里声明了所有客户端正在导入的包
import public "new.proto";
import "other.proto";
// 这是client.proto
import "old.proto"; // 不需要改动old.proto导入站点
// 现在你可以使用old.proto和new.proto的定义,但不能使用other.proto的定义。
通过在编译器命令行参数中使用-I/--proto_path
标志,编译器会在指定目录搜索要导入的文件。如果没有给出标志,编译器会搜索编译命令被调用的目录。
通常你只要指定proto_path
标志为你的工程根目录并且对所有导入使用完全限定名称就好。
使用proto2消息类型
可以导入proto2消息类型并在proto3消息中使用它们,反之亦然。
然而,proto2枚举不能直接在proto3语法中使用(如果导入的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;
}
}
}
更新消息类型
如果一个已有的消息格式已无法满足新的需求——如,要在消息中添加一个额外的字段——但是同时旧版本写的代码仍然可用。不用担心!更新消息而不破坏已有代码是非常简单的。在更新时只要记住以下的规则即可。
- 不要更改任何已有的字段的数值标识。
- 如果你增加新的字段,使用旧格式的字段仍然可以被你新产生的代码所解析。你应该记住这些元素的默认值这样你的新代码就可以以适当的方式和旧代码产生的数据交互。相似的,通过新代码产生的消息也可以被旧代码解析:只不过新的字段会被忽视掉。注意,未被识别的字段会在反序列化的过程中丢弃掉,所以如果消息再被传递给新的代码,新的字段依然是不可用的(这和
proto2
中的行为是不同的,在proto2
中未定义的域依然会随着消息被序列化) - 非
required
的字段可以移除——只要它们的标识号在新的消息类型中不再使用(更好的做法可能是重命名那个字段,例如在字段前添加OBSOLETE_
前缀,那样的话,使用的.proto
文件的用户将来就不会无意中重新使用了那些不该使用的标识号)。int32, uint32, int64, uint64,和bool是全部兼容的,这意味着可以将这些类型中的一个转换为另外一个,而不会破坏向前、 向后的兼容性。如果解析出来的数字与对应的类型不相符,那么结果就像在C++中对它进行了强制类型转换一样(例如,如果把一个64位数字当作int32来 读取,那么它就会被截断为32位的数字)。 - sint32和sint64是互相兼容的,但是它们与其他整数类型不兼容。
- string和bytes是兼容的——只要bytes是有效的UTF-8编码。
- 嵌套消息与bytes是兼容的——只要bytes包含该消息的一个编码过的版本。
- fixed32与sfixed32是兼容的,fixed64与sfixed64是兼容的。
- 枚举类型与int32,uint32,int64和uint64相兼容(注意如果值不相兼容则会被截断),然而在客户端反序列化之后他们可能会有不同的处理方式,例如,未识别的proto3枚举类型会被保留在消息中,但是他的表示方式会依照语言而定。int字段始终保持其值。
未知字段
Any
Any类型消息允许你在没有指定他们的.proto定义的情况下使用消息作为一个嵌套类型。一个Any类型包括一个可以被序列化bytes类型的任意消息,以及作为全局唯一标识符的URL并解析为该消息的类型。
为了使用Any类型,你需要导入import 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
。
不同语言的实现会支持动态库以线程安全的方式去帮助封装或者解封装Any
值。例如在java中,Any类型会有特殊的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 ...
}
}
目前,用于Any类型的动态(运行时)库仍在开发之中。
Oneof
如果你的消息中有很多可选字段, 并且同时至多一个字段会被设置, 你可以加强这个行为,使用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
定义中. 你可以增加任意类型的字段, 但是不能使用repeated
关键字.
在产生的代码中, oneof字段拥有同样的getters
和setters
, 就像正常的可选字段一样. 也有一个特殊的方法来检查到底那个字段被设置. 你可以在相应的语言API指南中找到oneof API介绍.
Oneof 特性
- 设置oneof会自动清除其它oneof字段的值. 所以设置多次后,只有最后一次设置的字段有值.
SampleMessage message;
message.set_name("name");
CHECK(message.has_name());
message.mutable_sub_message(); // Will clear name field.
CHECK(!message.has_name());
- 如果解析器遇到同一个oneof中有多个成员,只有最后一个会被解析成消息。
- oneof不支持
repeated
。 - 反射API对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()
两个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字段时一定要小心. 如果检查oneof的值返回None/NOT_SET
, 它意味着oneof字段没有被赋值或者在一个不同的版本中赋值了。
你不会知道是哪种情况,因为没有办法判断如果未识别的字段是一个oneof字段。
Tag重用问题:
- 将字段移入或移除oneof:在消息被序列化或者解析后,你也许会失去一些信息(有些字段也许会被清除)。
- 删除一个字段或者加入一个字段:在消息被序列化或者解析后,这也许会清除你现在设置的oneof字段。
- 拆分或合并oneof:行为与移动常规字段相似。
Map
如果要创建一个关联映射作为数据定义的一部分,协议缓冲区提供了一个方便的快捷语法:
map<key_type, value_type> map_field = N;
其中key_type
可以是任何整数或字符串类型(因此,除浮点类型和字节之外的任何标量类型)。value_type
可以是任何类型。
例如,如果你希望创建一个project的映射,每个Project
使用一个string
作为key,你可以像下面这样定义:
map<string, Project> projects = 3;
- Map的字段不能是
repeated
。 - 序列化后的顺序和map迭代器的顺序是不确定的,所以你不要期望以固定顺序处理Map。
- 当为
.proto
生成文本格式时,map按键排序。数字键按数字排序。 - 从序列化中解析或者合并时,如果有重复的key则使用所看到的最后一个键。当从文本格式中解析map时,如果存在重复的key,解析将失败。
向后兼容性
map语法序列化后等同于如下内容,因此即使是不支持map语法的protocol buffer实现也是可以处理你的数据的:
message MapFieldEntry {
key_type key = 1;
value_type value = 2;
}
repeated MapFieldEntry map_field = N;
包
您可以向.proto
文件添加可选的软件包说明符,以防止协议消息类型之间的名称冲突。
package foo.bar;
message Open { ... }
然后,您可以在定义消息类型的字段时使用包说明符:
message Foo {
...
foo.bar.Open open = 1;
...
}
包的声明符会根据使用语言的不同影响生成的代码:
- 对于C++,产生的类会被包装在C++的命名空间中,如上例中的
Open
会被封装在foo::bar
命名空间中。 - 对于Java,包声明符会变为java的一个包,除非在.proto文件中提供了一个明确的
option java_package
。 - 对于Python,这个包声明符是被忽略的,因为Python模块是按照其在文件系统中的位置进行组织的。
- 对于Go,包可以被用做Go包名称,除非你显式的提供一个
option go_package
在你的.proto
文件中。 - 对于Ruby,生成的类可以被包装在内置的Ruby名称空间中,转换成Ruby所需的大小写样式 (首字母大写;如果第一个符号不是一个字母,则使用
PB_
前缀),例如Open
会在Foo::Bar
命名空间中。 - 对于javaNano包会使用Java包,除非你在你的文件中显式的提供一个
option java_package
。 - 对于C#包可以转换为
PascalCase
后作为命名空间,除非你在你的文件中显式的提供一个option csharp_namespace
,例如,Open
会在Foo.Bar
命名空间中。
包及名称的解析
Protocol buffer
语言中类型名称的解析与C++
是一致的:首先从最内部开始查找,依次向外进行,每个包会被看作是其父类包的内部包。
当然对于(foo.bar.Baz
)这样以“.”分隔的意味着是从最外层的范围开始。
ProtocolBuffer编译器会解析.proto
文件中定义的所有类型名。对于不同语言的代码生成器会知道如何引用该语言中的每种类型,即使它们使用了不同的规则。
服务定义
如果想要将消息类型用在RPC(远程方法调用)系统中,可以在.proto
文件中定义一个RPC服务接口,protocol buffer编译器将会根据所选择的不同语言生成服务接口代码及存根。如,想要定义一个RPC服务并具有一个方法,该方法能够接收SearchRequest
并返回一个SearchResponse
,此时可以在.proto
文件中进行如下定义:
service SearchService {
rpc Search (SearchRequest) returns (SearchResponse);
}
最直观的使用protocol buffer
的RPC系统是gRPC:在Google开发的一种语言和平台中立的开源RPC系统。gRPC在使用protocl buffer时非常有效,如果使用特殊的protocol buffer插件可以直接为您从.proto
文件中产生相关的RPC代码。
如果您不想使用gRPC,也可以使用协议缓冲区实现自己的RPC。您可以在proto2语言指南中了解更多信息。
还有一些第三方使用Protocol Buffer开发的PRC实现。参考第三方插件wiki查看这些实现的列表。
JSON映射
Proto3 支持JSON的编码规范,使他更容易在不同系统之间共享数据,在下表中逐个描述类型。
如果JSON编码数据中缺少值,或者如果其值为空,这个数据会在解析成protocol buffer的时候被表示成默认值。如果一个字段在protocol buffer中被表示为默认值,则会在转化成JSON编码的时候忽略掉以节省空间。
proto3 | JSON | JSON example | Notes |
---|---|---|---|
message | object | {"fBar": v, | 产生JSON对象,消息字段名可以被映射成lowerCamelCase形式,并且成为JSON对象键,null被接受并成为对应字段的默认值 |
enum | string | "FOO_BAR" | 使用在proto中指定的枚举值的名称 |
map<K,V> | object | {"k": v, | 所有键都转换为字符串 |
repeated V | array | [v, | null被视为空列表 |
bool | true, false | true, | |
string | string | "Hello World!" | |
bytes | base64 string | "YWJjMTIzIT8kKiYoKSctPUB+" | |
int32, fixed32, uint32 | number | 1, | JSON值会是一个十进制数,数值型或者string类型都会接受 |
int64, fixed64, uint64 | string | "1", | JSON值会是一个十进制数,数值型或者string类型都会接受 |
float, double | number | 1. | JSON值会是一个数字或者一个指定的字符串如”NaN”,”infinity”或者”-Infinity”,数值型或者字符串都是可接受的,指数符号也可以接受 |
Any | object | {"@type": "url", | 如果Any包含一个具有特殊JSON映射的值,那么它将被转换如下:{“@type”:xxx,“value”:yyy}。否则,该值将被转换为JSON对象,并且将插入“@type”字段以指示实际的数据类型。 |
Timestamp | string | "1972-01-01T10:00:20. | 使用RFC 3339,其中生成的输出将始终为Z-normalized,并使用0,3,6或9个小数位数 |
Duration | string | "1. | 生成的输出总是0,3,6或者9位小数,具体依赖于所需要的精度,接受所有可以转换为纳秒级的精度 |
Struct | object | { … } | 任何JSON对象。参见struct. . |
Wrapper types | various types | 2, | 包装类型在JSON中使用与包装的原始类型相同的表示形式,但在数据转换和传输期间允许和保留null。 |
FieldMask | string | "f. | 参见fieldmask. . |
ListValue | array | [foo, | |
Value | value | 任何JSON值 | |
NullValue | null | JSON null值 |
选项
在定义.proto
文件时能够标注一系列的options。Options不改变声明的整体含义,但可能会影响在特定上下文中的处理方式。完整的可用选项可以在google/protobuf/descriptor.proto
找到。
一些选项是文件级选项,这意味着它们应该写在顶级作用域中,而不是在任何消息,枚举或服务定义内。一些选项是消息级选项,这意味着它们应该写入消息定义内。
一些选项是字段级选项,这意味着它们应该写在字段定义中。当然有些选项可以作用在域、enum类型、enum值、服务类型及服务方法中。到目前为止,并没有一种有效的选项能作用于所有的类型。
以下是一些常用的选项:
java_package(文件选项)
:这个选项表明生成java类所在的包。如果在.proto文件中没有明确的声明java_package,就采用默认的包名。当然,默认方式产生的java包名并不是最好的方式,不会按照以反向域名开始。如果不选择产生java代码,则该选项将不起任何作用。如:
option java_package = "com.example.foo";
java_multiple_files(文件选项)
:使顶层消息,枚举和服务可以在包级别上定义的,而不是在一个以Proto
后缀命名外部类文件。(其实也就是生成多个java源文件,而不是在一个类中定义)
option java_multiple_files = true;
java_outer_classname(文件选项)
: 该选项表明想要生成Java类的名称。如果在.proto文件中没有明确的java_outer_classname
定义,生成的class名称将会根据.proto文件的名称采用驼峰式的命名方式进行生成。如(foo_bar.proto
生成的java类名为FooBar.java
),如果不选择产生java代码,则该选项不起任何作用。如:
option java_outer_classname = "Ponycopter";
optimize_for(文件选项)
: 可以被设置为SPEED
,CODE_SIZE
,或者LITE_RUNTIME
。这些值将通过如下的方式影响C++及java代码的生成:SPEED (default)
: protocol buffer编译器将生成用于对消息类型进行序列化,解析和执行其他常见操作的代码。这种代码是最优的。CODE_SIZE
: protocol buffer编译器将会产生最少量的类,通过共享或基于反射的代码来实现序列化、语法分析及各种其它操作。采用该方式产生的代码将比SPEED
要少得多, 但是操作要相对慢些。当然实现的类及其对外的API与SPEED
模式都是一样的。这种方式经常用在一些包含大量的.proto
文件而且并不盲目追求速度的应用中。LITE_RUNTIME
: protocol buffer编译器依赖于运行时核心类库来生成代码(即采用libprotobuf-lite
替代libprotobuf
)。这种核心类库由于忽略了一些描述符及反射,要比全类库小得多。这种模式经常在移动手机平台应用多一些。编译器采用该模式产生的方法实现与SPEED
模式不相上下,产生的类实现MessageLite
接口,但它仅提供了Messager接口的一个子集。
option optimize_for = CODE_SIZE;
cc_enable_arenas(文件选项)
: 对于C++产生的代码启用arena allocationobjc_class_prefix(文件选项)
: 将Objective-C类前缀应用在所有Objective-C生成的类和该.proto生成的枚举。没有默认值。您应该使用Apple推荐的3-5个大写字母之间的前缀。请注意,所有2个字母的前缀都由Apple保留。deprecated(字段选项)
: 如果设置为true则表示该字段已经被废弃,并且不应该在新的代码中使用。在大多数语言中没有实际的意义。在java中,这回变成@Deprecated注释,在未来,其他语言的代码生成器也许会在字标识符中产生废弃注释,废弃注释会在编译器尝试使用该字段时发出警告。如果字段没有被使用你也不希望有新用户使用它,尝试使用保留语句替换字段声明。
int32 old_field = 6 [deprecated=true];
自定义选项
ProtocolBuffers允许自定义并使用选项。该功能应该属于一个高级特性,对于大部分人是用不到的。如果你的确希望创建自己的选项,请参看Proto2语言指南。
注意创建自定义选项使用了扩展,扩展只在proto3中可用。
生成访问类
可以通过定义好的.proto文件来生成Java,Python,C++, Ruby, JavaNano, Objective-C,或者C# 代码,需要基于.proto文件运行protocol buffer编译器protoc。如果你没有安装编译器,下载安装包并遵照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
--javanano_out=DST_DIR
--objc_out=DST_DIR
--csharp_out=DST_DIR path/to/file.proto
IMPORT_PATH
指定在解析import
指令时查找.proto
文件的目录。如果忽略该值,则使用当前目录。可以多次传递--proto_path
选项来指定多个导入目录;,它们将会按顺序被访问并执行导入。-I=IMPORT_PATH
是--proto_path
的简化形式。当然也可以提供一个或多个输出路径:
--cpp_out
在目标目录DST_DIR中产生C++代码,可以在C++代码生成参考中查看更多。--java_out
在目标目录DST_DIR中产生Java代码,可以在Java代码生成参考中查看更多。--python_out
在目标目录 DST_DIR 中产生Python代码,可以在Python代码生成参考中查看更多。--go_out
在目标目录 DST_DIR 中产生Go代码,可以在GO代码生成参考中查看更多。--ruby_out
在目标目录 DST_DIR 中产生Go代码,参考正在制作中。--javanano_out
在目标目录DST_DIR中生成JavaNano,JavaNano代码生成器有一系列的选项用于定制自定义生成器的输出:你可以通过生成器的README查找更多信息,JavaNano参考正在制作中。--objc_out
在目标目录DST_DIR中产生Objective-C代码,可以在Objective-C代码生成参考中查看更多。--csharp_out
在目标目录DST_DIR中产生C#代码,可以在C#代码生成参考中查看更多。--php_out
在目标目录DST_DIR中产生PHP代码,可以在PHP代码生成参考中查看更多。
作为一个方便的拓展,如果DST_DIR
以.zip
或者.jar
结尾,编译器会将源码输出写到一个ZIP格式文件或者符合JAR标准的.jar
文件中。注意如果输出已经存在则会被覆盖,编译器还没有智能到可以追加文件。
- 您必须提供一个或多个
.proto
文件作为输入。可以一次指定多个.proto
文件。虽然这些文件相对于当前目录命名,但每个文件必须位于其IMPORT_PATH
下,以便编译器可以确定其规范名称。
参考链接: Language Guide (proto3)