gRPC推荐使用proto3,这里只介绍常用语法,按照官方文档的结构翻译,英文水平有限,复杂的部分果断放弃,更多高级使用姿势请参考官方文档
建议初学者不要太刻意的记这里的语法,简单看一遍了解就好,使用过程有问题再回来查看。
一个message类型定义描述了一个请求或响应的消息格式,可以包含多种类型字段。
message
例如定义一个搜索请求的消息格式SearchRequest,每个请求包含查询字符串、页码、每页数目。每个字段声明以分号结尾。
SearchRequest
syntax = "proto3"; message SearchRequest { string query = 1; int32 page_number = 2; int32 result_per_page = 3; }
首行要求明确声明使用的protobuf版本为proto3,如果不声明,编译器默认使用proto2。本声明必须在文件的首行。
proto3
proto2
一个.proto文件中可以定义多个message,一般用于同时定义多个相关的message,例如在同一个.proto文件中同时定义搜索请求和响应消息:
.proto
syntax = "proto3"; message SearchRequest { string query = 1; int32 page_number = 2; int32 result_per_page = 3; } message SearchResponse { ... }
所有的字段需要前置声明数据类型,上面的示例指定了两个数值类型和一个字符串类型。除了基本的标量类型还有复合类型,如枚举、map、数组、甚至其它message类型等。后面会依次说明。
消息的定义中,每个字段都有一个唯一的数值标签。这些标签用于标识该字段在消息中的二进制格式,使用中的类型不应该随意改动。其中,[1-15]内的标识在编码时占用一个字节,包含标识和字段类型。[16-2047]之间的标识符占用2个字节。建议为频繁出现的消息元素分配[1-15]间的标签。如果考虑到以后可能或扩展频繁元素,可以预留一些。
最小的标识符可以从1开始,最大到229 - 1,或536,870,911。不可以使用[19000-19999]之间的标识符, Protobuf协议实现中预留了这些标识符。在.proto文件中使用这些预留标识号,编译时会报错。
repeated
required
optional
向.proto文件中添加注释,支持C/C++风格双斜线//单行注释。
//
syntax = "proto3"; // 协议版本声明 // SearchRequest 搜索请求消息 message SearchRequest { string query = 1; // 查询字符串 int32 page_number = 2; // 页码 int32 result_per_page = 3; // 每页条数 }
可以使用reserved关键字指定保留字段名和标签。
reserved
message Foo { reserved 2, 15, 9 to 11; reserved "foo", "bar"; }
注意,不能在一个reserved声明中混合字段名和标签。
当使用protocol buffer编译器运行.proto文件时,编译器将生成所选语言的代码,用于使用在.proto文件中定义的消息类型、服务接口约定等。不同语言生成的代码格式不同:
.h
.cc
.java
Builder
.pb.go
.rb
pbobjc.h
pbobjc.m
.cs
各种语言的更多的使用方法请参考官方API文档
关于这些类型在序列化时的编码规则请参考 Protocol Buffer Encoding.
[1] java
[2] all
[3] 64
[4] Python
针对不同语言的默认值的具体行为参考 generated code guide
当定义一个message时,想要一个字段只能是一个预定义好的值列表内的一个值,就需要用到enum类型了。
示例:这里定义一个名为Corpus的enum类型值,并且指定一个字段为Corpus类型
Corpus
message SearchRequest { string query = 1; int32 page_number = 2; int32 result_per_page = 3; // 定义enum类型 enum Corpus { UNIVERSAL = 0; WEB = 1; IMAGES = 2; LOCAL = 3; NEWS = 4; PRODUCTS = 5; VIDEO = 6; } Corpus corpus = 4; // 使用Corpus作为字段类型 }
注意:每个enum定义的第一个元素值必须是0
还可以通过给不同的enum元素赋相同的值来定义别名,要求设置allow_alias选项的值为true,否则会报错。
allow_alias
true
// 正确示例 enum EnumAllowingAlias { option allow_alias = true; // 开启allow_alias选项 UNKNOWN = 0; STARTED = 1; RUNNING = 1; // RUNNING和STARTED互为别名 } // 错误示例 enum EnumNotAllowingAlias { UNKNOWN = 0; STARTED = 1; RUNNING = 1; // 未开启allow_alias选项,编译会报错 }
enum类型值同样支持文件级定义和message内定义,引用方式与message嵌套一致
可以使用其它message类型作为字段类型。
例如,在SearchResponse中包含Result类型的消息,可以在相同的.proto文件中定义Result消息类型,然后在SearchResponse中指定字段类型为Result:
SearchResponse
Result
message SearchResponse { repeated Result results = 1; } message Result { string url = 1; string title = 2; repeated string snippets = 3; }
上面的例子中,Result类型和SearchResponse类型定义在同一个.proto文件中,我们还可以使用import语句导入使用其它描述文件中声明的类型:
import "others.proto";
默认情况,只能使用直接导入的.proto文件内的定义。但是有时候需要移动.proto文件到其它位置,为了避免更新所有相关文件,可以在原位置放置一个模型.proto文件,使用public关键字,转发所有对新文件内容的引用,例如:
public
// new.proto // 所有新的定义在这里
// old.proto // 客户端导入的原来的proto文件 import public "new.proto"; import "other.proto";
// client.proto import "old.proto"; // 这里可以使用old.proto和new.proto文件中的定义,但是不能使用other.proto文件中的定义。
protocol编译器会在编译命令中 -I / --proto_path参数指定的目录中查找导入的文件,如果没有指定该参数,默认在当前目录中查找。
-I / --proto_path
上面已经介绍过可以嵌套使用另一个message作为字段类型,其实还可以在一个message内部定义另一个message类型,作为子级message。
示例:Result类型在SearchResponse类型中定义并直接使用,作为results字段的类型
results
message SearchResponse { message Result { string url = 1; string title = 2; repeated string snippets = 3; } repeated Result results = 1; }
内部声明的message类型名称只可在内部直接使用,在外部引用需要前置父级message名称,如Parent.Type:
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; } } }
proto3支持map类型声明:
map<key_type, value_type> map_field = N; message Project {...} map<string, Project> projects = 1;
key_type
bytes
value_type
在.proto文件中使用package声明包名,避免命名冲突。
package
syntax = "proto3"; package foo.bar; message Open {...}
在其他的消息格式定义中可以使用包名+消息名的方式来使用类型,如:
message Foo { ... foo.bar.Open open = 1; ... }
在不同的语言中,包名定义对编译后生成的代码的影响不同:
Open
foo::bar
option jave_package
option go_package
Foo.Bar
option csharp_namespace
如果想要将消息类型用在RPC(远程方法调用)系统中,可以在.proto文件中定义一个RPC服务接口,protocol编译器会根据所选择的不同语言生成服务接口代码。例如,想要定义一个RPC服务并具有一个方法,该方法接收SearchRequest并返回一个SearchResponse,此时可以在.proto文件中进行如下定义:
service SearchService { rpc Search (SearchRequest) returns (SearchResponse) {} }
生成的接口代码作为客户端与服务端的约定,服务端必须实现定义的所有接口方法,客户端直接调用同名方法向服务端发起请求。
在定义.proto文件时可以标注一系列的options。Options并不改变整个文件声明的含义,但却可以影响特定环境下处理方式。完整的可用选项可以查看google/protobuf/descriptor.proto.
google/protobuf/descriptor.proto
一些选项是文件级别的,意味着它可以作用于顶层作用域,不包含在任何消息内部、enum或服务定义中。一些选项是消息级别的,可以用在消息定义的内部。
以下是一些常用的选项:
java_package
java_outer_classname
objc_class_prefix
描述文件以.proto做为文件后缀,除结构定义外的语句以分号结尾
Message命名采用驼峰命名方式,字段命名采用小写字母加下划线分隔方式
message SongServerRequest { required string song_name = 1; }
Enums类型名采用驼峰命名方式,字段命名采用大写字母加下划线分隔方式
enum Foo { FIRST_VALUE = 1; SECOND_VALUE = 2; }
Service名称与RPC方法名统一采用驼峰式命名
通过定义好的.proto文件生成Java, Python, C++, Go, Ruby, JavaNano, Objective-C, or C# 代码,需要安装编译器protoc。参考Github项目google/protobuf安装编译器.
protoc
运行命令:
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
这里只做参考就好,具体语言的编译实例请参考详细文档。
吐槽: 照着官方文档一步步操作不一定成功哦!
这些用法在实践中很少使用,这里不做介绍,有需要请参考官方文档。
Copyright© 2013-2020
All Rights Reserved 京ICP备2023019179号-8