1. 基本规范
- 文件以
.proto
作为文件后缀,除结构定义之外的语句以分号结尾 - 结构定义可以包含:message、service、enum
- message 定义结构体,service 定义方法
- rpc方法定义结尾的分号可有可无
- Message 命名采用驼峰命名方式,字段命名采用小写字母加下划线分隔方式
- Enums 类型名采用驼峰命名方式,字段命名采用大写字母加下划线分隔方式
- Service与rpc方法名统一采用驼峰式命名
enum GenderType {
SECRET = 0;
FEMALE = 1;
MALE = 2;
}
// 人
message Person {
int64 id = 1;
string name = 2;
GenderType gender = 3;
string number = 4;
}
复制代码
2. 字段规则
字段格式
字段格式:限定修饰符 | 数据类型 | 字段名称 | = | 字段编码值 | [字段默认值]
限定修饰符
限定修饰符包含:required、optional、repeated
- Required:表示是一个必须字段
- Optional:表示一个可选字段。对于接收方,如果能够识别可选字段就进行相应的处理,如果无法识别,则忽略该字段
- Repeated:表示该字段可以包含0-N个元素。其中特性和optional一样,但是每一次可以包含多个值。可以看做是在传递一个数组的值
数据类型
Protobuf定义了一套基本数据类型:
Protobuf 数据类型 | 描述 | 打包 |
---|---|---|
bool | 布尔类型 | 1字节 |
double | 64位浮点数 | N |
float | 32位浮点数 | N |
int32 | 32位整数 | N |
uint32 | 无符号32位整数 | N |
int64 | 64位整数 | N |
uint64 | 64位无符号整数 | N |
$int32 | 32位整数,处理负数效率更高 | N |
$int64 | 64位整数,处理负数效率更高 | N |
fixed32 | 32位无符号整数 | 4 |
fixed64 | 64位无符号整数 | 8 |
$fixed32 | 32位整数,能以更高的效率处理负数 | 4 |
$fixed64 | 64位整数,能以更高的效率处理负数 | 8 |
string | 只能处理ASCII字符 | N |
bytes | 用于处理多字节的语言字符,如中文 | N |
enum | 可以包含一个用户自定义的枚举类型uint32 | N(uiint32) |
message | 可以包含一个用户自定义的消息类型 | N |
- N 表示打包的字节并不是固定,而是根据数据的大小或者长度
- 关于fiex32 和 int32的区别:fixed32的打包效率比int32的效率高,但是使用的空间一般比int32多
字段名称
- 字段名称的命名与C、Java等语言的变量命名方式几乎是相同的
- protobuf 建议字段的命名采用以下划线分隔的驼峰式
字段编码值
- 有了该值,通信双方才能互相识别对方的字段,相同的编码值,其限定修饰符和数据类型必须相同,编码值的取值范围为:1 ~ 2^32 (4294967296)
- 其中 1 ~ 15的编码时间和空间效率都是最高的,编码值越大,其编码的时间和空间效率就越低
- 1900 ~ 2000 编码值为 Google protobuf 系统内部保留值,建议不要在项目中使用
字段默认值
- 当在传递数据时,对于required数据类型,如果用户没有设置值,则使用默认值传递到对端
3. service 如何定义
- 如果想要将消息类型用在 RPC 系统中,可以在
.proto
文件中定义一个 RPC 服务接口,protocol buffer 编译器会根据所选择的不同语言生成服务接口代码 - 生成的接口代码作为客户端与服务端的约定,服务端必须实现定义的所有接口方法,客户端直接调用同名方法向服务端发起请求(即便业务上不需要参数也必须指定一个请求消息,一般会定义一个空message)
比如,想要定义一个 RPC 服务并具有一个方法,该方法接收 SearchRequest 并返回一个 SearchResponse,此时可以在.proto
文件中进行如下定义:
service SearchService {
rpc Search(SearchRequest) returns (SearchResponse) {}
}
复制代码
4. Message 如何定义
- 一个 message 类型定义描述了一个请求或响应的消息格式,可以包含多种类型字段
- 字段名用小写,转为 go 文件后自动变为大写,message 就相当于结构体
5. 添加更多 Message 类型
一个 .proto 文件中可以定义多个消息类型,一般用于同时定义多个相关的消息,例如在同一个 .proto 文件中同时定义搜索请求和响应消息:
syntax = "proto3" // 声明使用的 protobuf 版本
message SearchRequest {
string query = 1; // 查询字符串
int32 page_number = 2;
int32 result_per_page = 3;
}
message SearchResponse {
}
复制代码
6. 如何使用其他 Message
message 支持嵌套使用,作为另一个 message 中的字段类型
message SearchResponse {
repeated Result results = 1;
}
message Result {
string url = 1;
string title = 2;
repeated string snippets = 3;
}
复制代码
7. Message 嵌套的使用
支持嵌套消息,消息可以包含另一个消息作为字段。也可以在消息内定义一个新的消息。
内部声明的 message 类型名称只可在内部直接使用:
message SearchResponse {
message Result {
string url = 1;
string title = 2;
repeated string snippets = 3;
}
repeated Result results = 1;
}
复制代码
另外,还可以多层嵌套:
message Outer {
message A {
message Inner {
int64 ival = 1;
bool booly = 2;
}
}
message B {
message Inner {
int64 ival = 1;
bool booly = 2;
}
}
}
复制代码
8. proto3 的 Map 类型
-
proto3 支持 map 类型声明
-
键、值类型可以是内置类型,也可以是自定义 message 类型
-
字段不支持 repeated 属性
map<key_type, value_type>map_field = N; message Project {...} map<string, Project>projects = 1; 复制代码
9. .proto 文件编译
- 通过定义好的 .proto 文件生成 Java、Python、Go、Ruby等代码,需要安装编译器 protoc
- 使用 protobuf 编译器不同的语言生成的代码格式不同:
- Go:生成一个 .pb.go 文件,每个消息类型对应一个结构体
- Java:生成一个 java 文件,每个消息对应一个类,同时还有一个特殊的 Builder 类用于创建消息接口
- ......
10. Import 导入定义
-
可以使用 import 语句导入使用其它描述文件中声明的类型
-
protobuf 接口文件可以通过 import 导入需要的文件,例如:
import "example.proto"
-
protobuf 编译器会在 -I/ --proto_path 参数指定的目录中查找导入的文件,如果没有指定该参数,默认在当前目录中查找
11. 包的使用
在 proto 文件中使用 package 声明包名,避免命名冲突:
syntax = "proto3"
package foo.bar
message Open {...}
复制代码
在其他的消息格式定义中可以使用包名 + 消息名的方式来使用类型,如:
message Foo {
...
foo.bar.Open open = 1;
...
}
复制代码
在不同的语言中,包名定义对编译后生成的代码影响不同:
- Go:默认使用 package 名作为包名,除非指定了 option go_package 选项
- Java:默认使用 package 名作为包名,除非指定了 option go_package 选项
- Python:package 被忽略
近期评论