Protocol Buffers(proto3)

简介

protocol buffer是google的一个开源项目,它是用于结构化数据串行化的灵活、高效、自动的方法,例如XML,不过它比xml更小、更快、也更简单。你可以定义自己的数据结构,然后使用代码生成器生成的代码来读写这个数据结构。你甚至可以在无需重新部署程序的情况下更新数据结构

定义一个消息类型

一个简单的.proto文件

在.proto文件定义消息,message是.proto文件最小的逻辑单元,由一系列name-value键值对构成。

syntax = "proto3";

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

// This is an annotation
  1. syntax: 注明使用的语法,常用的又proto2和proto3两种

  2. 在一个message中,定义的每个字段由字段限制,字段类型,字段名和编号四部分组成

    1. 字段限制
    required: 必须赋值的字段(proto3没有了)
    optional: 可有可无的字段
    repeated: 可重复的字段
    

    注意:使用required字段一定要小心,因为该字段是永久性的。如果以后因为某种原因,想不用该字段,或者要将该字段改成optional或者repeated,那么使用旧接口读取新的协议时,如果发现没有该字段,他们会认为该字段是不完整的,会拒接接收该消息,或者直接丢弃。
    2. 字段类型

    .proto TypeNotesC++ TypeJava TypePython Type[2]Go TypeRuby TypeC# TypePHP TypeDart Type
    doubledoubledoublefloatfloat64Floatdoublefloatdouble
    floatfloatfloatfloatfloat32Floatfloatfloatdouble
    int32Uses variable-length encoding. Inefficient for encoding negative numbers – if your field is likely to have negative values, use sint32 instead.int32intintint32Fixnum or Bignum (as required)intintegerint
    int64Uses variable-length encoding. Inefficient for encoding negative numbers – if your field is likely to have negative values, use sint64 instead.int64longint/long[3]int64Bignumlonginteger/string[5]Int64
    uint32Uses variable-length encoding.uint32int[1]int/long[3]uint32Fixnum or Bignum (as required)uintintegerint
    uint64Uses variable-length encoding.uint64long[1]int/long[3]uint64Bignumulonginteger/string[5]Int64
    sint32Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int32s.int32intintint32Fixnum or Bignum (as required)intintegerint
    sint64Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int64s.int64longint/long[3]int64Bignumlonginteger/string[5]Int64
    fixed32Always four bytes. More efficient than uint32 if values are often greater than 228.uint32int[1]int/long[3]uint32Fixnum or Bignum (as required)uintintegerint
    fixed64Always eight bytes. More efficient than uint64 if values are often greater than 256.uint64long[1]int/long[3]uint64Bignumulonginteger/string[5]Int64
    sfixed32Always four bytes.int32intintint32Fixnum or Bignum (as required)intintegerint
    sfixed64Always eight bytes.int64longint/long[3]int64Bignumlonginteger/string[5]Int64
    boolboolbooleanboolboolTrueClass/FalseClassboolbooleanbool
    stringA string must always contain UTF-8 encoded or 7-bit ASCII text.stringStringstr/unicode[4]stringString (UTF-8)stringstringString
    bytesMay contain any arbitrary sequence of bytes.stringByteStringstr[]byteString (ASCII-8BIT)ByteStringstring
    除了这些基本类型,也可以是自定义的message类型
    1. 字段编号

    消息中的每一个字段都有一个独一无二的数值类型的编号。1到15使用一个字节编码,16到2047使用2个字节编码,所以应该将编号1到15留给频繁使用的字段。
    可以指定的最小的编号为1,最大为2^{29}-1或536,870,911。但是不能使用19000到19999之间的值,这些值是预留给protocol buffer的。

  3. //用于注释

预留字段

如果消息的字段被移除或注释掉,但是使用者可能重复使用字段编码,就有可能导致例如数据损坏、隐私漏洞等问题。一种避免此类问题的方法就是指明这些删除的字段是保留的。如果有用户使用这些字段的编号,protocol buffer编译器会发出告警。

message Foo {
  reserved 2, 15, 9 to 11;
  reserved "foo", "bar";
}

默认值(proto3默认值不能指定了)

如果没有指定默认值,则会使用系统默认值,对于string默认值为空字符串,对于bool默认值为false,对于数值类型默认值为0,对于enum默认值为定义中的第一个元素,对于repeated默认值为空。

message PhoneNumber {
    required string number=1;
    optional PhoneType type=2 [default=HOME];
}

枚举类型

message SearchRequest {
  string query = 1;
  int32 page_number = 2;
  int32 result_per_page = 3;
  enum Corpus {
    UNIVERSAL = 0;
    WEB = 1;
    IMAGES = 2;
    LOCAL = 3;
    NEWS = 4;
    PRODUCTS = 5;
    VIDEO = 6;
  }
  Corpus corpus = 4;
}

通过设置可选参数allow_alias为true,就可以在枚举结构中使用别名(两个值元素值相同)

enum EnumAllowingAlias {
  option allow_alias = true;
  UNKNOWN = 0;
  STARTED = 1;
  RUNNING = 1;
}
enum EnumNotAllowingAlias {
  UNKNOWN = 0;
  STARTED = 1;
  // RUNNING = 1;  // Uncommenting this line will cause a compile error inside Google and a warning message outside.
}

由于枚举值采用varint编码,所以为了提高效率,不建议枚举值取负数。这些枚举值可以在其他消息定义中重复使用。

使用其他消息类型

可以使用一个消息的定义作为另一个消息的字段类型

message SearchResponse {
  repeated Result results = 1;
}

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

你还可以导入其他的.proto文件

import "myproject/other_protos.proto";

嵌套类型

在protocol中可以定义如下的嵌套类型

message TsirResponse{
    message Man{
        int sex = 1;
        float weight = 2;
    }
    Man base = 1;
}

如果在另外一个消息中需要使用Man定义,则可以通过Parent.Type来使用

message OtherMessage {
  TsirResponse.Man other = 1;
}

更新一个数据类型

在实际的开发中会存在这样一种应用场景,既消息格式因为某些需求的变化而不得不进行必要的升级,但是有些使用原有消息格式的应用程序暂时又不能被立刻升级,这便要求我们在升级消息格式时要遵守一定的规则,从而可以保证基于新老消息格式的新老程序同时运行。规则如下:

  • 不要修改已经存在字段的标签号。
  • 任何新添加的字段必须是optional和repeated限定符,否则无法保证新老程序在互相传递消息时的消息兼容性。
  • 在原有的消息中,不能移除已经存在的required字段,optional和repeated类型的字段可以被移除,但是他们之前使用的标签号必须被保留,不能被新的字段重用。
    int32、uint32、int64、uint64和bool等类型之间是兼容的,sint32和sint64是兼容的,string和bytes是兼容的,fixed32和sfixed32,以及fixed64和sfixed64之间是兼容的,这意味着如果想修改原有字段的类型时,为了保证兼容性,只能将其修改为与其原有类型兼容的类型,否则就将打破新老消息格式的兼容性。
  • optional和repeated限定符也是相互兼容的。

任意消息类型Any

Any类型是一种不需要在.proto文件中定义就可以直接使用的消息类型,使用前import google/protobuf/any.proto文件即可。

import  "google/protobuf/any.proto";

message ErrorStatus  {
  string message =  1;
  repeated google.protobuf.Any details =  2;
}

Oneof

消息中的多个字段类型在同一时刻只有一个字段会被使用,使用case()或WhichOneof()方法来检测哪个字段被使用了。

message SampleMessage  {
  oneof test_oneof {
    string name =  4;
    SubMessage sub_message =  9;
  }
}

Maps表映射

protocol buffers提供了简介的语法来实现map类型:

map<key_type, value_type> map_field = N;

key_type可以是除浮点指针或bytes外的其他基本类型,value_type可以是任意类型

map<string,  Project> projects =  3;
  • Map的字段不可以是重复的(repeated)
  • 线性顺序和map值的的迭代顺序是未定义的,所以不能期待map的元素是有序的
  • maps可以通过key来排序,数值类型的key通过比较数值进行排序
  • 线性解析或者合并的时候,如果出现重复的key值,最后一个key将被使用。从文本格式来解析map,如果出现重复key值则解析失败。

命名空间,用来防止名称冲突

package foo.bar;

定义服务

如果想在RPC系统中使用消息类型,就需要在.proto文件中定义RPC服务接口,然后使用编译器生成对应语言的存根。

service SearchService {
  rpc Search (SearchRequest) returns (SearchResponse);
}

JSON映射

Proto3支持JSON格式的编码。编码后的JSON数据的如果没有值或值为空,解析时protocol buffer将会使用默认值,在对JSON编码时可以节省空间。

proto3JSONJSON exampleNotes
messageobject{“fBar”: v, “g”: null, …}Generates JSON objects. Message field names are mapped to lowerCamelCase and become JSON object keys. null is accepted and treated as the default value of the corresponding field type.
enumstring“FOO_BAR”The name of the enum value as specified in proto is used.
map< K,V>object{“k”: v, …}All keys are converted to strings.
repeated Varray[v, …]null is accepted as the empty list [].
booltrue, falsetrue, false
stringstring“Hello World!”
bytesbase64 string“YWJjMTIzIT8kKiYoKSctPUB+”
int32, fixed32, uint32number1, -10, 0JSON value will be a decimal number. Either numbers or strings are accepted.
int64, fixed64, uint64string“1”, “-10”JSON value will be a decimal string. Either numbers or strings are accepted.
float, doublenumber1.1, -10.0, 0, “NaN”, “Infinity”JSON value will be a number or one of the special string values “NaN”, “Infinity”, and “-Infinity”. Either numbers or strings are accepted. Exponent notation is also accepted.
Anyobject{“@type”: “url”, “f”: v, … }If the Any contains a value that has a special JSON mapping, it will be converted as follows: {“@type”: xxx, “value”: yyy}. Otherwise, the value will be converted into a JSON object, and the “@type” field will be inserted to indicate the actual data type.
Timestampstring“1972-01-01T10:00:20.021Z”Uses RFC 3339, where generated output will always be Z-normalized and uses 0, 3, 6 or 9 fractional digits.
Durationstring“1.000340012s”, “1s”Generated output always contains 0, 3, 6, or 9 fractional digits, depending on required precision. Accepted are any fractional digits (also none) as long as they fit into nano-seconds precision.
Structobject{ … }Any JSON object. See struct.proto.
Wrapper typesvarious types2, “2”, “foo”, true, “true”, null, 0, …Wrappers use the same representation in JSON as the wrapped primitive type, except that null is allowed and preserved during data conversion and transfer.
FieldMaskstring“f.fooBar,h”See fieldmask.proto.
ListValuearray[foo, bar, …]
ValuevalueAny JSON value
NullValuenullJSON null
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值