阿川CH
学海无涯,上栽上栽!
Toggle navigation
阿川CH
主页
归档
标签
Protocol Buffers 2 简要用法
SerDe
Protocol Buffers
2018-03-09 17:05:08
0
0
0
cqc
SerDe
Protocol Buffers
# Protocol Buffers 2 简要用法 (本说明仅负责入门,满足能看的懂源码的需求,官方提到的一些重要使用规范不在此文档说明,详情需查阅[官方文档](https://developers.google.com/protocol-buffers/docs/proto)) ## 定义一个消息类型 ``` message SearchRequest { required string query = 1; optional int32 page_number = 2; optional int32 result_per_page = 3; } ``` 文件以`.proto`作为文件后缀名 ### TAGS 标签 上例中的 `1` `2` `3`即是标签,每个字段需要定义一个唯一的标签值。 这些标签值主要是用于在序列化或反序列时,识别哪些位置属于哪个字段,所以一旦消息结构已经开始使用,将不能再改变标签值,除非你认为旧结构的数据已经不存在了。 ### 申明保留的字段或标签值 当你需要对一个消息的字段进行删除后,后面的开发同学也许就不记得曾经有过这个字段或标签值,导致其重新添加了。 所以在删除后,需要使用`reserved`来声明哪些字段名或标签值曾经已经用过 ``` message Foo { reserved 2, 15, 9 to 11; reserved "foo", "bar"; } ``` ### 字段规则 每个字段前面需要一个字段类型 * `required` 必须存在仅且一个的该字段 * `optional` 存在0到1个的该字段 * `repeated` 存在0到N个的该字段 *如果`repeated`用于修饰数字类型,增加`[packed=true]`来获得更高的性能* ``` repeated int32 samples = 4 [packed=true]; ``` ### 在同一个proto文件中可定义多个的消息 ``` message SearchRequest { required string query = 1; optional int32 page_number = 2; optional int32 result_per_page = 3; } message SearchResponse { ... } ``` ### 注释 使用单行注释`//`和多行注释`/* ... */` ``` /* SearchRequest represents a search query, with pagination options to * indicate which results to include in the response. */ message SearchRequest { required string query = 1; optional int32 page_number = 2; // Which page number do we want? optional int32 result_per_page = 3; // Number of results to return per page. } ``` ## 数据原生类型 | .proto Type | 说明 | C++ Type | Java Type | Python Type[2] | Go Type | | - | - | - | - | - | - | | double | | double | double | float | *float64 | | float | | float | float | float | *float32 | | int32 | 不固定长度。编码负数的性能较差,如果存在负数,建议使用sint32 | int32 | int | int | *int32 | | int64 | 不固定长度。编码负数的性能较差,如果存在负数,建议使用sint64 | int64 | long | int/long[3] | *int64 | | uint32 | Uses variable-length encoding. | uint32 | int | int/long | *uint32 | | uint64 | Uses variable-length encoding. | uint64 | long | int/long | *uint64 | | sint32 | Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int32s. | int32 | int | int | *int32 | | sint64 | Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int64s. | int64 | long | int/long[3] | *int64 | | fixed32 | Always four bytes. More efficient than uint32 if values are often greater than 228. | uint32 | int[1] | int | *uint32 | | fixed64 | Always eight bytes. More efficient than uint64 if values are often greater than 256. | uint64 | long[1] | int/long[3] | *uint64 | | sfixed32 | Always four bytes. | int32 | int | int | *int32 | | sfixed64 | Always eight bytes. | int64 | long | int/long[3] | *int64 | | bool | | bool | boolean | bool | *bool | | string | A string must always contain UTF-8 encoded or 7-bit ASCII text. | string | String | str/unicode[4] | *string | | bytes | May contain any arbitrary sequence of bytes. | string | ByteString | str | []byte | ## 可选字段的默认值 ``` optional int32 result_per_page = 3 [default = 10]; ``` 默认值非必需的,但如果未定义,则使用类型相关的值 * string类型的为空字符串 * 数值类型的为0 * 布尔类型的为false * 枚举类型的为其定义的枚举的第一个 ## 枚举 ``` message SearchRequest { required string query = 1; optional int32 page_number = 2; optional int32 result_per_page = 3 [default = 10]; enum Corpus { UNIVERSAL = 0; WEB = 1; IMAGES = 2; LOCAL = 3; NEWS = 4; PRODUCTS = 5; VIDEO = 6; } optional Corpus corpus = 4 [default = UNIVERSAL]; } ``` 注意,上面枚举中的 =0 是标签值哈 如果需要让枚举支持别名,则需要在枚举中增加`option 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. } ``` ### 保留字段在枚举中的定义 ``` enum Foo { reserved 2, 15, 9 to 11, 40 to max; reserved "FOO", "BAR"; } ``` ## 使用Message类型 ``` message SearchResponse { repeated Result result = 1; } message Result { required string url = 1; optional string title = 2; repeated string snippets = 3; } ``` ** 引入其他proto的定义 ** ``` import "myproject/other_protos.proto"; ``` 正常可以直接引入某个位置上的proto文件,不过当这个文件要被移到其他的位置,而又有很多proto文件需要依赖该文件时,可通过在移动前的位置定义一个同名代理文件,代理文件中重新指向新位置 需要使用`import public`的方式来声明可以传递给后面引入此文件的proto使用 ``` // 假设这是迁移后的文件test.proto ``` ``` // 在迁移前的位置也再定义一个文件 test.proto // 引入新位置的proto import public "xxx/test.proto"; ``` ``` // 这是其他原来需要依赖test.proto的proto文件 import "test.proto"; ... ``` ## 内嵌类型 ``` message SearchResponse { message Result { required string url = 1; optional string title = 2; repeated string snippets = 3; } repeated Result result = 1; } ``` 使用内嵌 ``` message SomeOtherMessage { optional SearchResponse.Result result = 1; } ``` 只要有需要,内嵌的深度不限 ``` message Outer { // Level 0 message MiddleAA { // Level 1 message Inner { // Level 2 required int64 ival = 1; optional bool booly = 2; } } message MiddleBB { // Level 1 message Inner { // Level 2 required int32 ival = 1; optional bool booly = 2; } } } ``` ## 变更消息结构 当原有的消息结构不满足新的需求或太臃肿时,此时你可能想变更你的消息结构,为保证新旧版本的兼容,需要满足以下的条件: * 不可以改变已存在字段的标签值 * 任何新增的字段只能是`optional`或`repeated`,不能使用`required`是因为使用新的消息结构去解析旧的数据时,会导致异常 * Optional字段允许删除,只要其标签值在后续不出现即可 * A non-required field can be converted to an extension and vice versa, as long as the type and number stay the same. * `int32` `uint32` `int64` `uint64`和 `bool`是可以相互转换的。如果从64位转为32位的数字,就好比long类型强转为int类型的效果一样 * `sint32`和`sint64`可以相互转换,不能和其他的数值类型转换 * `string`和`bytes`可以相互转换,只要bytes是以UTF8编码的 * `fixed32`和`sfixed32`相兼容,`fixed64`和`sfixed64`相兼容 * `optional`和`repeated`是可以相兼容,当repeated类型转成optional时,将把repeated的最后一个值赋值给optional * 改变一个默认值是完全没问题的。需要注意的是,发送方是不会把默认值发到接收方,默认值是由接收方的默认值来赋值的 ## 扩展消息结构 ``` message Foo { // ... extensions 100 to 199; //定义需要扩展的子结构中只能使用标签值为100到199 } ``` ``` extend Foo { optional int32 bar = 126; } ``` ## Oneof 当你的消息结构中有多个的optional字段,而在每一次都仅有一个字段会有值时,则可以使用`Oneof`来声明,以此来节约资源的使用 ``` message SampleMessage { oneof test_oneof { string name = 4; int32 age = 9; } } ``` 你可以添加optional或repeated字段到oneof的定义中。oneof上的字段同样也都是有setter和getter的方法,当设置多个的oneof中的字段的值且在序列化时,只会把最后一个设置的oneof字段值保留下来 oneof 字段不可以是 repeated ## 定义Map类型的字段 ``` map<key_type, value_type> map_field = N; ``` key_type只能是数值类型或string类型, value_type则允许是除Map类型之外的其他类型 ``` map<string, Project> projects = 3; ``` 等同于 ``` message MapFieldEntry { optional key_type key = 1; optional value_type value = 2; } repeated MapFieldEntry map_field = N; ``` ## 定义包名 正常我们引用消息类型时是不需要加包名的,而当在多个的proto文件中存在同名的消息类型时,则可以通过包名来防止冲突 ``` package foo.bar; message Open { ... } ``` ``` message Foo { ... required foo.bar.Open open = 1; ... } ``` 当未定义`option java_package`时,则以package的定义作为包名,否则以`option java_package`为主 ## 定义RPC服务 ``` service SearchService { rpc Search (SearchRequest) returns (SearchResponse); } ``` 需要加上`option java_generic_services = true`, 在编译后会生成一个`SearchService`抽象类,具体用法**后面补充** ## Options ### file级别 * `java_package` 定义包名 * `java_outer_classname ` 输出的类名,若未定义,则使用proto文件的文件名作为类名 * `optimize_for` 其可被设置为`SPEED`(默认),`CODE_SIZE`,`LITE_RUNTIME` ``` option optimize_for = CODE_SIZE ``` * `cc_generic_services` `java_generic_services` `py_generic_services`用于表示在对应语言中是否需要生成RPC服务类 ``` option java_generic_services = true; ``` ### field级别 * `packed`被用于repeated 数值型字段,有更好的压缩编码效果。 ``` repeated int32 samples = 4 [packed=true]; ``` * `deprecated `声明字段已过期,在java编译后后会生成`@Deprecated` ``` optional int32 old_field = 6 [deprecated=true]; ``` ### 用户自定义的option 这里不说明,详情查[官网](https://developers.google.com/protocol-buffers/docs/proto#customoptions) ## 编译 ``` protoc --proto_path=IMPORT_PATH --cpp_out=DST_DIR --java_out=DST_DIR --python_out=DST_DIR path/to/file.proto ``` * `IMPORT_PATH`是proto文件的所在文件夹,通常用于`import`引入其他的proto文件时使用,可多次定义来指定多个的位置,可用 `-I=IMPORT_PATH` 来简写 * 依据使用语言的不同,可选择`cpp_out` `java_out` `python_out` 来指定生成的位置 ## 使用Maven自动编译 * 在`src/main/proto`下定义proto文件 * 在pom.xml中配置 ```xml <properties> <protobuf.version>3.5.0</protobuf.version> </properties> <dependencies> <dependency> <groupId>com.google.protobuf</groupId> <artifactId>protobuf-java</artifactId> <version>${protobuf.version}</version> </dependency> </dependencies> <build> <extensions> <extension> <groupId>kr.motd.maven</groupId> <artifactId>os-maven-plugin</artifactId> <version>1.5.0.Final</version> </extension> </extensions> <plugins> <plugin> <groupId>org.xolstice.maven.plugins</groupId> <artifactId>protobuf-maven-plugin</artifactId> <version>0.5.1</version> <extensions>true</extensions> <configuration> <protocArtifact>com.google.protobuf:protoc:${protobuf.version}:exe:${os.detected.classifier}</protocArtifact> </configuration> <executions> <execution> <goals> <goal>compile</goal> <goal>test-compile</goal> </goals> </execution> </executions> </plugin> </plugins> </build> ``` * 通过`mvn compile`,就可以在target/generated-sources 下看到生成的源码了
上一篇:
java版本版本冲突的识别
下一篇:
基于流的方式在传输或存取时进行加减密
文档导航