1.了解protoBuf
Protobuf是google开发的一种跨语言和平台的序列化数据结构的方式,类似于XML但是更小更快而且更简单,只需要定义一次结构体,通过生成的源代码可以在不同的数据流和不同的语言平台上去读写数据结构,
了解ProtoBuf首先了解序列化和反序列化
序列化: 就是将一个对象转换为字节序的过程;
反序列化 就是将一个字节序转换为一个完整对象的过程;
Protobuf3通过proto文件定义交互的对象,可以定义数据的结构,然后使⽤特殊⽣成的源代码轻松的在各种数据流中使⽤各种语⾔进⾏编写和读取结构数据。
- 编写.proto⽂件,⽬的是为了定义结构对象(message)及属性内容。
- 使⽤protoc编译器编译.proto⽂件,⽣成⼀系列接⼝代码,存放在新⽣成头⽂件和源⽂件中。
- 依赖⽣成的接⼝,将编译⽣成的头⽂件包含进我们的代码中,实现对.proto⽂件中定义的字段进⾏设置和获取,和对message对象进⾏序列化和反序列化。
2.关键字
1、message:定义一个消息类型,即一组相关的字段。
2、field:定义消息类型中的一个字段,包括字段的名称、编号和类型。
3、enum:定义一个枚举类型,用于在一组有限的值中进行选择。
4、option:为ProtoBuf编译器提供指令,用于控制如何生成代码。
5、package:定义一组相关消息类型的命名空间。
6、import:引用其他ProtoBuf文件中定义的消息类型。
7、oneof:定义一组互斥的字段,只能设置其中的一个字段。
8、repeated:定义一个字段可以包含多个值。
9、map:定义一个映射类型,可以将一组键值对映射到另一组值。
10、service:定义一个RPC服务,包括一组RPC方法和其参数。
3.字段类型
ProtoBuf类型 | java语言类型 | 含义 |
---|---|---|
double | double | 64位浮点数 |
float | float | 32位浮点数 |
int32 | int | 32位有符号整数 |
int64 | long | 64位有符号整数 |
uint32 | int | 32位无符号整数 |
uint64 | long | 64位无符号整数 |
sint32 | int | 32位有符号整数,使用可变长度编码 |
sint64 | long | 64位有符号整数,使用可变长度编码 |
fixed32 | int | 32位无符号整数,使用固定长度编码 |
fixed64 | long | 64位无符号整数,使用固定长度编码 |
sfixed32 | int | 32位有符号整数,使用固定长度编码 |
sfixed64 | long | 64位有符号整数,使用固定长度编码 |
bool | boolean | 布尔类型 |
string | String | UTF-8编码的字符串 |
bytes | byte[] | 字节数组 |
enum | enum | 枚举类型 |
message | 自定义类 | 消息类型 |
repeated | java.until.List | 重复的字段,可以包含多个值 |
map | java.until.Map | 映射类型,将一组键值对映射到另一组值 |
Any | 任意类型的消息 | |
Duration | 持续时间 | |
Timestamp | 时间戳 | |
FieldMask | 字段掩码 | |
Struct | 结构化数据 | |
Value | 值 |
4.proto文件解析
// 指定使用proto3协议; 否则使用proto2
syntax = "proto3";
// 定义消息的命名空间
package pb;
// 导入Any类型
import "google/protobuf/any.proto";
// java_xx表示生成java代码需要的几个属性
// java_package: 指定生成java的包名
option java_package = "com.sy.common.pojo";
// java_outer_classname: 生成java的类型,注意不能与message中定义的名称重名
option java_outer_classname = "MyResp";
// 用message定义一个消息(message可以理解为定义一个结构体的意思), 名为result
message Result {
// 定义一个int类型的变量,变量名为code,
// 赋值为1表示的是这个变量的唯一编号,序列化的时候会用这个编号替代变量名
// 注意:编号1~15,编码时占1字节。16~2047编码时占两个字节。编号19000~19999为保留编号,不能用。
int32 code = 1;
// 定义一个string类型的变量,名为msg, 唯一编号为2
string msg = 2;
// 定义一个Any类型(Any表示泛型,也可以理解为java的Object类型)的变量,名为data,唯一编号为3
// 定义成Any类型的好处时,赋值的时候可以给data赋任意类型的值
google.protobuf.Any data = 3;
}
// 定义一个Student类型的消息
message Student {
int32 id = 1;
string name = 2;
// repeated Book表示 List<Book>的意思
// 定义一个List类型的字段,名为book,编号为3
repeated Book book = 3;
// map<type1, type2>
// 定义一个map列席的字段,名为attr,编号为4
map<string, string> attr = 4;
// 定义一个子类型Book, 其中包含id,name两个字段
message Book {
int32 id = 1;
string name = 2;
}
//枚举类型 枚举类型中第一个值必须是0值,因为必须存在一个0值作为枚举类型的缺省值
enum PhoneType {
MOBILE = 0;
HOME = 1;
WORK = 2;
}
}
service StudentService{
//简单的RPC
//定义一个名为StudentService的RPC服务 其中有一个名为GetStudentInfo的方法 该方法接受一个Student消息类型的参数并返回一个Result消息类型的响应
//客户端使用 Stub 发送请求到服务器并等待响应返回,就像平常的函数调用一样,这是一个阻塞型的调用
rpc GetStudentInfo(Student) returns (Result) {}
//服务器端流式RPC
//客户端发送请求到服务器,拿到一个流去读取返回的消息序列。客户端读取返回的流,直到里面没有任何消息。从例子中可以看出,通过在响应类型前插入 stream 关键字,可以指定一个服务器端的流方法
rpc ListFeatures(Student) returns (stream Result) {}
//客户端流式 RPC
//客户端写入一个消息序列并将其发送到服务器,同样也是使用流。一旦客户端完成写入消息,它等待服务器完成读取返回它的响应。通过在请求类型前指定 stream 关键字来指定一个客户端的流方法
rpc RecordRoute(stream Student) returns (Result) {}
//双向流式 RPC
//双方使用读写流去发送一个消息序列。两个流独立操作,因此客户端和服务器可以以任意喜欢的顺序读写:比如, 服务器可以在写入响应前等待接收所有的客户端消息,或者可以交替的读取和写入消息,或者其他读写的组合。每个流中的消息顺序被预留。你可以通过在请求和响应前加 stream 关键字去制定方法的类型
rpc RouteChat(stream Student) returns (stream Result) {}
}
标识号
在ProtoBuf中每一个子段都需要分配一个唯一的编号(以便解析器在消息二进制编码期间可以识别和处理消息中的字段)编号必须为正整数,且不能重复或跳过(范围1-536870911)编号一旦指定,以后更新协议的时候也不能修改,否则无法对旧版本兼容。
5.补充
5.1 在proto语法中,有两种引用其他 proto 文件的方法: import
和 import public
// my.proto
import "first.proto";
my.proto不能使用second.proto可以使用first.proto first.proto 可以使用 second.proto
// first.proto
//import "second.proto";
import public "second.proto";
my.proto能使用second.proto也可以使用first.proto first.proto 可以使用 second.proto
5.2 升级proto文件
升级更改 proto 需要遵循以下原则
- 不要修改任何已存在的变量的 Tag
- 如果你新增了变量,新生成的代码依然能解析旧的数据,但新增的变量将会变成默认值。相应的,新代码序列化的数据也能被旧的代码解析,但旧代码会自动忽略新增的变量。
- 废弃不用的变量用 reserved 标注
- int32、 uint32、 int64、 uint64 和 bool 是相互兼容的,这意味你可以更改这些变量的类型而不会影响兼容性
- sint32 和 sint64 是兼容的,但跟其他类型不兼容
- string 和 bytes 可以兼容,前提是他们都是UTF-8编码的数据
- fixed32 和 sfixed32 是兼容的, fixed64 和 sfixed64是兼容的
Options 分为 file-level options(只能出现在最顶层,不能在消息、枚举、服务内部使用)、 message-level options(只能在消息内部使用)、field-level options(只能在变量定义时使用)
- java_package (file option):指定生成类的包名,如果没有指定此选项,将由关键字package指定包名。此选项只在生成 java 代码时有效
- java_multiple_files (file option):如果为 true, 定义在最外层的 message 、enum、service 将作为单独的类存在
- java_outer_classname (file option):指定最外层class的类名,如果不指定,将会以文件名作为类名
- optimize_for (file option):可选有 [SPEED|CODE_SIZE|LITE_RUNTIME] ,分别是效率优先、空间优先,第三个lite是兼顾效率和代码大小,但是运行时需要依赖 libprotobuf-lite
- cc_enable_arenas (file option):启动arena allocation,c++代码使用
- objc_class_prefix (file option):Objective-C使用
- deprecated (field option):提示变量已废弃、不建议使用
option java_package = "com.example.foo";
option java_multiple_files = true;
option java_outer_classname = "Ponycopter";
option optimize_for = CODE_SIZE;
int32 old_field = 6 [deprecated=true];