protobuf语法详解

文章开头,先贴一段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是差不多的。

二、标识号

在消息的定义中,每个字段等号后面都有唯一的标识号,用于在反序列化过程中识别各个字段的,一旦开始使用就不能改变。

三、字段规则

字段规则有三种:

  1. required:该规则规定,消息体中该字段的值是必须要设置的。
  2. optional:消息体中该规则的字段的值可以存在,也可以为空,optional的字段可以根据defalut设置默认值。
  3. 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:

  1. option java_package="xxx/xxx" 该选项指定了java文件生成的路径
  2. 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域中,旧的值将会出现在新添加的值之后,因此其顺序不是固定的。


阅读更多
想对作者说点什么?
相关热词

博主推荐

换一批

没有更多推荐了,返回首页