基本语法
package foo.bar;// 指定proto版本, 默认proto2syntax = "proto3"// 字段后的数字是字段的唯一标识, 用来在消息的二进制格式中识别各个字段的. 一旦开始使用就不能够再改变// [1, 15]之内的标识号在编码时会占用一个字节, [16, 2047]之内占用2个字节, 所以应该为那些频繁出现的消息元素保留[1,15]之内的标识号. 要为将来可能添加的频繁出现的标识号预留一些// 最小标识从1开始, 最大到2^29 - 1/536870911, 不可以使用[19000 - 19999], (从FieldDescriptor::kFirstReservedNumber 到 FieldDescriptor::kLastReservedNumber)// 的标识号, Protobuf协议实现中对这些进行了预留message MyMessage1 {// 保留标识符, 如果通过删除或注释字段, 后续更新消息定义时可能重用这些删除的字段, 如果使用旧版定义加载相同消息, 会导致严重问题,包括数据损坏,隐私错误等// 为保证不会发生这种情况, 把不再使用的标识符指定保留标识符, 编译器会警告未来尝试使用这些标识符的用户// 注:不要在同一行reserved声明中同时声明域名字和标识号reserved 15, 20 to 25;reserved "foo", "bar"string name = 1;// 使用变长编码, 对于负值效率很低, 请使用sint64代替int32 num1 = 2;// 使用变长编码, 无符号uint32 num2 = 3;// 使用变长编码, 无符号uint64 num3 = 4;// 使用变长编码,这些编码在负值时比int32高效的多sint32 num4 = 5;// 使用变长编码,有符号的整型值。编码时比通常的int64高效。sint64 num5 = 6;// 总是4个字节,如果数值总是比2^28大的话,这个类型会比uint32高效。fixed32 num6 = 7;// 总是8个字节,如果数值总是比总是比2^56大的话,这个类型会比uint64高效。fixed64 num7 = 8;// 总是4个字节sfixed32 num8 = 9;// 总是8个字节sfixed64 num9 = 10;bool boolean = 11;bytes b = 12;double d = 13;float f = 14;}message MyMessage2 {// 枚举enum Corpus {// 第一个常量必需映射为0, 以0值做为默认值// 这个0值必须为第一个元素, 为了兼容proto2语义UNIVERSAL = 0;WEB = 1;IMAGES = 2;LOCAL = 3;}Corpus corpus = 1;// 枚举常量必须在32位整型范围内, 因为enum值使用可变编码, 对负数不够高效, 因此不推荐使用负数enum EnumAllowingAlias {// 你可以将不同的枚举常量定位为相同的值, 如果要这样做, 需要将allow_alias设置为true, 否则编译错误option allow_alias = true;UNKNOWN = 0;STARTED = 1;RUNNING = 1;}}message MyMessage3 {// 此字段可以重复任意多次(包括0), 重复值的顺序会被保留repeated Hint list = 1;}message Hint {string name = 1;repeated string snippets = 2;}// 嵌套类型message MyMessage4 {message Result {string url = 1;string title = 2;}}message Mymessage5 {MyMessage4.Result result = 1;}
标准类型
| .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 | 使用变长编码,对于负值的效率很低,如果你的域有可能有负值,请使用sint64替代 | int32 | int | int | int32 | Fixnum 或者 Bignum(根据需要) | int | integer |
| uint32 | 使用变长编码 | uint32 | int | int/long | uint32 | Fixnum 或者 Bignum(根据需要) | uint | integer |
| uint64 | 使用变长编码 | uint64 | long | int/long | uint64 | Bignum | ulong | integer/string |
| sint32 | 使用变长编码,这些编码在负值时比int32高效的多 | int32 | int | int | int32 | Fixnum 或者 Bignum(根据需要) | int | integer |
| sint64 | 使用变长编码,有符号的整型值。编码时比通常的int64高效。 | int64 | long | int/long | int64 | Bignum | long | integer/string |
| fixed32 | 总是4个字节,如果数值总是比总是比228大的话,这个类型会比uint32高效。 | uint32 | int | int | uint32 | Fixnum 或者 Bignum(根据需要) | uint | integer |
| fixed64 | 总是8个字节,如果数值总是比总是比256大的话,这个类型会比uint64高效。 | uint64 | long | int/long | uint64 | Bignum | ulong | integer/string |
| sfixed32 | 总是4个字节 | int32 | int | int | int32 | Fixnum 或者 Bignum(根据需要) | int | integer |
| sfixed64 | 总是8个字节 | int64 | long | int/long | int64 | Bignum | long | integer/string |
| bool | bool | boolean | bool | bool | TrueClass/FalseClass | bool | boolean | |
| string | 一个字符串必须是UTF-8编码或者7-bit ASCII编码的文本。 | string | String | str/unicode | string | String (UTF-8) | string | string |
| bytes | 可能包含任意顺序的字节数据。 | string | ByteString | str | []byte | String (ASCII-8BIT) | ByteString | string |
|
默认值
当一个消息被解析的时候,如果被编码的信息不包含一个特定的singular元素,被解析的对象锁对应的域被设置位一个默认值,对于不同类型指定如下:
- 对于string,默认是一个空string
- 对于byte,默认是一个空的bytes
- 对于bool,默认是false
- 对于数值类型,默认是0
- 对于枚举,默认是第一个定义的枚举值,必须为0;
- 对于消息类型(message),域没有被设置,确切的消息是根据语言确定的,详见generated code guide
对于可重复域的默认值是空(通常情况下是对应语言中空列表)。
注:对于标量消息域,一旦消息被解析,就无法判断域释放被设置为默认值(例如,例如boolean值是否被设置为false)还是根本没有被设置。你应该在定义你的消息类型时非常注意。例如,比如你不应该定义boolean的默认值false作为任何行为的触发方式。也应该注意如果一个标量消息域被设置为标志位,这个值不应该被序列化传输。Any
Any类型消息允许你在没有指定他们的.proto定义的情况下使用消息作为一个嵌套类型。一个Any类型包括一个可以被序列化bytes类型的任意消息,以及一个URL作为一个全局标识符和解析消息类型。为了使用Any类型,你需要导入import google/protobuf/any.proto```protobuf
import “google/protobuf/any.proto”;
message ErrorStatus { string message = 1; repeated google.protobuf.Any details = 2; }
<a name="gc6rT"></a>
## Oneof
如果你的消息中有很多可选字段, 并且同时至多一个字段会被设置, 你可以加强这个行为,使用oneof特性节省内存.<br />Oneof字段就像可选字段, 除了它们会共享内存, 至多一个字段会被设置。 设置其中一个字段会清除其它字段。 你可以使用`case()`或者`WhichOneof()` 方法检查哪个oneof字段被设置, 看你使用什么语言了.<br />你可以增加任意类型的字段, 但是不能使用repeated 关键字. 在产生的代码中, oneof字段拥有同样的 getters 和setters, 就像正常的可选字段一样. 也有一个特殊的方法来检查到底那个字段被设置. 你可以在相应的语言
```protobuf
message SampleMessage {
oneof test_oneof {
string name = 4;
SubMessage sub_message = 9;
}
}
- 设置oneof会自动清楚其它oneof字段的值. 所以设置多次后,只有最后一次设置的字段有值.
- 如果解析器遇到同一个oneof中有多个成员,只有最会一个会被解析成消息
- oneof不支持repeated
- 反射API对oneof 字段有效.
Maps
map<key_type, value_type> map_field = N;
其中key_type可以是任意Integer或者string类型(所以,除了浮点数和bytes的任意标量类型都是可以的)value_type可以是任意类型。
- Map的字段可以是repeated。
- 序列化后的顺序和map迭代器的顺序是不确定的,所以你不要期望以固定顺序处理Map
- 当为.proto文件产生生成文本格式的时候,map会按照key 的顺序排序,数值化的key会按照数值排序。
- 从序列化中解析或者融合时,如果有重复的key则后一个key不会被使用,当从文本格式中解析map时,如果存在重复的key。
更新一个消息类型
如果一个已有的消息格式已无法满足新的需求——如,要在消息中添加一个额外的字段——但是同时旧版本写的代码仍然可用。不用担心!更新消息而不破坏已有代码是非常简单的。在更新时只要记住以下的规则即可。
- 不要更改任何已有的字段的数值标识。
- 如果你增加新的字段,使用旧格式的字段仍然可以被你新产生的代码所解析。你应该记住这些元素的默认值这样你的新代码就可以以适当的方式和旧代码产生的数据交互。相似的,通过新代码产生的消息也可以被旧代码解析:只不过新的字段会被忽视掉。注意,未被识别的字段会在反序列化的过程中丢弃掉,所以如果消息再被传递给新的代码,新的字段依然是不可用的(这和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类型的字段总会保留他们的
定义服务(Service)
如果想要将消息类型用在RPC(远程方法调用)系统中,可以在.proto文件中定义一个RPC服务接口,protocol buffer编译器将会根据所选择的不同语言生成服务接口代码及存根。如,想要定义一个RPC服务并具有一个方法,该方法能够接收 SearchRequest并返回一个SearchResponse,此时可以在.proto文件中进行如下定义:
service SearchService {
rpc Search (SearchRequest) returns (SearchResponse);
}
JSON 映射
Proto3 支持JSON的编码规范,使他更容易在不同系统之间共享数据,在下表中逐个描述类型。
如果JSON编码的数据丢失或者其本身就是null,这个数据会在解析成protocol buffer的时候被表示成默认值。如果一个字段在protocol buffer中表示为默认值,体会在转化成JSON的时候编码的时候忽略掉以节省空间。具体实现可以提供在JSON编码中可选的默认值。
| proto3 | JSON | JSON示例 | 注意 |
|---|---|---|---|
| message | object | {“fBar”: v, “g”: null, …} | 产生JSON对象,消息字段名可以被映射成lowerCamelCase形式,并且成为JSON对象键,null被接受并成为对应字段的默认值 |
| enum | string | “FOO_BAR” | 枚举值的名字在proto文件中被指定 |
| map | object | {“k”: v, …} | 所有的键都被转换成string |
| repeated V | array | [v, …] | null被视为空列表 |
| bool | true, false | true, false | |
| string | string | “Hello World!” | |
| bytes | base64 string | “YWJjMTIzIT8kKiYoKSctPUB+” | |
| int32, fixed32, uint32 | number | 1, -10, 0 | JSON值会是一个十进制数,数值型或者string类型都会接受 |
| int64, fixed64, uint64 | string | “1”, “-10” | JSON值会是一个十进制数,数值型或者string类型都会接受 |
| float, double | number | 1.1, -10.0, 0, “NaN”, “Infinity” | JSON值会是一个数字或者一个指定的字符串如”NaN”,”infinity”或者”-Infinity”,数值型或者字符串都是可接受的,指数符号也可以接受 |
| Any | object | {“@type”: “url”, “f”: v, … } | 如果一个Any保留一个特上述的JSON映射,则它会转换成一个如下形式:{"@type": xxx, "value": yyy}否则,该值会被转换成一个JSON对象, @type字段会被插入所指定的确定的值 |
| Timestamp | string | “1972-01-01T10:00:20.021Z” | 使用RFC 339,其中生成的输出将始终是Z-归一化啊的,并且使用0,3,6或者9位小数 |
| Duration | string | “1.000340012s”, “1s” | 生成的输出总是0,3,6或者9位小数,具体依赖于所需要的精度,接受所有可以转换为纳秒级的精度 |
| Struct | object | { … } | 任意的JSON对象,见struct.proto |
| Wrapper types | various types | 2, “2”, “foo”, true, “true”, null, 0, … | 包装器在JSON中的表示方式类似于基本类型,但是允许nulll,并且在转换的过程中保留null |
| FieldMask | string | “f.fooBar,h” | 见fieldmask.proto |
| ListValue | array | [foo, bar, …] | |
| Value | value | 任意JSON值 | |
| NullValue | null | JSON null |
选项(option)
在定义.proto文件时能够标注一系列的options。Options并不改变整个文件声明的含义,但却能够影响特定环境下处理方式。完整的可用选项可以在google/protobuf/descriptor.proto找到。
一些选项是文件级别的,意味着它可以作用于最外范围,不包含在任何消息内部、enum或服务定义中。一些选项是消息级别的,意味着它可以用在消息定义的内部。当然有些选项可以作用在域、enum类型、enum值、服务类型及服务方法中。到目前为止,并没有一种有效的选项能作用于所有的类型。
如下就是一些常用的选择:
java_package(文件选项) :这个选项表明生成java类所在的包。如果在.proto文件中没有明确的声明java_package,就采用默认的包名。当然了,默认方式产生的 java包名并不是最好的方式,按照应用名称倒序方式进行排序的。如果不需要产生java代码,则该选项将不起任何作用。如:option java_package = "com.example.foo";java_outer_classname(文件选项): 该选项表明想要生成Java类的名称。如果在.proto文件中没有明确的java_outer_classname定义,生成的class名称将会根据.proto文件的名称采用驼峰式的命名方式进行生成。如(foo_bar.proto生成的java类名为FooBar.java),如果不生成java代码,则该选项不起任何作用。如:option java_outer_classname = "HelloWorld";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文件产生的类和枚举类型。没有默认值,所使用的前缀应该是苹果推荐的3-5个大写字符,注意2个字节的前缀是苹果所保留的。deprecated(字段选项):如果设置为true则表示该字段已经被废弃,并且不应该在新的代码中使用。在大多数语言中没有实际的意义。在java中,这回变成@Deprecated注释,在未来,其他语言的代码生成器也许会在字标识符中产生废弃注释,废弃注释会在编译器尝试使用该字段时发出警告。如果字段没有被使用你也不希望有新用户使用它,尝试使用保留语句替换字段声明。int32 old_field = 6 [deprecated=true];
代码生成
要生成java、Kotlin、python、C++、Go、Ruby、Objective-C或C代码,需要使用.proto文件中定义的消息类型, 如果尚未安装编译器,请下载该软件包并按照自述文件中的说明进行操作。对于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文件的目录。如果省略,则使用当前目录。通过多次传递—proto_path选项,可以指定多个导入目录;他们将按顺序被搜查,-I=IMPORT_PATH可以用作--proto_path的一种简短形式。您可以提供一个或多个输出指令:
--cpp_outgenerates C++ code inDST_DIR. See the C++ generated code reference for more.--java_outgenerates Java code inDST_DIR. See the Java generated code reference for more.--kotlin_outgenerates additional Kotlin code inDST_DIR. See the Kotlin generated code reference for more.--python_outgenerates Python code inDST_DIR. See the Python generated code reference for more.--go_outgenerates Go code inDST_DIR. See the Go generated code reference for more.--ruby_outgenerates Ruby code inDST_DIR. Ruby generated code reference is coming soon!--objc_outgenerates Objective-C code inDST_DIR. See the Objective-C generated code reference for more.--csharp_outgenerates C# code inDST_DIR. See the C# generated code reference for more.--php_outgenerates PHP code inDST_DIR. See the PHP generated code reference for more.As an extra convenience, if theDST_DIRends in.zipor.jar, the compiler will write the output to a single ZIP-format archive file with the given name..jaroutputs will also be given a manifest file as required by the Java JAR specification. Note that if the output archive already exists, it will be overwritten; the compiler is not smart enough to add files to an existing archive.
您必须提供一个或多个.proto文件作为输入。可以一次指定多个.proto文件。尽管文件是相对于当前目录命名的,但每个文件必须位于其中一个导入路径中,以便编译器可以确定其规范名称。
