文章开头,先贴一段protobuf的定义语法
option java_outer_classname = "StudentEntity";
message Student {
required int32 id = 1;
required string name = 2;
optional string nickname = 3 [default=""];
}
一、message
protobuf中定义一个数据结构需要用到关键字message,这一点和java的class是差不多的。
二、标识号
在消息的定义中,每个字段等号后面都有唯一的标识号,用于在反序列化过程中识别各个字段的,一旦开始使用就不能改变。
三、字段规则
字段规则有三种:
- required:该规则规定,消息体中该字段的值是必须要设置的。
- optional:消息体中该规则的字段的值可以存在,也可以为空,optional的字段可以根据defalut设置默认值。
- repeated:消息体中该规则字段可以存在多个(包括0个),该规则对应java的数组。
四、数据类型
数据类型没什么好说的,都是常见的数据类型,具体见下面表格
五、枚举及其它消息类型的使用
option java_outer_classname = "StudentEntity";
message Student {
required int32 id = 1;
required string name = 2;
optional string nickname = 3 [default=""];
enum Age{
男=1;
女=2;
}
required Age age=4;
required Class class=5;
}
message Class{
required int32 id=1;
required string name=2;
}
除此之外,message还支持map类型的字段,声明如下
map<int32, string> m = N;
如果需要引用的message是写在别的.proto文件中,可以通过import "xxx.proto"来进行引入。
六、嵌套
option java_outer_classname = "StudentEntity";
message Student {
required int32 id = 1;
required string name = 2;
optional string nickname = 3 [default=""];
enum Age{
男=1;
女=2;
}
required Age age=4;
message Class{
required int32 id=1;
required string name=2;
}
required Class class=5;
}
七、Option
在消息定义前,可以通过option来进行配置,常用的两个option:
- option java_package="xxx/xxx" 该选项指定了java文件生成的路径
- option java_outer_classname="xxx" 该选项制定了生成的java类名
八、message的更新
message定义后如果需要修改,为了保证之前的序列化和反序列化能够兼容于新的message,message的修改需要满足以下规则:
- 不可以修改已存在域中的标识号
- 所有新增添的域必须是 optional 或者 repeated。这就意味着,只要不遗失已经定义的required域,旧序列化信息可以被新的代码解析。为了新代码可以正常解析旧代码提供的序列,我们需要在更新的时候向新域中添加一些默认值(默认值是不会被传递的,仅仅在本地保存,也就是说不同版本的代码对同一个域可能读取不同的值)。同样,新代码产生的序列化文件也可以被旧代码解析,解析时旧代码会忽略新添加的域,这些域在解析时不会被丢弃,在旧代码序列化时,这些域会跟随其他域一起被序列化。将序列化之后的文件传递给新代码。这些新域也是有效的。
- 非required域可以被删除。但是这些被删除域的标识号不可以再次被使用。
- 非required域可以被转化,转化时可能发生扩展或者截断,此时标识号和名称都是不变的。
- int32, uint32, int64, uint64 和bool都是相互兼容的。这就意味着这些类型之间可以相互转换,并且都是向前,向后兼容的。如果我们设定的类别不足以表示数字,那么就会发生类似于C++的截断。
- sint32和sint64是相互兼容的,并且不和其他整数类型兼容
- string 在UTF-8编码时与bytes相互兼容
- 嵌套Message在编码方式相同的情况下兼容bytes
- fixed32兼容sfixed32。 fixed64兼容sfixed64。
- optional兼容repeated。发送端发送repeated域,用户使用optional域读取,将会读取repeated域的最后一个元素。
- 改变默认值通常来说是没有问题的。但值得注意的是:只要用户的版本没有更新,那么在没有设置这个域的情况下,用户依旧是读取原来的默认值。
- 枚举类型兼容int32, uint32, int64, uint64(注意如果值和类型不匹配则会发生截断),所以我们需要注意,用户端可能在解析之后对这些值进行非enum的一些处理。通常,没能识别(不在enum定义的集合内)的enum值将会被丢弃,此时如果使用has方法,则会返回false。用getter方法读取这个值,会返回enum 列表的第一个值,如果定义了default则返回default的值。在repeated enum域里面所有不可识别的元素都将被移除这个列表,除非是整数域,它会保留整数域的值。因此,我们在提升一个整数类型为enum类型的时候要注意是否超过其值域。
- 在现有的java和c++应用中,当不能识别的enum被移除时,它其实是和其他不可识别的元素一起保存在了“未知域(unknown fields)”。当客户端在此解析未知域的时候,如果能够识别,则有可能发生一些奇怪行为。在optional域中,在原来消息解析完毕并写入新的数据之后,用户依然可以读取原来的消息。在repeated域中,旧的值将会出现在新添加的值之后,因此其顺序不是固定的。