Proto buffers

Proto buffers文件是什么

你可以理解Protocol Buffers是一种更加灵活、高效的数据格式,与XML、JSON类似,在一些高性能且对响应速度有要求的数据传输场景非常适用。

Proto buffers文件结构

文件格式

​ .proto结尾的文件,就是Proto buffers文件

先看一个简单的例子

syntax = "proto3";

message SearchRequest {
  string query = 1;
  int32 page_number = 2;
  int32 result_per_page = 3; // 添加注释
}

第一行的含义是限定该文件使用的是proto3的语法,如果没有 syntax = “proto3”;

SearchRequest定义有三个承载消息的属性,每一个被定义在SearchRequest消息体中的字段,都是由数据类型和属性名称组成。

指定字段类型

在上面的例子中,所有的属性都是标量,两个整型(page_number、result_per_page)和一个字符串(query),你还可以在指定复合类型,包括枚举类型或者其他的消息类型。

分配标量

就像所看见的一样,每一个被定义在消息中的字段都会被分配给一个唯一的标量,这些标量用于标识你定义在二进制消息格式中的属性,标量一旦被定义就不允许在使用过程中再次被改变。标量的值在1~15的这个范围里占一个字节编码(详情请参看 谷歌的 Protocol Buffer Encoding )。

指定属性规则

singular: 一个正确的消息可以有零个或者多个这样的消息属性(但是不要超过一个).
repeated: 这个属性可以在一个正确的消息格式中重复任意次数(包括零次),

在proto3中,标量数字类型的重复字段默认使用压缩编码

添加注释

proto文件中的注释使用的是c/c++中的单行注释 // 语法风格。

message SearchRequest {
  string query = 1;
  int32 page_number = 2;  // 当前页数
  int32 result_per_page = 3;  // 每页数据返回的数据量

保留属性

为了避免在加载相同的.proto的旧版本,包括数据损坏,隐含的错误等,这可能会导致严重的问题的方法是指定删除的字段的字段标签(和/或名称,也可能导致JSON序列化的问题)被保留。 如果将来的用户尝试使用这些字段标识符,协议缓冲区编译器将会报错。

保留字段的使用例子:

message Foo {
  reserved 2;
  reserved "foo", "bar";
}

上述例子定义保留属性为"foo", "bar",定义保留属性位置为2,即在2这个位置上不可以定义属性,如:string name=2;是不允许的,编译器在编译proto文件的时候如果发现,2这个位置上有属性被定义则会报错。

定义更多消息类型

在一个proto文件中可以定义多个消息类型,你可以在一个文件中定义一些相关的消息类型,上面的例子proto文件中只有一个请求查找的消息类型,现在可以为他多添加一个响应的消息类型,具体如下:

syntax = "proto3";

message SearchRequest {
  string query = 1;
  int32 page_number = 2;
  int32 result_per_page = 3;
}

message SearchResponse {
    ....
}

基本类型(例如:c#中领域模型)

1586238644300

基本类型默认值

当proto消息被解析成具体的语言的时候,如果消息编码没包含特定的元素,则消息对象中的属性会被设置默认值,这些默认值具体如下:

  • string类型,默认值是空字符串,注意不是null
  • bytes类型,默认值是空bytes
  • bool类型,默认值是false
  • 数字类型,默认值是0
  • 枚举类型,默认值是第一个枚举值,即0
  • repeated修饰的属性,默认值是空(在相对应的编程语言中通常是一个空的list).

定义

定义枚举

1、定义
enum EnumAllowingAlias {
  option allow_alias = true;
  UNKNOWN = 0;
  STARTED = 1;
  RUNNING = 1;
}
enum EnumNotAllowingAlias {
  UNKNOWN = 0;
  STARTED = 1;
  //如果解除这个注释编译器在编译该proto文的时候会报错
  // RUNNING = 1;  
}
2、使用
message SearchRequest {
  EnumNotAllowingAlias nnumNotAllowingAlias = 4;
}

如上例中所示,Corpus枚举类型的第一个枚举值是0,每一个枚举值定义都会与一个常量映射,而这些常量的第一个常量值必须为0,原因如下:

  • 必须有一个0作为值,以至于我们可是使用0作为默认值
  • 第一个元素的值取0,用于与第一个元素枚举值作为默认值的proto2语义兼容

定义复杂数据类型

你可以在定义消息类型的时候引用其他已经定义好的消息类型作为新消息类型的属性,官网例子如下:

message SearchResponse {
  Result results = 1;
}

message Result {
  string url = 1;
  string title = 2;
  string snippets = 3;
}

在上面的消息例子中,SearchResponse这个响应消息类型的属性results,返回的是一个Result类型的消息。

定义集合类型

message SearchResponse {
 repeated Result results = 1;
}

message Result {
  string url = 1;
  string title = 2;
 repeated string snippets = 3;
}

在上面的消息例子中,SearchResponse这个响应消息类型的属性results,返回的是一个Result类型的消息集合。Result里面的snippets返回的也是集合

定义proto中如何引用其他proto文件

在上面的例子中,Result和SearchResponse消息类型被定义在同一个.proto文件中,如果把他们分成两个文件定义,应该如何引用呢?

proto中为我们提供了import 关键字用于引入不同.proto文件中的消息类型,你可以在你的.proto文件的顶部加入如下语句因为其他.proto文件的消息类型:
import "myproject/other_protos.proto";
例子:

  • 文件名称search_response.proto
syntax = "proto3";
import "test/result.proto";
package test1;

message SearchResponse {
  //包名.消息名
  repeated test2.Result results = 1;
}
  • 文件名称result.proto,在与search_response.proto同级目录的test下
syntax = "proto3";
package test2;

message Result {
  string url = 1;
  string title = 2;
  repeated string snippets = 3;
}

如果两个.proto文件在同一个目录下直接这样import "result.proto";倒入即可。

定义内嵌类型

我们还可以在消息类型中定义消息,例子如下:

message SearchResponse {
  message Result {
    string url = 1;
    string title = 2;
    repeated string snippets = 3;
  }
  repeated Result results = 1;
}

在上面的例子中在SearchResponse消息体中定义了一个Result消息并使用。

如果想在其他的消息体引用Result这个消息,可以Parent.Type这样引用,例子:

message SomeOtherMessage {
  SearchResponse.Result result = 1;
}

消息还可以深层的嵌套定义,如下例子:

message Outer {                  // Level 0
  message MiddleAA {  // Level 1
    message Inner {   // Level 2
      int64 ival = 1;
      bool  booly = 2;
    }
  }
  message MiddleBB {  // Level 1
    message Inner {   // Level 2
      int32 ival = 1;
      bool  booly = 2;
    }
  }
}

定义Map(例如:字典)

proto支持map属性类型的定义,语法如下:
map<key_type,value_type> map_field = N;
key_type可以是任何整数或字符串类型(除浮点类型和字节之外的任何标量类型,枚举类型也是不合法的key类型),value_type可以是任何类型的数据。

message SomeOtherMessage {
  map<string,string> resultMap = 0;
  map<string,Result> resultMap = 0;
}

注意:Key in map fields cannot be float/double, bytes or message types

定义服务

如果你想在RPC中使用已经定义好的消息类型,你可以在.proto文件中定一个消息服务接口,protocol buffer编译器会生成对应语言的接口代码。

  • 接口定义例子:
service SearchService {
    //  方法名  方法参数                 返回值
    rpc Search(SearchRequest) returns (SearchResponse); 
}

定义流式服务

如果你想在RPC中使用已经定义好的消息类型,你可以在.proto文件中定一个消息服务接口,protocol buffer编译器会生成对应语言的接口代码。

  • 接口定义例子:
service SearchService {
    //  方法名  方法参数                 返回值
    rpc Search(SearchRequest) returns (stream SearchResponse); 
}