语言指南(proto 3)
语言指南(proto 3)| 协议缓冲区文档 --- Language Guide (proto 3) | Protocol Buffers Documentation
本指南描述了如何使用协议缓冲区语言来构建您的协议缓冲区数据,包括 .proto
文件语法以及如何从 .proto
文件生成数据访问类。它涵盖了协议缓冲区语言的 proto3 修订版。
有关版本语法的信息,请参阅 Protobuf Editions 语言指南。
有关 proto2 语法的信息,请参阅 Proto2 语言指南。
这是一个参考指南 - 有关使用本文档中描述的许多功能的逐步示例,请参阅您选择的语言的教程。
定义消息类型
首先让我们看一个非常简单的例子。假设您想定义一个搜索请求消息格式,其中每个搜索请求都有一个查询字符串、您感兴趣的特定结果页面以及每页的结果数量。以下是用于定义消息类型的 .proto
文件。
syntax = "proto3";
message SearchRequest {
string query = 1;
int32 page_number = 2;
int32 results_per_page = 3;
}
-
文件的第一行指定您正在使用 protobuf 语言规范的 proto3 修订版。
edition
(或syntax
用于 proto2/proto3)必须是文件中第一个非空、非注释行。- 如果未指定
edition
或syntax
,协议缓冲区编译器将假定您使用的是 proto2。
-
SearchRequest
消息定义指定了三个字段(名称/值对),每个字段对应您希望在此类消息中包含的一条数据。每个字段都有一个名称和一个类型。
指定字段类型
在前面的示例中,所有字段都是标量类型:两个整数( page_number
和 results_per_page
)和一个字符串( query
)。您还可以为字段指定枚举和复合类型,如其他消息类型。
分配字段编号
您必须为消息定义中的每个字段提供一个介于 1
和 536,870,911
之间的数字,并遵守以下限制:
- 给定的数字在该消息的所有字段中必须是唯一的。
- 字段编号
19,000
到19,999
保留给 Protocol Buffers 实现。如果在消息中使用这些保留的字段编号,协议缓冲区编译器将会报错。 - 您不能使用任何先前保留的字段编号或已分配给扩展的任何字段编号。
此编号一旦在消息类型中使用便无法更改,因为它标识消息线格式中的字段。“更改”字段编号相当于删除该字段并创建一个具有相同类型但新编号的新字段。有关如何正确删除字段,请参阅删除字段。
字段编号不应重复使用。切勿将字段编号从保留列表中取出以用于新的字段定义。请参阅字段编号重复使用的后果。
您应该使用字段编号 1 到 15 来设置最常用的字段。较低的字段编号值在有线格式中占用更少的空间。例如,范围为 1 到 15 的字段编号需要一个字节来编码。范围为 16 到 2047 的字段编号需要两个字节。您可以在协议缓冲区编码中找到更多相关信息。
重用字段编号的后果
重复使用字段编号会使解码线格式消息变得模糊不清。
protobuf 线格式很精简,并且无法检测使用一种定义编码和使用另一种定义解码的字段。
使用一种定义对字段进行编码,然后使用不同的定义对同一字段进行解码可能会导致:
- 开发人员因调试而损失的时间
- 解析/合并错误(最佳情况)
- 泄露的 PII/SPII
- 数据损坏
字段编号重用的常见原因:
- 重新编号字段(有时为了实现更美观的字段编号顺序)。重新编号会有效地删除并重新添加所有涉及重新编号的字段,从而导致不兼容的线格式更改。
- 删除字段并且不保留该号码以防止将来重复使用。
字段编号限制为 29 位而不是 32 位,因为有三位用于指定字段的线格式。有关更多信息,请参见编码主题。
指定字段基数
消息字段可以是以下之一:
-
单数:
在 proto3 中,有两种类型的单数字段:
-
(推荐)
optional
字段有两种可能的状态之一:- 该字段已设置,并包含一个明确设置或从线上解析的值。它将被序列化到线上。
- 该字段未设置,将返回默认值。它不会被序列化到线上。
您可以检查该值是否已显式设置。
optional
推荐使用显式字段以最大限度地兼容 protobuf 版本和 proto2。 -
隐式: (不推荐) 隐式字段没有显式基数标签,其行为如下:
-
如果该字段是消息类型,它的行为就像一个
optional
字段。 -
如果该字段不是消息,它有两种状态:
- 该字段被设置为非默认(非零)值,该值是显式设置或从线上解析的。它将被序列化到线上。
- 该字段设置为默认(零)值。它不会被序列化到线上。事实上,您无法确定默认(零)值是被设置的、从线上解析的,还是根本没有提供。有关此主题的更多信息,请参见字段存在性。
-
-
-
repeated
:此字段类型可以在格式良好的消息中重复零次或多次。重复值的顺序将被保留。 -
map
:这是一个成对的键/值字段类型。见 map 了解更多此字段类型的信息。
重复字段默认打包
在 proto3 中,标量数值类型的 repeated
字段默认使用 packed
编码。
您可以在packed
编码中找到更多信息 协议缓冲区编码.
消息类型字段始终具有字段存在性
在 proto3 中,消息类型字段已经具有字段存在性。因此,添加 optional
修饰符不会改变字段的存在性。
以下代码示例中 Message2
和 Message3
的定义为所有语言生成相同的代码,并且在二进制、JSON 和 TextFormat 中的表示没有区别
syntax="proto3";
package foo.bar;
message Message1 {}
message Message2 {
Message1 foo = 1;
}
message Message3 {
optional Message1 bar = 1;
}
格式良好的消息
术语“well-formed”在应用于 protobuf 消息时,是指序列化/反序列化的字节。protoc 解析器验证给定的 proto 定义文件是否可解析。
单字段可以在线格式字节中出现多次。解析器将会 接受输入,但只能访问该字段的最后一个实例 通过生成的绑定。见 最后一个获胜 有关此主题的更多信息。
添加更多消息类型
可以在单个 .proto
文件中定义多种消息类型。这在定义多个相关消息时非常有用——例如,如果您想定义与 SearchResponse
消息类型对应的回复消息格式,您可以将其添加到同一个 .proto
中:
message SearchRequest {
string query = 1;
int32 page_number = 2;
int32 results_per_page = 3;
}
message SearchResponse {
...
}
合并消息会导致膨胀 虽然可以在单个 .proto
文件中定义多种消息类型(例如消息、枚举和服务),但当在单个文件中定义大量具有不同依赖项的消息时,也会导致依赖项膨胀。建议每个 .proto
文件包含尽可能少的消息类型。
添加注释
要向您的 .proto
文件添加注释:
-
更喜欢在 .proto 代码元素之前的行上使用 C/C++/Java 行尾样式注释“//”
-
C 风格的内联/多行注释
/* ... */
也被接受。- 使用多行注释时,建议使用‘*’作为边界线。
/** * SearchRequest represents a search query, with pagination options to * indicate which results to include in the response. */
message SearchRequest {
string query = 1;
// Which page number do we want? int32 page_number = 2;
// Number of results to return per page. int32 results_per_page = 3;
}
删除字段
删除字段如果操作不当,可能会导致严重问题。
当您不再需要字段并且所有引用都已从客户端删除时 代码,您可以从消息中删除字段定义。但是,您 必须保留已删除的字段编号。如果不保留字段编号,开发人员将来可能会重复使用该编号。
您还应保留字段名称,以允许您的消息的 JSON 和 TextFormat 编码继续解析。
保留字段编号
如果你更新消息类型时完全删除一个字段,或者 将其注释掉,未来的开发人员在制作时可以重复使用该字段编号 他们自己对类型的更新。这可能会导致严重问题,如中所述。 重复使用字段编号的后果。为确保不会发生这种情况,请将已删除的字段编号添加到reserved
列表中。
protoc 编译器将在任何未来开发人员尝试使用这些保留字段编号时生成错误消息。
message Foo {
reserved 2, 15, 9 to 11;
}
保留字段编号范围是包含的( 9 to 11
与 9, 10, 11
相同)。
保留字段名称
稍后重复使用旧字段名称通常是安全的,除非使用 TextProto 或 JSON 编码,其中字段名称被序列化。为了避免这种风险,您可以将已删除的字段名称添加到 reserved
列表中。
保留名称仅影响 protoc 编译器行为,而不影响运行时行为,但有一个例外:TextProto 实现可能会在解析时丢弃具有保留名称的未知字段(不会像其他未知字段那样引发错误)(目前只有 C++和 Go 实现这样做)。运行时 JSON 解析不受保留名称影响。
message Foo {
reserved 2, 15, 9 to 11;
reserved "foo", "bar";
}
请注意,不能在同一个 reserved
语句中混合使用字段名称和字段编号。
从您的 .proto
生成了什么?
当你在.proto
上运行protocol buffer compiler时,编译器会生成你所选择的语言代码,你将需要使用文件中描述的消息类型,包括获取和设置字段值、将消息序列化到输出流以及从输入流解析消息。
- 对于C++,编译器会从每个文件生成一个
.h
和.cc
文件。.proto
,为文件中描述的每种消息类型创建一个类。 - 对于Java,编译器生成一个
.java
文件,其中包含每种消息类型的类,以及一个特殊的Builder
类用于创建消息类实例。 - 对于Kotlin,除了生成的 Java 代码外,编译器还会为每种消息类型生成一个
.kt
文件,并提供改进的 Kotlin API。这包括一个简化创建消息实例的 DSL、一个可为空的字段访问器和一个复制函数。 - Python 有点不同——Python 编译器生成一个模块,其中包含
.proto
中每种消息类型的静态描述符,然后与 metaclass 一起使用,以在运行时创建必要的 Python 数据访问类。 - 对于Go,编译器会生成一个
.pb.go
文件,其中包含文件中每个消息类型的类型。 - 对于Ruby,编译器生成一个
.rb
文件,其中包含您的消息类型的 Ruby 模块。 - 对于Objective-C,编译器会从每个
.proto
文件生成一个pbobjc.h
和pbobjc.m
文件,并为文件中描述的每种消息类型生成一个类。 - 对于C#,编译器从每个
.cs
文件生成一个.proto
文件,并为文件中描述的每种消息类型生成一个类。 - 对于PHP,编译器为文件中描述的每种消息类型生成一个
.php
消息文件,并为每个.proto
生成一个.php
元数据文件。 你编译的文件。元数据文件用于加载有效的消息类型。 进入描述符池。 - 对于Dart,编译器生成一个
.pb.dart
文件,其中包含文件中每种消息类型的类。
您可以通过遵循所选语言的教程来了解有关使用每种语言的 API 的更多信息。有关更多 API 详细信息,请参阅相关的API 参考
标量值类型
标量消息字段可以具有以下类型之一——表格显示了在 .proto
文件中指定的类型,以及在自动生成的类中对应的类型:
原型 | 笔记 |
---|---|
双倍 | |
浮动 | |
int32 | 使用可变长度编码。对编码负数效率低下——如果您的字段可能有负值,请使用 sint32。 |
int64 | 使用可变长度编码。对负数编码效率低下——如果您的字段可能有负值,请使用 sint64。 |
uint32 | 使用可变长度编码。 |
uint64 | 使用可变长度编码。 |
sint32 | 使用可变长度编码。Signed int value。这些比常规的 int32s 更有效地编码负数。 |
sint64 | 使用可变长度编码。Signed int value。这些比常规的 int64s 更有效地编码负数。 |
fixed32 | 始终为四个字节。如果值通常大于 2^28 ,则比 uint32 更有效。 |
fixed64 | 始终为八个字节。如果值通常大于 2^56,则比 uint64 更高效。 |
sfixed32 | 始终为四个字节。 |
sfixed64 | 始终八个字节。 |
布尔 | |
字符串 | 字符串必须始终包含 UTF-8 编码或 7 位 ASCII 文本,并且不能长于 2^32 。 |
字节 | 可能包含不超过 2^32 的任意字节序列。 |
Proto Type | C++ Type | Java/Kotlin Type[1] | Python Type[3] | Go Type | Ruby Type | C# Type | PHP Type | Dart Type | Rust Type | |
---|---|---|---|---|---|---|---|---|---|---|
double | double | double | float | float64 | Float | double | float | double | f64 | |
float | float | float | float | float32 | Float | float | float | double | f32 | |
int32 | int32_t | int | int | int32 | Fixnum or Bignum (as required) | int | integer | int | i32 | |
int64 | int64_t | long | int/long[4] | int64 | Bignum | long | integer/string[6] | Int64 | i64 | |
uint32 | uint32_t | int[2] | int/long[4] | uint32 | Fixnum or Bignum (as required) | uint | integer | int | u32 | |
uint64 | uint64_t | long[2] | int/long[4] | uint64 | Bignum | ulong | integer/string[6] | Int64 | u64 | |
sint32 | int | int | int32 | Fixnum or Bignum (as required) | int | integer | int | i32 | ||
sint64 | long | int/long[4] | int64 | Bignum | long | integer/string[6] | Int64 | i64 | ||
fixed32 | uint32_t | int[2] | int/long[4] | uint32 | Fixnum or Bignum (as required) | uint | integer | int | u32 | |
fixed64 | uint64_t | 长[2] | int/long[4] | uint64 | Bignum | ulong | integer/string[6] | Int64 | u64 | |
sfixed32 | int32_t | int | int | int32 | Fixnum or Bignum (as required) | int | integer | int | i32 | |
sfixed64 | int64_t | long | int/long[4] | int64 | Bignum | long | integer/string[6] | Int64 | i64 | |
bool | bool | boolean | bool | bool | TrueClass/FalseClass | bool | boolean | bool | bool | |
string | string | String | str/unicode[5] | string | String (UTF-8) | string | string | String | ProtoString | |
bytes | string | ByteString | str (Python 2), bytes (Python 3) | []byte | String (ASCII-8BIT) | ByteString | string | List | ProtoBytes |
[1] Kotlin 使用来自 Java 的相应类型,即使是无符号类型,以确保在混合 Java/Kotlin 代码库中的兼容性。
[2] 在 Java 中,无符号 32 位和 64 位整数使用其有符号对应物表示,最高位仅存储在符号位中。
[3] 在所有情况下,将值设置为字段将执行类型检查以确保其有效。
[4] 64 位或无符号 32 位整数在解码时总是表示为 long,但在设置字段时,如果给定的是 int,则可以是 int。在所有情况下,设置时的值必须符合表示的类型。参见[2]。
[5] Python 字符串在解码时表示为 unicode,但如果给定 ASCII 字符串,则可以是 str(此行为可能会更改)。
[6] Integer 用于 64 位机器,string 用于 32 位机器。
您可以了解更多关于这些类型在序列化时如何编码的信息 消息输入 协议缓冲区编码
默认字段值
当消息被解析时,如果编码的消息字节不包含特定字段,则在解析对象中访问该字段会返回该字段的默认值。默认值是类型特定的:
- 对于字符串,默认值为空字符串。
- 对于字节,默认值为空字节。
- 对于 bools,默认值为 false。
- 对于数值类型,默认值为零。
- 对于消息字段,该字段未设置。其确切值为 语言相关。见 生成的代码指南 了解详细信息。
- 对于枚举,默认值是第一个定义的枚举值,必须为 0。请参阅枚举默认值。
重复字段的默认值为空(通常在适当的语言中为空列表)。
映射字段的默认值为空(通常是适当语言中的空映射)。
请注意,对于隐式存在的标量字段,一旦消息被解析,就无法判断该字段是否被显式设置为默认值(例如布尔值是否被设置为 false
)或根本没有设置:在定义消息类型时应牢记这一点。例如,如果您不希望某些行为在默认情况下发生,请不要有一个布尔值在设置为 false
时切换某些行为。还要注意,如果标量消息字段被设置为默认值,该值将不会在传输中序列化。如果浮点数或双精度值被设置为+0,它将不会被序列化,但-0 被认为是不同的并且将被序列化。
请参阅您选择的语言的生成代码指南,了解有关生成代码中默认值如何工作的更多详细信息
枚举
当您定义消息类型时,您可能希望其字段之一仅具有预定义值列表中的一个值。例如,假设您想为每个 SearchRequest
添加一个 corpus
字段,其中语料库可以是 UNIVERSAL
、 WEB
、 IMAGES
、 LOCAL
、 NEWS
、 PRODUCTS
或 VIDEO
。您可以通过在消息定义中添加一个 enum
,并为每个可能的值添加一个常量来非常简单地实现这一点。
在以下示例中,我们添加了一个名为 Corpus
的 enum
,包含所有可能的值,以及一个类型为 Corpus
的字段:
enum Corpus {
CORPUS_UNSPECIFIED = 0;
CORPUS_UNIVERSAL = 1;
CORPUS_WEB = 2;
CORPUS_IMAGES = 3;
CORPUS_LOCAL = 4;
CORPUS_NEWS = 5;
CORPUS_PRODUCTS = 6;
CORPUS_VIDEO = 7;
}
message SearchRequest {
string query = 1;
int32 page_number = 2;
int32 results_per_page = 3;
Corpus corpus = 4;
}
枚举默认值
SearchRequest.corpus
字段的默认值是 CORPUS_UNSPECIFIED
因为那是枚举中定义的第一个值。
在 proto3 中,枚举定义中的第一个值必须为零,并且应命名为ENUM_TYPE_NAME_UNSPECIFIED
或 ENUM_TYPE_NAME_UNKNOWN
。这是因为:
- 必须有一个零值,这样我们才能将 0 用作数字 默认值.
- 零值需要是第一个元素,以确保兼容性 proto2 语义,其中第一个枚举值是默认值,除非明确指定了其他值。
还建议第一个默认值除了“此值未指定”之外没有其他语义意义
枚举值别名
您可以通过为不同的枚举常量分配相同的值来定义别名。为此,您需要将 allow_alias
选项设置为 true
。否则,当找到别名时,协议缓冲区编译器会生成警告消息。尽管所有别名值在序列化时都是有效的,但在反序列化时仅使用第一个值。
enum EnumAllowingAlias {
option allow_alias = true;
EAA_UNSPECIFIED = 0;
EAA_STARTED = 1;
EAA_RUNNING = 1;
EAA_FINISHED = 2;
}
enum EnumNotAllowingAlias {
ENAA_UNSPECIFIED = 0;
ENAA_STARTED = 1;
// ENAA_RUNNING = 1; // Uncommenting this line will cause a warning message. ENAA_FINISHED = 2;
}
枚举常量必须在 32 位整数的范围内。由于enum
值使用 varint 编码 on the 线,负值效率低下,因此不推荐使用。你可以定义 enum
在消息定义中,如前面的示例中,或在外部 – 这些 enum
可以在 .proto
文件中的任何消息定义中重用。您还可以使用在一个消息中声明的 enum
类型作为不同消息中字段的类型,使用语法 _MessageType_._EnumType_
。
当你在使用.proto
文件运行协议缓冲区编译器时,生成的代码将会有一个对应的enum
用于 Java、Kotlin 或 C++,或者一个特殊的EnumDescriptor
类用于 Python,该类用于在运行时生成的类中创建一组具有整数值的符号常量。
重要
生成的代码可能会受到特定语言中枚举数数量限制的影响(对于一种语言,限制在几千个)。请查看您计划使用的语言的限制。在反序列化期间,未识别的枚举值将保留在消息中,尽管当消息被反序列化时如何表示取决于语言。在支持具有超出指定符号范围的值的开放枚举类型的语言中,例如 C++和 Go,未知的枚举值只是作为其底层整数表示存储。在具有封闭枚举类型的语言中,例如 Java,枚举中的一个案例用于表示未识别的值,并且可以通过特殊访问器访问底层整数。在任何一种情况下,如果消息被序列化,未识别的值仍将与消息一起序列化。
重要
对于 有关枚举应如何工作的信息与它们当前在中的工作方式形成对比 不同语言,见 枚举行为. 。有关如何在应用程序中使用消息enum
的更多信息,请参阅生成的代码指南 为您选择的语言。
保留值
如果您通过完全删除枚举条目或将其注释掉来更新枚举类型,未来的用户在更新类型时可以重用该数值。这可能会导致严重的问题,例如在他们稍后加载相同 .proto
的旧实例时,包括数据损坏、隐私漏洞等。确保这种情况不会发生的一种方法是指定已删除条目的数值(和/或名称,这也可能导致 JSON 序列化问题)为 reserved
。如果未来的用户尝试使用这些标识符,协议缓冲区编译器将会报错。您可以使用 max
关键字指定保留的数值范围达到最大可能值。
enum Foo {
reserved 2, 15, 9 to 11, 40 to max;
reserved "FOO", "BAR";
}
请注意,不能在同一个 reserved
语句中混合字段名称和数值。
使用其他消息类型
您可以使用其他消息类型作为字段类型。例如,假设您想在每个 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 "myproject/other_protos.proto";
默认情况下,您只能使用直接导入的 .proto
文件中的定义。但是,有时您可能需要将 .proto
文件移动到新位置。与其直接移动 .proto
文件并在一次更改中更新所有调用点,您可以在旧位置放置一个占位符 .proto
文件,以使用 import public
概念将所有导入转发到新位置。
请注意,公共导入功能在 Java、Kotlin、TypeScript、JavaScript、GCL 以及使用 protobuf 静态反射的 C++ 目标中不可用。
import public
依赖项可以被导入包含 import public
语句的 proto 的任何代码传递性地依赖。例如:
// 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 消息中使用它们,反之亦然。但是,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;
}
您可以将消息嵌套得尽可能深。在下面的示例中,请注意,两个名为 Inner
的嵌套类型是完全独立的,因为它们是在不同的消息中定义的:
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;
}
}
}
更新消息类型
如果现有的消息类型不再满足您的所有需求——例如,您希望消息格式有一个额外的字段——但您仍然希望使用旧格式创建的代码,请不要担心!使用二进制线格式时,更新消息类型非常简单,不会破坏任何现有代码。
注意
如果 你使用 JSON 或 原型文本格式 存储您的协议缓冲区消息,您可以进行的更改 proto 定义不同。检查 Proto 最佳实践 和以下规则:
- 不要更改任何现有字段的字段编号。“更改”字段 数字等同于删除该字段并添加一个新字段 相同类型。如果您想重新编号字段,请参阅说明。 删除字段.
- 如果添加新字段,使用“旧”消息格式序列化的任何消息仍然可以由新生成的代码解析。您应该记住这些元素的默认值,以便新代码能够正常工作。 代码可以正确地与旧代码生成的消息进行交互。同样地, 由您的新代码创建的消息可以由您的旧代码解析:old 二进制文件在解析时会忽略新字段。请参阅 未知字段 部分了解详细信息。
- 字段可以被删除,只要字段编号不再被使用 更新的消息类型。您可能希望重命名该字段。 添加前缀“OBSOLETE_”,或使字段编号 保留,以便将来的
.proto
用户不会意外地重新使用该号码。 int32
、uint32
、int64
、uint64
和bool
都是兼容的——这意味着您可以在不破坏前向或后向兼容性的情况下将字段从这些类型之一更改为另一种。如果从线上解析的数字不符合相应的类型,您将获得与在 C++ 中将数字转换为该类型时相同的效果(例如,如果将 64 位数字读取为 int32,它将被截断为 32 位)。sint32
和sint64
彼此兼容但 不 兼容 与其他整数类型兼容。string
和bytes
兼容,只要这些字节是有效的 UTF-8。- 嵌入消息与
bytes
兼容,如果这些字节包含消息的编码实例。 fixed32
兼容sfixed32
,fixed64
兼容sfixed64
。- 对于
string
、bytes
和 message 字段,单数形式是兼容的repeated
。给定重复字段的序列化数据作为输入,期望该字段为单一的客户端将采用最后一个输入值(如果它是一个原始类型字段)或合并所有输入元素(如果它是一个消息类型字段)。请注意,这对包括not在内的数值类型通常是不安全的。 布尔值和枚举。数值类型的重复字段被序列化在 打包格式默认情况下,当需要单个字段时,将无法正确解析。 enum
与int32
、uint32
、int64
和uint64
在线格式方面兼容(请注意,如果值不适合,它们将被截断)。但是,请注意,客户端代码在反序列化消息时可能会对它们进行不同的处理:例如,未识别的 proto3enum
值将保留在消息中,但在反序列化消息时如何表示取决于语言。Int 字段始终只保留其值。- 将单个
optional
字段或扩展更改为newoneof
的成员是二进制兼容的,但对于某些语言(特别是 Go 语言)来说, 生成代码的 API 将以不兼容的方式更改。出于这个原因, 谷歌不会在其公共 API 中进行此类更改,如文档中所述 AIP-180。关于源代码兼容性的相同警告,如果您确定没有代码一次设置多个字段,则将多个字段移动到新的oneof
可能是安全的。将字段移动到现有的oneof
是不安全的。同样,将单个字段oneof
更改为optional
字段或扩展是安全的。 - 在
map<K, V>
和相应的repeated
之间更改字段 消息字段是二进制兼容的(请参阅下面的地图,了解消息布局和其他限制)。但是,更改的安全性取决于应用程序:在反序列化和重新序列化消息时,使用repeated
字段定义的客户端将生成语义上相同的结果;但是,使用map
字段定义的客户端可能会重新排序条目并删除具有重复键的条目。
未知字段
未知字段是格式良好的协议缓冲区序列化数据,表示解析器无法识别的字段。例如,当旧二进制文件解析由具有新字段的新二进制文件发送的数据时,这些新字段在旧二进制文件中变为未知字段。
Proto3 消息保留未知字段,并在解析和序列化输出中包含它们,这与 proto2 行为一致。
保留未知字段
某些操作可能会导致未知字段丢失。例如,如果您执行以下操作之一,未知字段将丢失:
- 将 proto 序列化为 JSON。
- 遍历消息中的所有字段以填充新消息。
为了避免丢失未知字段,请执行以下操作:
- 使用二进制;避免使用文本格式进行数据交换。
- 使用面向消息的 API,例如
CopyFrom()
和MergeFrom()
,来复制数据,而不是逐字段复制
TextFormat 是一个特殊情况。序列化到 TextFormat 会使用字段编号打印未知字段。但是,如果有使用字段编号的条目,则将 TextFormat 数据解析回二进制 proto 会失败。
任何
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_
。
不同语言的实现将支持运行时库助手以类型安全的方式打包和解包 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 google::protobuf::Any& detail : status.details()) {
if (detail.Is<NetworkErrorDetails>()) {
NetworkErrorDetails network_error;
detail.UnpackTo(&network_error);
... processing network_error ...
}
}
其中之一
如果您的消息包含许多单一字段,并且每次最多只能设置一个字段,则可以使用 oneof 功能来强制执行此行为并节省内存。
oneof 字段类似于可选字段,不同之处在于 oneof 中的所有字段共享内存,并且同一时间最多只能设置一个字段。设置 oneof 的任何成员会自动清除所有其他成员。您可以使用特殊的 case()
或 WhichOneof()
方法(取决于您选择的语言)检查 oneof 中设置的值(如果有)。
请注意,如果设置了多个值,则由 proto 中顺序确定的最后一个设置值将覆盖所有先前的值。
oneof 字段的字段编号在封闭消息中必须是唯一的。
使用 Oneof
要在您的 .proto
中定义一个 oneof,您可以使用 oneof
关键字,然后是您的 oneof 名称,在本例中为 test_oneof
:
message SampleMessage {
oneof test_oneof {
string name = 4;
SubMessage sub_message = 9;
}
}
然后将您的 oneof 字段添加到 oneof 定义中。您可以添加任何类型的字段,除了 map
字段和 repeated
字段。如果需要向 oneof 添加重复字段,可以使用包含重复字段的消息。
在您生成的代码中,oneof 字段具有与常规字段相同的 getters 和 setters。您还可以获得一种特殊方法来检查 oneof 中设置了哪个值(如果有的话)。您可以在相关的 API 参考 中找到有关所选语言的 oneof API 的更多信息。
一项功能
-
设置一个 oneof 字段将自动清除 oneof 的所有其他成员。因此,如果设置多个 oneof 字段,则只有最后设置的字段仍然有值。
SampleMessage message; message.set_name("name"); CHECK_EQ(message.name(), "name"); // Calling mutable_sub_message() will clear the name field and will set // sub_message to a new instance of SubMessage with none of its fields set. message.mutable_sub_message(); CHECK(message.name().empty());
-
如果解析器在传输线上遇到同一个 oneof 的多个成员,则仅使用在解析消息中看到的最后一个成员。在传输线上解析数据时,从字节的开头开始,评估下一个值,并应用以下解析规则:
-
首先,检查同一个 oneof 中是否设置了不同的字段,如果是,则将其清除。
-
然后应用这些内容,就好像该字段不是 oneof 中的一个字段一样
- 原语将覆盖已设置的任何值
- 消息将合并到任何已设置的值中
-
-
oneof 不能是
repeated
。 -
反射 API 适用于 oneof 字段。
-
如果将 oneof 字段设置为默认值(例如将 int32 oneof 字段设置为 0),则该 oneof 字段的“case”将被设置,并且该值将在线上序列化。
-
如果你使用 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_EQ(msg2.name(), "name");
向后兼容性问题
在添加或删除 oneof 字段时要小心。如果检查 oneof 的值返回 None
/ NOT_SET
,这可能意味着 oneof 尚未设置,或者已设置为 oneof 的不同版本中的字段。无法区分,因为无法知道线上未知字段是否是 oneof 的成员。
标签重用问题
- 将单个字段移入或移出 oneof:消息序列化和解析后,您可能会丢失一些信息(某些字段将被清除)。但是,您可以安全地将单个字段移入新的 oneof,并且如果已知只有一个字段被设置,您可能能够移动多个字段。有关详细信息,请参阅更新消息类型。
- 删除一个 oneof 字段并重新添加:这可能会在消息序列化和解析后清除您当前设置的 oneof 字段。
- 拆分或合并 oneof:这与移动单个字段有类似的问题。
Maps 映射
如果您想在数据定义中创建关联映射,protocol buffers 提供了一个方便的快捷语法:
map<key_type, value_type> map_field = N;
…其中 key_type
可以是任何整数或字符串类型(因此,任何标量类型,除了浮点类型和 bytes
)。请注意,枚举和 proto 消息都不是 key_type
的有效类型。 value_type
可以是任何类型,除了另一个 map。
因此,例如,如果您想创建一个项目映射,其中每个 Project
消息都与一个字符串键相关联,您可以这样定义它:
map<string, Project> projects = 3;
映射功能
- 映射字段不能是
repeated
。 - 线格式排序和映射值的映射迭代顺序未定义,因此您不能依赖映射项按特定顺序排列。
- 生成
.proto
的文本格式时,映射按键排序。数字键按数值排序。 - 从线上解析或合并时,如果有重复的映射键,则使用最后一个看到的键。从文本格式解析映射时,如果有重复的键,解析可能会失败。
- 如果为映射字段提供了键但没有值,则该字段序列化时的行为取决于语言。在 C++、Java、Kotlin 和 Python 中,序列化的是该类型的默认值,而在其他语言中则不序列化任何内容。
- 没有符号
FooEntry
可以与映射foo
在同一范围内存在,因为FooEntry
已经被映射的实现使用。
生成的地图 API 目前适用于所有支持的语言。您可以在相关的 API 参考中了解有关所选语言的地图 API 的更多信息。
向后兼容性
映射语法在传输时等效于以下内容,因此不支持映射的协议缓冲区实现仍然可以处理您的数据:
message MapFieldEntry {
key_type key = 1;
value_type value = 2;
}
repeated MapFieldEntry map_field = N;
任何支持映射的协议缓冲区实现都必须生成和接受可以被早期定义接受的数据。
包裹
您可以向 .proto
文件添加一个可选的 package
说明符,以防止协议消息类型之间的名称冲突。
package foo.bar;
message Open {
... }
然后可以在定义消息类型的字段时使用包说明符:
message Foo {
...
foo.bar.Open open = 1;
...
}
包说明符影响生成代码的方式取决于您选择的语言:
- 在 C++ 中,生成的类被包装在一个 C++ 命名空间内。例如,
Open
将在命名空间foo::bar
中。 - 在 Java 和 Kotlin 中,package 用作 Java 包,除非您在
.proto
文件中明确提供option java_package
。 - 在 Python 中,
package
指令会被忽略,因为 Python 模块是根据它们在文件系统中的位置组织的。 - 在 Go 中,
package
指令被忽略,生成的.pb.go
文件位于以相应的go_proto_library
Bazel 规则命名的包中。对于开源项目,您必须提供go_package
选项或设置 Bazel-M
标志。 - 在 Ruby 中,生成的类被包装在嵌套的 Ruby 命名空间内,转换为所需的 Ruby 大写风格(首字母大写;如果第一个字符不是字母,则在前面加上
PB_
)。例如,Open
将位于命名空间Foo::Bar
中。 - 在 PHP 中,包在转换为 PascalCase 后用作命名空间,除非您在
option php_namespace
文件中明确提供了.proto
。例如,Open
将在命名空间Foo\Bar
中。 - 在 C# 中,包在转换为 PascalCase 后用作命名空间,除非您在
option csharp_namespace
文件中明确提供了.proto
。例如,Open
将在命名空间Foo.Bar
中。
请注意,即使 package
指令不会直接影响生成的代码,例如在 Python 中,仍然强烈建议为 .proto
文件指定包,否则可能会导致描述符中的命名冲突,并使 proto 在其他语言中不可移植。
包和名称解析
在协议缓冲区语言中,类型名称解析的工作方式类似于 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,也可以使用 protocol buffers 与 你自己的 RPC 实现。你可以在其中找到更多信息。 Proto2 语言指南.
还有许多正在进行的第三方项目来开发 RPC 协议缓冲区的实现。我们知道的项目链接列表 关于,见 第三方插件维基页面。
JSON 映射
标准的 protobuf 二进制线格式是使用 protobuf 的两个系统之间通信的首选序列化格式。对于与使用 JSON 而不是 protobuf 线格式的系统通信,Protobuf 支持在JSON中的规范编码。
选项
单个声明在 .proto
文件中可以用许多选项进行注释。选项不会改变声明的整体含义,但可能会影响在特定上下文中的处理方式。可用选项的完整列表在 /google/protobuf/descriptor.proto 中定义。
有些选项是文件级选项,这意味着它们应该写在顶级范围内,而不是在任何消息、枚举或服务定义中。有些选项是消息级选项,这意味着它们应该写在消息定义中。有些选项是字段级选项,这意味着它们应该写在字段定义中。选项也可以写在枚举类型、枚举值、oneof 字段、服务类型和服务方法上;然而,目前这些都没有有用的选项。
以下是一些最常用的选项:
-
java_package
(文件选项):要用于生成的 Java/Kotlin 类的包。如果在.proto
文件中没有给出明确的java_package
选项,则默认情况下将使用 proto 包(使用.proto
文件中的“package”关键字指定)。但是,proto 包通常不是好的 Java 包,因为 proto 包不需要以反向域名开头。如果不生成 Java 或 Kotlin 代码,则此选项无效。option java_package = "com.example.foo";
-
java_outer_classname
(文件选项):要生成的包装 Java 类的类名(以及文件名)。如果在.proto
文件中未指定显式java_outer_classname
,则类名将通过将.proto
文件名转换为驼峰式命名法来构造(因此foo_bar.proto
变为FooBar.java
)。如果禁用java_multiple_files
选项,则为.proto
文件生成的所有其他类/枚举等将作为嵌套类/枚举等在此外部包装 Java 类中生成。如果不生成 Java 代码,则此选项无效。option java_outer_classname = "Ponycopter";
-
java_multiple_files
(文件选项):如果为 false,则只会为此.proto
文件生成一个.java
文件,并且为顶级消息、服务和枚举生成的所有 Java 类/枚举等都将嵌套在外部类中(请参阅java_outer_classname
)。如果为 true,则会为顶级消息、服务和枚举生成的每个 Java 类/枚举等生成单独的.java
文件,并且为此.proto
文件生成的包装 Java 类不会包含任何嵌套类/枚举等。这是一个布尔选项,默认为false
。如果不生成 Java 代码,则此选项无效。option java_multiple_files = true;
-
optimize_for
(文件选项):可以设置为SPEED
、CODE_SIZE
或LITE_RUNTIME
。这会影响 C++和 Java 代码生成器(以及可能的第三方生成器),具体如下:- 协议缓冲区编译器将生成代码,用于序列化、解析和对消息类型执行其他常见操作。此代码经过高度优化。
- 协议缓冲区编译器将生成最小的类,并依赖于共享的基于反射的代码来实现序列化、解析和各种其他操作。因此,生成的代码将比
SPEED
小得多,但操作会更慢。类仍将实现与SPEED
模式完全相同的公共 API。此模式在包含大量.proto
文件且不需要所有文件都非常快的应用程序中最有用。 LITE_RUNTIME
: 协议缓冲区编译器将生成仅依赖于“lite”运行时库的类(libprotobuf-lite
而不是libprotobuf
)。lite 运行时库比完整库小得多(大约小一个数量级),但省略了某些功能,如描述符和反射。这对于在受限平台(如手机)上运行的应用程序特别有用。编译器仍将像在SPEED
模式下一样生成所有方法的快速实现。生成的类将在每种语言中仅实现MessageLite
接口,该接口仅提供完整Message
接口方法的子集。
option optimize_for = CODE_SIZE;
-
cc_generic_services
,java_generic_services
,py_generic_services
(文件选项): 通用服务已弃用。协议缓冲区编译器是否应基于服务定义在 C++、Java 和 Python 中生成抽象服务代码。由于遗留原因,这些默认值为true
。然而,从 2.3.0 版本(2010 年 1 月)开始,建议 RPC 实现提供代码生成器插件,以生成更具体于每个系统的代码,而不是依赖于“抽象”服务。// This file relies on plugins to generate service code. option cc_generic_services = false; option java_generic_services = false; option py_generic_services = false;
-
cc_enable_arenas
(文件选项): 启用 C++ 生成代码的 arena 分配。 -
objc_class_prefix
(文件选项):设置 Objective-C 类前缀,该前缀会添加到此 .proto 中生成的所有 Objective-C 类和枚举之前。没有默认值。您应使用 Apple 推荐的 3-5 个大写字符的前缀。请注意,所有 2 个字母的前缀均由 Apple 保留。 -
packed
(字段选项):默认情况下,true
在基本数值类型的重复字段上使用,导致更紧凑的编码。要使用未打包的线格式,可以将其设置为false
。这提供了与 2.3.0 之前版本的解析器的兼容性(很少需要),如下例所示:repeated int32 samples = 4 [packed = false];
-
deprecated
(字段选项):如果设置为true
,则表示该字段已弃用,不应在新代码中使用。在大多数语言中,这实际上没有任何效果。在 Java 中,这将变成@Deprecated
注解。对于 C++,clang-tidy 将在使用弃用字段时生成警告。将来,其他特定语言的代码生成器可能会在字段的访问器上生成弃用注解,这反过来会在编译尝试使用该字段的代码时发出警告。如果该字段没有被任何人使用,并且您想阻止新用户使用它,请考虑用保留语句替换字段声明。int32 old_field = 6 [deprecated = true];
枚举值选项
枚举值选项是支持的。您可以使用 deprecated
选项来表示不应再使用某个值。您还可以使用扩展创建自定义选项。
以下示例显示了添加这些选项的语法:
import "google/protobuf/descriptor.proto";
extend google.protobuf.EnumValueOptions {
optional string string_name = 123456789;
}
enum Data {
DATA_UNSPECIFIED = 0;
DATA_SEARCH = 1 [deprecated = true];
DATA_DISPLAY = 2 [
(string_name) = "display_value"
];
}
读取 string_name
选项的 C++ 代码可能如下所示:
const absl::string_view foo = proto2::GetEnumDescriptor<Data>()
->FindValueByName("DATA_DISPLAY")->options().GetExtension(string_name);
请参阅自定义选项,了解如何将自定义选项应用于枚举值和字段。。
自定义选项
协议缓冲区还允许您定义和使用自己的选项。请注意,这是一个高级功能,大多数人不需要。如果您确实认为 你需要创建自己的选项,请参见 Proto2 语言指南 有关详细信息。请注意,创建自定义选项使用 扩展,仅允许用于 proto3 中的自定义选项
选项保留
选项具有保留的概念,它控制选项是否保留在生成的代码中。选项默认具有运行时保留,这意味着它们保留在生成的代码中,因此在运行时在生成的描述符池中可见。但是,您可以设置 retention = RETENTION_SOURCE
以指定选项(或选项中的字段)在运行时不保留。这称为源保留。
选项保留是一项高级功能,大多数用户不需要担心,但如果您希望在不增加二进制文件大小的情况下使用某些选项,它可能会很有用。具有源保留的选项对于 protoc
和 protoc
插件仍然可见,因此代码生成器可以使用它们来自定义其行为。
保留可以直接在选项上设置,如下所示:
extend google.protobuf.FileOptions {
optional int32 source_retention_option = 1234
[retention = RETENTION_SOURCE];
}
它也可以设置在一个普通字段上,在这种情况下,只有当该字段出现在选项中时才会生效:
message OptionsMessage {
int32 source_retention_field = 1 [retention = RETENTION_SOURCE];
}
您可以设置 retention = RETENTION_RUNTIME
,如果您愿意,但这没有效果,因为这是默认行为。当消息字段标记为 RETENTION_SOURCE
时,其全部内容将被删除;其中的字段不能通过尝试设置 RETENTION_RUNTIME
来覆盖这一点。
注意
截至 Protocol Buffers 22.0,选项保留的支持仍在进行中,目前仅支持 C++ 和 Java。Go 从 1.29.0 开始支持。Python 支持已完成,但尚未发布。选项目标
字段具有 targets
选项,该选项控制字段在用作选项时可能适用的实体类型。例如,如果字段具有 targets = TARGET_TYPE_MESSAGE
,则该字段不能在枚举(或任何其他非消息实体)上的自定义选项中设置。Protoc 会强制执行此操作,如果违反目标约束,将引发错误。
乍一看,这个功能似乎没有必要,因为每个自定义选项都是特定实体选项消息的扩展,已经将选项限制在一个实体上。然而,当您有一个共享的选项消息应用于多个实体类型,并且希望控制该消息中各个字段的使用时,选项目标是有用的。例如:
message MyOptions {
string file_only_option = 1 [targets = TARGET_TYPE_FILE];
int32 message_and_enum_option = 2 [targets = TARGET_TYPE_MESSAGE,
targets = TARGET_TYPE_ENUM];
}
extend google.protobuf.FileOptions {
optional MyOptions file_options = 50000;
}
extend google.protobuf.MessageOptions {
optional MyOptions message_options = 50000;
}
extend google.protobuf.EnumOptions {
optional MyOptions enum_options = 50000;
}
// OK: this field is allowed on file options option (file_options).file_only_option = "abc";
message MyMessage {
// OK: this field is allowed on both message and enum options option (message_options).message_and_enum_option = 42;
}
enum MyEnum {
MY_ENUM_UNSPECIFIED = 0;
// Error: file_only_option cannot be set on an enum. option (enum_options).file_only_option = "xyz";
}
生成您的类
要生成与.proto
文件中定义的消息类型一起使用的 Java、Kotlin、Python、C++、Go、Ruby、Objective-C 或 C#代码,您需要在.proto
文件上运行协议缓冲区编译器protoc
。 还没有安装编译器, 下载软件包并按照 README 中的说明进行操作。对于 Go,您还需要为编译器安装一个特殊的代码生成器插件;您可以在golang/protobuf中找到此插件和安装说明。 GitHub 上的 repository
协议编译器的调用方式如下:
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
指定了在解析import
指令时查找.proto
文件的目录。如果省略,则使用当前目录。可以通过多次传递--proto_path
选项来指定多个导入目录;它们将按顺序搜索。-I=_IMPORT_PATH_
可以用作--proto_path
的简写形式。 -
您可以提供一个或多个输出指令:
--cpp_out
生成DST_DIR
中的 C++ 代码。请参见 C++ 生成的代码参考 更多。--java_out
生成DST_DIR
中的 Java 代码。请参阅 Java 生成的代码参考 更多。--kotlin_out
生成额外的 Kotlin 代码在DST_DIR
。请参阅 Kotlin 生成的代码参考 更多内容。--python_out
生成DST_DIR
中的 Python 代码。请参阅 Python 生成的代码参考 了解更多。--go_out
生成DST_DIR
中的 Go 代码。请参阅 Go 生成的代码参考 更多。--ruby_out
生成DST_DIR
中的 Ruby 代码。请参阅 Ruby 生成的代码参考 更多内容。--objc_out
生成DST_DIR
中的 Objective-C 代码。见 Objective-C 生成的代码参考 更多内容。--csharp_out
生成 C# 代码在DST_DIR
。见 C# 生成的代码参考 更多内容。--php_out
生成DST_DIR
中的 PHP 代码。请参阅 PHP 生成的代码参考 更多。
作为额外的便利,如果
DST_DIR
以.zip
或.jar
结尾,编译器将把输出写入具有给定名称的单个 ZIP 格式归档文件中。.jar
输出还将根据 Java JAR 规范提供清单文件。请注意,如果输出归档文件已存在,它将被覆盖。 -
您必须提供一个或多个
.proto
文件作为输入。多个.proto
文件可以一次指定。尽管文件是相对于 当前目录,每个文件必须位于IMPORT_PATH
之一,以便编译器可以确定其规范名称。
文件位置
最好不要将 .proto
文件与其他语言源文件放在同一目录中。考虑在项目的根包下为 .proto
文件创建一个子包 proto
。
位置应与语言无关
在处理 Java 代码时,将相关的 .proto
文件放在与 Java 源代码相同的目录中是很方便的。然而,如果任何非 Java 代码使用相同的 protos,路径前缀将不再有意义。因此,一般来说,将 protos 放在相关的与语言无关的目录中,例如 //myteam/mypackage
。
此规则的例外情况是,当明确 protos 仅在 Java 环境中使用时,例如用于测试。
支持的平台
有关以下内容的信息:
- 有关支持的操作系统、编译器、构建系统和 C++ 版本,请参阅基础 C++ 支持政策。
- 支持的 PHP 版本,请参阅 Supported PHP versions。