支持C++所有的基本类型:
.proto 类型 | 说明 | C++类型 |
---|---|---|
double | double | |
float | float | |
int32 | 可变长度编码,对负数编码效率低,如果字段可能为负值请使用sint32 | int32 |
int64 | 可变长度编码,对负数编码效率低,如果字段可能为负值请使用sint64 | int64 |
uint32 | 可变长度编码 | uint32 |
uint64 | 可变长度编码 | uint64 |
sint32 | 可变长度编码,对负数编码效率高 | int32 |
sint64 | 可变长度编码,对负数编码效率高 | int64 |
fixed32 | 固定4字节,对值大于2^28编码效率比uint32更高 | uint32 |
fixed64 | 固定8字节,对值大于2^56编码效率比uint64更高 | uint64 |
sfixed32 | 固定4字节 | uint32 |
sfixed64 | 固定8字节 | uint64 |
bool | bool | |
string | 字符串必须包含UTF8编码或者是7-bit ASCII文本,长度不超过2^32 | string |
bytes | 长度不超过2^32的任意字节序列 | string |
还支持复合类型、自定义类型(基本上和C/C++类型定义类型一样)。
消息定义中的每个字段都有一个唯一的编号,这些字段编号用于在消息二进制格式中标识字段,并且在使用消息类型后不应更改。最小编号1,最大编号2^29 - 1 ,注意:
1-15范围内的字段编号需要一个字节来进行编码,包括字段编号、字段类型;
16-2047范围内的字段编号占用俩个字节。因此应该为频繁出现的消息元素保留1到15,也为扩展预留字段编号;
19000 到 19999是协议内部保留字段编号,不能使用;
类型 | 意义 |
---|---|
singular | 单个值(不能超过一个值) |
repeated | 多个值(可以为0个,动态数组) |
oneof | 类似C/C++ union |
类型 | 默认值 |
---|---|
string | 空 |
bytes | 空 |
bool | false |
number | 0 |
enum | 默认为第一个enum的值,必须为0 |
repeated | 空 |
message | 取决于语言 |
枚举常量必须定义在32位整数范围内,由于enum使用varint编码,负值效率低,因此不推荐定义为负数:
message Foo{
enum Status{
option allow_alias = true; // 允许枚举定义相同值的别名,如果不打开此选项编译会报错
UNKNOWN = 0; // 必须为0,兼容proto2 和proto3
SUCCESS = 1;
OK = 1;
FAILED = 2;
NG = 2;
}
Status status = 3;
}
如果通过完全删除枚举条目或将其注释掉来更新枚举类型,将来的用户可以在对类型进行自己的更新时重用该数值。如果他们稍后加载相同的旧版本,这可能会导致严重问题.proto,包括数据损坏、隐私错误等。确保不会发生这种情况的一种方法是指定已删除条目的数值(和/或名称,这也可能导致 JSON 序列化问题)为reserved. 如果将来有任何用户尝试使用这些标识符,protocol buffer 编译器会抱怨。max您可以使用关键字指定保留的数值范围达到最大可能值
不能在同一语句中混合字段名称和数值:
enum Foo {
reserved 2, 15, 9 to 11, 40 to max;
reserved "FOO", "BAR";
}
同c/c++复合类型:
message SearchResponse {
repeated Result results = 1;
}
message Result {
string url = 1;
string title = 2;
repeated string snippets = 3;
}
同C/C++ 嵌套结构体/类定义:
message SearchResponse {
message Result {
string url = 1;
string title = 2;
repeated string snippets = 3;
}
repeated Result results = 1;
}
可在message之外的使用其它消息类型,如上定义的meesage SearchResponse,可以引用其定义的类型:
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;
}
}
}
同C/C++的include,protobuf可以通过 import .proto
来自其他文件的定义来使用它们。要导入另一个的定义,请在文件顶部添加一个 import 语句:.proto
import "myproject/other_protos.proto";
.proto
不会意外重用该编号。int32
、uint32
、int64
、uint64
和bool
都是兼容的——这意味着您可以将字段从其中一种类型更改为另一种类型,而不会破坏向前或向后兼容性。如果从不适合相应类型的线路中解析出一个数字,您将获得与在 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 及更高版本中,未知字段在解析期间保留并包含在序列化输出中。
同C/C void;
消息类型允许您将Any
消息用作嵌入类型,而无需它们的 .proto 定义。AnAny
包含任意序列化消息 as 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); //打包Any类型
// Reading an arbitrary message from Any.
ErrorStatus status = ...;
for (const Any& detail : status.details()) {
if (detail.Is()) {
NetworkErrorDetails network_error;
detail.UnpackTo(&network_error); // 解包Any类型
... processing network_error ...
}
}
如果已经熟悉[proto2 语法],则Any
可以保存任意 proto3 消息,类似于可以允许[扩展]的 proto2 消息。
同C/C++ union;
可以添加任意类型,map和repeated除外:
message SampleMessage {
oneof test_oneof { //union
string name = 4;
SubMessage sub_message = 9;
}
}
设置 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中多次写入,则只能解析看到最后一次的。
不能为repeated类型。
反射API使用oneof。
如果将 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++ 中,俩个消息msg1
和msg2
对象通过调用Swap()
oneofs已经设置的值 :则msg1
和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 中,并且如果知道只设置了一个字段,则可以移动多个字段。
删除 oneof 字段并重新添加:这可能会在消息被序列化和解析后清除当前设置的 oneof 字段。
拆分或合并 oneof:这与移动常规字段有类似的问题。
同C++ map类,语法:
map map_field = N;
.其中key_type
可以是任何整数或字符串类型(除浮点类型、标量类型、bytes
类型)。注意, enum 不是有效的key_type
. value_type
可以是任何类型,除了另外一个Map。
例如创建一个project映射,每一个project与一个字符串映射:
map projects = 3;
repeated
类型。.proto
文本格式时,按键排序(数字key按数字排序)。map 语法在网络上等同于以下内容,因此不支持 map 的协议缓冲区实现仍然可以处理数据:
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++类似,由内->外。protobuf编译器通过解析导入的.proto
文件来解析所有类型名称。每种语言的代码生成器都知道如何引用该语言中的每种类型,即使它有不同的范围规则。
在 RPC(远程过程调用)系统中使用定义的消息类型,可以在一个.proto
文件中定义一个 RPC 服务接口,并且protobuf编译器将以选择的语言生成服务接口代码和存根。例如,定义一个 RPC 服务,它的方法接受你的SearchRequest
并返回 SearchResponse
,你可以在你的.proto
文件中定义它,如下所示:
service SearchService {
rpc Search(SearchRequest) returns (SearchResponse);
}
与protobuf一起使用的最直接的 RPC 系统是[gRPC]:由 Google 开发的一种语言和平台中立的开源 RPC 系统。gRPC 特别适用于协议缓冲区,并允许.proto
使用特殊的协议缓冲区编译器插件直接从文件中生成相关的 RPC 代码。
如果不想使用 gRPC,也可以将协议缓冲区与自己的 RPC 实现一起使用。[可以在Proto2 语言指南]中找到更多相关信息。
还有一些正在进行的第三方项目为 Protocol Buffers 开发 RPC 实现。有关我们了解的项目的链接列表,请参阅[第三方附加组件 wiki 页面]。
Proto3 支持 JSON 中的规范编码,从而更容易在系统之间共享数据。下表中按类型描述了编码,
如果 JSON 编码的数据中缺少某个值,或者其值为null
,则在解析到protobuf为null
将被解释为适当的[默认值。]如果某个字段在协议缓冲区中具有默认值,则在 JSON 编码的数据中默认将其省略以节省空间。实现可以提供选项以在 JSON 编码的输出中发出具有默认值的字段。
proto3 | json | 例 | 说明 |
---|---|---|---|
message | object | {“fooBar”: v, “g”: null, …} | 生成 JSON 对象。消息字段名称映射到 lowerCamelCase 并成为 JSON 对象键。如果指定了field 选项,则指定的值将用作键。解析器接受 lowerCamelCase 名称(或选项指定的名称)和原始 proto 字段名称。是所有字段类型的可接受值,并被视为相应字段类型的默认值。json_name``json_name``null |
enum | string | “FOO_BAR” | 使用 proto 中指定的枚举值的名称。解析器接受枚举名称和整数值。 |
map |
object | {“k”: v, …} | 所有键都转换为字符串。 |
repeated V | array | [v, …] | null 视为空列表[] 。 |
bool | true, false | true, false |
|
string | string | "Hello World!" |
|
bytes | base64 string | "YWJjMTIzIT8kKiYoKSctPUB+" |
JSON 值将是使用带有填充的标准 base64 编码编码为字符串的数据。接受带有/不带有填充的标准或 URL 安全的 base64 编码。 |
int32, fixed32, uint32 | number | 1, -10, 0 | JSON 值将是一个十进制数。接受数字或字符串 |
int64、fixed64、uint64 | string | “1”, “-10” | JSON 值将是一个十进制字符串。接受数字或字符串。 |
float, double | number | 1.1, -10.0, 0, “NaN”, “Infinity” | JSON 值将是一个数字或特殊字符串值“NaN”、“Infinity”和“-Infinity”之一。接受数字或字符串。也接受指数符号。-0 被认为等同于 0。 |
Any | object | {“@type”: “url”, “f”: v, … } | 如果Any 包含具有特殊 JSON 映射的值,则将按如下方式转换:. 否则,会将值转换为 JSON 对象,并插入字段以指示实际数据类型。{"@type": xxx, "value": yyy}``"@type |
Timestamp | string | “1972-01-01T10:00:20.021Z” | 使用 RFC 3339,其中生成的输出将始终进行 Z 归一化,并使用 0、3、6 或 9 位小数。也接受除“Z”之外的偏移量。 |
Duration | string | “1.000340012s”, “1s” | 生成的输出始终包含 0、3、6 或 9 个小数位数,具体取决于所需的精度,后跟后缀“s”。接受任何小数位(没有也可以),只要它们符合纳秒精度并且需要后缀“s”。 |
Struct | object | { … } | 任何 JSON 对象 |
Wrapper types | various types | 2, “2”, “foo”, true, “true”, null, 0, … | 包装器在 JSON 中使用与包装的原始类型相同的表示,除了null 在数据转换和传输期间允许和保留。 |
FieldMask | string | “f.fooBar,h” | |
ListValue | array | [foo, bar, …] | |
Value | value | Any JSON value(null_value 、number_value 、string_value、bool_value、struct_value、list_value) | |
NullValue | null | json null | |
Empty | object | {} | 一个空的 JSON 对象 |
文件中的各个声明.proto
可以用许多选项进行注释。选项不会改变声明的整体含义,但可能会影响它在特定上下文中的处理方式。可用选项的完整列表在 中定义google/protobuf/descriptor.proto
。
optimize_for
(文件选项):可以设置为SPEED
、CODE_SIZE
或LITE_RUNTIME
。这会通过以下方式影响 C++ 和 Java 代码生成器(可能还有第三方生成器):
SPEED
(默认):protocol buffer 编译器将生成用于对消息类型进行序列化、解析和执行其他常见操作的代码。这段代码是高度优化的。
CODE_SIZE
:protocol buffer 编译器将生成最少的类,并将依赖共享的、基于反射的代码来实现序列化、解析和各种其他操作。因此生成的代码将比 with 小得多SPEED
,但操作会更慢。类仍将实现与模式中完全相同的公共 API SPEED
。此模式在包含大量.proto
文件且不需要所有文件都非常快的应用程序中最有用。
option optimize_for = CODE_SIZE;
LITE_RUNTIME
:protocol buffer 编译器将生成仅依赖于“lite”运行时库的类(libprotobuf-lite
,而不是libprotobuf
)。lite 运行时比完整库小得多(大约小一个数量级),但省略了描述符和反射等某些功能。这对于在手机等受限平台上运行的应用程序特别有用。编译器仍将生成所有方法的快速实现,就像它在SPEED
模式中所做的那样。生成的类只会实现MessageLite
每种语言的接口,它只提供完整Message
接口方法的子集。
cc_enable_arenas
(文件选项):为 C++ 生成的代码启用竞技场分配。
objc_class_prefix
(文件选项):设置 Objective-C 类前缀,该前缀添加到所有来自此 .proto 的 Objective-C 生成的类和枚举中。没有默认值。[应该使用Apple 推荐的]介于 3-5 个大写字符之间的前缀。请注意,所有 2 个字母前缀均由 Apple 保留。
eprecated
(字段选项):如果设置为true
,则表示该字段已弃用,不应被新代码使用。在大多数语言中,这没有实际效果。在 Java 中,这成为@Deprecated
注解。将来,其他特定于语言的代码生成器可能会在字段的访问器上生成弃用注释,这反过来会导致在编译尝试使用该字段的代码时发出警告。如果该字段未被任何人使用并且您希望阻止新用户使用它,请考虑将字段声明替换为保留语句。
int32 old_field = 6 [deprecated = true];
Protocol Buffers 还允许您定义和使用自己的选项。大多数人不需要的高级功能。(proto2)
要生成需要使用.proto
文件中定义的消息类型的 Java、Kotlin、Python、C++、Go、Ruby、Objective-C 或 C# 代码,如下命令:
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
。--java_out
生成 Java 代码DST_DIR
。--kotlin_out
在DST_DIR
。--python_out
生成 Python 代码DST_DIR
。--go_out
生成 Go 代码DST_DIR
。--ruby_out
生成 Ruby 代码DST_DIR
。--objc_out
在DST_DIR
。--csharp_out
生成 C# 代码DST_DIR
。--php_out
生成 PHP 代码DST_DIR
。如果输出文件已经存在,它将被覆盖;编译器无法将文件添加到现有存档中。.zip``.jar``.jar
必须提供一个或多个.proto
文件作为输入。.proto
可以一次指定多个文件。尽管文件是相对于当前目录命名的,但每个文件必须位于其中一个IMPORT_PATH
s 中,以便编译器可以确定其规范名称。
一个proto文件中可以定义多个message;
注释方式同C/C++的样式;
学习来源于官网,仅作为学习笔记!