1、基本规范
文件以
.proto
做为文件后缀,除结构定义外的语句以分号结尾结构定义可以包含:
message
、service
、enum
。rpc方法定义结尾的分号可有可无。
Message
命名采用驼峰命名方式,字段 命名 采用 小写字母 加 下划线 分隔方式。message SongServerRequest { required string song_name = 1; }
Enum
类型名采用 驼峰命名 方式,字段 命名采用 大写字母 加 下划线 分隔方式enum Foo { FIRST_VALUE = 1; SECOND_VALUE = 2; }
Service
与rpc方法
名统一采用 驼峰式 命名。service SearchService { rpc Search (SearchRequest) returns (SearchResponse) {} }
2、字段规则
字段格式:
限定修饰符 | 数据类型 | 字段名称 | = | 字段编码值 | [字段默认值]
2.1 限定修饰符
包含 required \ optional \ repeated
Required
: 表示是一个必须字段,必须相对于发送方,在发送消息之前必须设置该字段的值,对于接收方,必须能够识别该字段的意思。发送之前没有设置
required
字段或者无法识别required
字段都会引发编解码异常,导致消息被丢弃。Optional
:表示是一个可选字段,可选对于发送方,在发送消息时,可以有选择性的设置或者不设置该字段的值。对于接收方,如果能够识别可选字段就进行相应的处理,如果无法识别,则忽略该字段,消息中的其它字段正常处理。因为
optional
字段的特性,很多接口在升级版本中都把后来添加的字段都统一的设置为optional
字段,这样老的版本无需升级程序也可以正常的与新的软件进行通信,只不过新的字段无法识别而已,因为并不是每个节点都需要新的功能,因此可以做到按需升级和平滑过渡。Repeated
:表示该字段可以包含0~N个元素。其特性和optional
一样,但是每一次可以包含多个值。可以看作是在传递一个数组的值。
2.2 数据类型
- Protobuf定义了一套基本数据类型。几乎都可以映射到C++\Java等语言的基础数据类型
N 表示打包的字节并不是固定。而是根据数据的大小或者长度
关于 fixed32 和int32的区别。fixed32的打包效率比int32的效率高,但是使用的空间一般比int32多。因此一个属于时间效率高,一个属于空间效率高。
2.3 字段名称
字段名称的命名与C、C++、Java等语言的变量命名方式几乎是相同的
protobuf 建议字段的命名采用以下划线分割的驼峰式。例如 first_name 而不是firstName
2.4 字段编码值
有了该值,通信双方才能互相识别对方的字段,相同的编码值,其限定修饰符和数据类型必须相同,编码值的取值范围为 1~2^32(4294967296)
其中 1~15的编码时间和空间效率都是最高的,编码值越大,其编码的时间和空间效率就越低,所以建议经常要传递的值其字段编码设置为1-15之间的值。
1900~2000编码值为Google protobuf 系统内部保留值,建议不要在自己的项目中使用。
2.5 字段默认值
- 当在传递数据时,对于required数据类型,如果用户没有设置值,则使用默认值传递到对端
3、各类型具体定义示例
3.1 Message如何定义
一个message类型定义描述了 一个请求 或 响应 的消息格式,可以包含多种类型字段
例如定义一个搜索请求的消息格式,每个请求包含查询 字符串、页码、每页数目
syntax = "proto3";
message SearchRequest {
string key = 1;
int64 page = 2;
int64 page_size = 3;
}
字段名用小写,转为go文件后自动变为大写,message就相当于结构体。
首行声明使用的protobuf版本为proto3。
SearchRequest
定义了三个字段,每个字段声明以分号结尾,.proto
文件支持双斜线//
添加单行注释。一个
.proto
文件中可以定义多个消息类型,一般用于同时定义多个相关的消息。
例如在同一个.proto文件中同时定义搜索请求和响应消息。
syntax = "proto3";
message SearchRequest {
string key = 1;
int64 page = 2;
int64 page_size = 3;
}
message SearchResponse {
......
}
- message支持嵌套使用,作为另一message中的字段类型。
syntax = "proto3";
message Result {
string url = 1;
string title = 2;
repeated string snippets = 3; // 表示字符串数组
}
message SearchResponse {
repeated Result result = 1; // 表示Result数组
}
嵌套消息,可以包含另一个消息作为其字段,也可以在消息内定义一个新的消息。
内部声明的message类型名称只可在内部直接使用message SearchResponse { message Result { // 转为go结构体时,名称为 SearchResponse_Result string url = 1; string title = 2; repeated string snippets = 3; // 表示字符串数组 } repeated Result result = 1; // 表示Result数组 }
支持多层嵌套。
这里只定义了,未在嵌套消息中引用。message Outer { // Level 0 message MiddleAA { // Level 1 // 转为go结构体时,名称为 Outer_MiddleAA message Inner { // Level 2 // 转为go结构体时,名称为 Outer_MiddleAA_Inner int64 ival = 1; bool booly = 2; } } message MiddleBB { // Level 1 // 转为go结构体时,名称为 Outer_MiddleBB message Inner { // Level 2 // 转为go结构体时,名称为 Outer_MiddleBB_Inner int32 ival = 1; bool booly = 2; } } }
3.2 enum如何定义
当希望字段 仅 具有预定义的值列表之一时,可以定义 enum 类型消息;
例如,假设想添加一个 corpus 字段每个 SearchRequest,其中语料库可以 UNIVERSAL,WEB,IMAGES,LOCAL,NEWS,PRODUCTS 或 VIDEO。
enum Corpus {
CORPUS_UNIVERSAL = 0;
CORPUS_WEB = 1;
CORPUS_IMAGES = 2;
CORPUS_LOCAL = 3;
CORPUS_NEWS = 4;
CORPUS_PRODUCTS = 5;
CORPUS_VIDEO = 6;
}
message SearchRequest {
string key = 1;
int64 page = 2;
int64 page_size = 3;
Corpus corpus = 4; // message 嵌套定义的 enum 类型 作为一个字段
}
3.3 service如何定义
如果想要将消息类型用在RPC系统中,可以在.proto
文件中定义一个RPC服务接口,protocol buffer
编译器会根据所选择的不同语言生成服务接口代码。
例如,想要定义一个RPC服务并具有一个方法,该方法接收SearchRequest
并返回一个SearchResponse
,此时可以在.proto
文件中进行如下定义:
service SearchService {
rpc Search (SearchRequest) returns (SearchResponse) {}
}
生成的接口代码作为 客户端 与 服务端 的约定,服务端 必须实现定义的所有接口方法,客户端 直接调用同名方法向服务端发起请求。
比较麻烦的是,即便业务上不需要参数也必须指定一个请求消息,一般会定义一个 空 message。
3.4 proto3的Map类型
proto3支持map类型声明
map<key_type, value_type> map_field = N;
message Project {...}
map<string, Project> projects = 1;
键、值类型可以是内置的类型,也可以是自定义message类型
字段不支持repeated属性。
其中 key_type 可以是 任何整数 或 字符串类型(因此,除了浮点类型和bytes之外的任何标量类型)。
请注意, enum 不是有效的key_type。
value_type可以是任何类型的,除了map类型。
3.4.1 向后兼容
map 语法等效于以下内容,因此不支持 map的protocol buffers实现仍然可以处理数据:
message MapFieldEntry {
key_type key = 1;
value_type value = 2;
}
repeated MapFieldEntry map_field = N;
任何支持映射的protocol buffers实现都必须生成和接受上述定义可以接受的数据。
4、import导入定义
可以使用
import
语句导入使用其它描述文件中声明的类型。protobuf
接口文件可以像C语言的h文件一个,分离为多个,在需要的时候通过import
导入需要对文件。其行为和C语言的#include
或者java的import
的行为大致相同,例如import "others.proto"
;protocol buffer编译器会在
-I / --proto_path
参数指定的目录中查找导入的文件,如果没有指定该参数,默认在当前目录中查找。
例如:上面的示例中,Result消息类型是在同一个文件中定义的SearchResponse——如果想用作字段类型的消息类型已经在另一个.proto文件中定义了怎么办?
.proto可以通过导入其他文件中的定义来使用它们。要导入 another.proto的定义,请在文件顶部添加一个 import 语句:
import "myproject/other_protos.proto";
公共导入功能
默认情况下,您只能使用直接导入的定义 .proto
文件。 但是,有时您可能需要移动一个 .proto文件到新位置。
而不是移动 .proto
直接归档并更新所有 引用的文件 单个更改,您可以放置一个占位符 .proto
文件在旧位置 使用 import public
概念。
公共导入功能在 Java 中不可用。
import public
依赖可以被任何代码传递依赖 导入包含的原型 import public
声明。
示例:
// 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
// 这里你可以 通过 old.proto 使用 new.proto 中声明的类型,但是不能 使用 other.proto 中的类型
5、包的使用
- 在.proto文件中使用package声明包名,避免命名冲突。
syntax = "proto3";
package foo.bar;
message Open {...}
- 在其他的消息格式定义中可以使用包名+消息名的方式来使用类型,如:
message Foo {
...
foo.bar.Open open = 1; // 使用包名+消息名 来使用定义的类型
...
}
在不同的语言中,包名定义对编译后生成的代码的影响不同。
- C++ 中:对应C++命名空间,例如Open会在命名空间
foo::bar
中 - Java 中:package会作为Java包名,除非指定了
option jave_package
选项 - Python 中:package被忽略
- Go 中:默认使用package名作为包名,除非指定了
option go_package
选项 - JavaNano 中:同Java
- C# 中:package会转换为驼峰式命名空间,如
Foo.Bar
,除非指定了option csharp_namespace
选项
- C++ 中:对应C++命名空间,例如Open会在命名空间
最后编辑:joker.liu 更新时间:2023-05-22 11:02