Protocol Buffers 3.0

定義:

 一種  結構化數據 的數據存儲格式。(類似於xml, json)

作用

通過將  結構化的數據  進行串行化(序列化),從而實現 數據存儲/rpc數據交換 的功能

序列化:將數據結構或對象 轉換成 二進制的  過程

飯序列化:將在序列化過程中所生成的二進制串  轉換成  數據結構或對象 的過程。

特點

相對於xml,json,protocol buffer有如下特點:

 

應用場景

傳輸數據量大 & 網絡環境不穩定的數據存儲 、rpc數據交換的需求場景。

序列化原理解析:

序列化本質:對數據進行編碼 + 存儲

1. 序列化速度快:

   編碼/解碼 方式簡單(只需簡單的數學運算= 位移等)

  採用pb自身的框架代碼 和編譯器共同完成

2. 序列化後體積小(數據壓縮效果好)

  採用了獨特的編碼方式,如Varint, Zigzag等

  採用T - L - V 的數據存儲方式,

  • 即 Tag - Length - Value,標識 - 長度 - 字段值 存儲方式,減少了分隔符的使用,數據存儲得緊湊

protocol buffer語言來組織你的protocol buffer數據,包括.proto文件的語法規則以及如何通過.proto文件來生成數據訪問類代碼。

Defining A Message Type(定義一個消息類型)

syntax = "proto3";

message SearchRequest {
  string query = 1;
  int32 page_number = 2;
  int32 result_per_page = 3;
}
  • 語法說明(syntax)前只能是空行或者註釋
  • 每個字段由字段限制、字段類型、字段名和編號四部分組成

Specifying Field Types(指定字段類型)

在上面的例子中,該消息定義了三個字段,兩個int32類型和一個string類型的字段

Assigning Tags(賦予編號)

消息中的每一個字段都有一個獨一無二的數值類型的編號。1到15使用一個字節編碼,16到2047使用2個字節編碼,所以應該將編號1到15留給頻繁使用的字段。
可以指定的最小的編號爲1,最大爲2^{29}-1或536,870,911。但是不能使用19000到19999之間的值,這些值是預留給protocol buffer的。

Specifying Field Rules(指定字段限制)

  • required:必須賦值的字段
  • optional:可有可無的字段
  • repeated:可重複字段(變長字段)

Adding More Message Types(添加更多消息類型)

一個.proto文件可以定義多個消息類型:

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

message SearchResponse {
 ...
}

Adding Comments(添加註釋)

.proto文件也使用C/C++風格的註釋語法//

message SearchRequest {
  string query = 1;
  int32 page_number = 2;  // Which page number do we want?
  int32 result_per_page = 3;  // Number of results to return per page.
}

Reserved Fields(預留字段)

如果消息的字段被移除或註釋掉,但是使用者可能重複使用字段編碼,就有可能導致例如數據損壞、隱私漏洞等問題。一種避免此類問題的方法就是指明這些刪除的字段是保留的。如果有用戶使用這些字段的編號,protocol buffer編譯器會發出告警。

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

What‘s Generated From Your .proto?(編譯.proto文件)

對於C++,每一個.proto文件經過編譯之後都會對應的生成一個.h和一個.cc文件。

Scalar Value Types(類型對照表)

.proto Type Notes C++ Type
double double double
float float float
int32 Uses variable-length encoding. Inefficient for encoding negative numbers – if your field is likely to have negative values, use sint32 instead. int32
int64 Uses variable-length encoding. Inefficient for encoding negative numbers – if your field is likely to have negative values, use sint64 instead. int64
uint32 Uses variable-length encoding. uint32
uint64 Uses variable-length encoding. uint64
sint32 Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int32s. int32
sint64 Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int64s. int64
fixed32 Always four bytes. More efficient than uint32 if values are often greater than 2^28 uint32
fixed64 Always eight bytes. More efficient than uint64 if values are often greater than 2^56 uint64
sfixed32 Always four bytes. int32
sfixed64 Always eight bytes. int64
bool bool boolean
string A string must always contain UTF-8 encoded or 7-bit ASCII text. string
bytes May contain any arbitrary sequence of bytes. string

Default Values(缺省值)

如果沒有指定默認值,則會使用系統默認值,對於string默認值爲空字符串,對於bool默認值爲false,對於數值類型默認值爲0,對於enum默認值爲定義中的第一個元素,對於repeated默認值爲空。

Enumerations(枚舉)

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編碼,所以爲了提高效率,不建議枚舉值取負數。這些枚舉值可以在其他消息定義中重複使用。

Using Other Message Types(使用其他消息類型)

可以使用一個消息的定義作爲另一個消息的字段類型。

message SearchResponse {
  repeated Result results = 1;
}

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

Importing Definitions(導入定義)

就像C++的頭文件一樣,你還可以導入其他的.proto文件

import "myproject/other_protos.proto";

如果想要移動一個.proto文件,但是又不想修改項目中import部分的代碼,可以在文件原先位置留一個空.proto文件,然後使用import public導入文件移動後的新位置:

// new.proto
// All definitions are moved here
// old.proto
// This is the proto that all clients are importing.
import public "new.proto";
import "other.proto";
// client.proto
import "old.proto";
// You use definitions from old.proto and new.proto, but not other.proto

Nested Types(嵌套類型)

在protocol中可以定義如下的嵌套類型

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

如果在另外一個消息中需要使用Result定義,則可以通過Parent.Type來使用。

message SomeOtherMessage {
  SearchResponse.Result result = 1;
}

protocol支持更深層次的嵌套和分組嵌套,但是爲了結構清晰起見,不建議使用過深層次的嵌套。

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;
    }
  }

Updating A Message Type(更新一個數據類型)

在實際的開發中會存在這樣一種應用場景,既消息格式因爲某些需求的變化而不得不進行必要的升級,但是有些使用原有消息格式的應用程序暫時又不能被立刻升級,這便要求我們在升級消息格式時要遵守一定的規則,從而可以保證基於新老消息格式的新老程序同時運行。規則如下:

  • 不要修改已經存在字段的標籤號。
  • 任何新添加的字段必須是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;
}

C++使用PackFrom()UnpackTo()方法來打包和解包Any類型消息。

// Storing an arbitrary message type in Any.
NetworkErrorDetails details =  ...;
ErrorStatus status;
status.add_details()->PackFrom(details);

// Reading an arbitrary message from Any.
ErrorStatus status =  ...;
for  (const  Any& detail : status.details())  {  if  (detail.Is<NetworkErrorDetails>())  {    NetworkErrorDetails network_error;
    detail.UnpackTo(&network_error);    ... processing network_error ...  }
}

Oneof(其中一個字段類型)

有點類似C++中的聯合,就是消息中的多個字段類型在同一時刻只有一個字段會被使用,使用case()WhichOneof()方法來檢測哪個字段被使用了。

Using Oneof(使用Oneof)

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

你可以添加除repeated外任意類型的字段到Oneof定義中

Oneof Features(Oneof特性)

  • oneof字段只有最後被設置的字段纔有效,即後面的set操作會覆蓋前面的set操作

    SampleMessage message;
    message.set_name("name");
    CHECK(message.has_name());
    message.mutable_sub_message();   // Will clear name field.
    CHECK(!message.has_name());
  • oneof不可以是repeated
  • 反射API可以作用於oneof字段
  • 如果使用C++要防止內存泄露,即後面的set操作會覆蓋之前的set操作,導致前面設置的字段對象發生析構,要注意字段對象的指針操作

    SampleMessage message;
    SubMessage* sub_message = message.mutable_sub_message();
    message.set_name("name");      // Will delete sub_message
    sub_message->set_...            // Crashes her
  • 如果使用C++的Swap()方法交換兩條oneof消息,兩條消息都不會保存之前的字段

    SampleMessage msg1;
    msg1.set_name("name");
    SampleMessage msg2;
    msg2.mutable_sub_message();
    msg1.swap(&msg2);
    CHECK(msg1.has_sub_message());
    CHECK(msg2.has_name());

Backwards-compatibility issues(向後兼容)

添加或刪除oneof字段的時候要注意,如果檢測到oneof字段的返回值是None/NOT_SET,這意味着oneof沒有被設置或者設置了一個不同版本的oneof的字段,但是沒有辦法能夠區分這兩種情況,因爲沒有辦法確認一個未知的字段是否是一個oneof的成員。

Tag Reuse Issues(編號複用問題)

  • 刪除或添加字段到oneof:在消息序列化或解析後會丟失一些信息,一些字段將被清空
  • 刪除一個字段然後重新添加:在消息序列化或解析後會清除當前設置的oneof字段
  • 分割或合併字段:同普通的刪除字段操作

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值則解析失敗。

Backwards compatibility(向後兼容)

map語法下面的表達方式在線性上是等價的,所以即使protocol buffers沒有實現maps數據結構也不會影響數據的處理:

message MapFieldEntry  {
  key_type key =  1;
  value_type value =  2;
}
repeated MapFieldEntry map_field = N;

類似C++的命名空間,用來防止名稱衝突

package foo.bar;
message Open { ... }

你可以使用包說明符來定義你的消息字段:

message Foo {
  ...
  foo.bar.Open open = 1;
  ...
}

定義服務

如果想在RPC系統中使用消息類型,就需要在.proto文件中定義RPC服務接口,然後使用編譯器生成對應語言的存根。

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

JSON映射

Proto3支持JSON格式的編碼。編碼後的JSON數據的如果沒有值或值爲空,解析時protocol buffer將會使用默認值,在對JSON編碼時可以節省空間。

proto3 JSON JSON example Notes
message object {"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.
enum string "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 V array [v, …] null is accepted as the empty list [].
bool true, false true, false  
string string "Hello World!"  
bytes base64 string "YWJjMTIzIT8kKiYoKSctPUB+"  
int32, fixed32, uint32 number 1, -10, 0 JSON value will be a decimal number. Either numbers or strings are accepted.
int64, fixed64, uint64 string "1", "-10" JSON value will be a decimal string. Either numbers or strings are accepted.
float, double number 1.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.
Any object {"@type": "url", "f": v, … } If the Any contains a value that has a special JSON mapping, it will be converted as follows: {"@type": xxx,<wbr style="box-sizing: inherit;"> "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.
Timestamp string "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.
Duration string "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.
Struct object { … } Any JSON object. See struct.proto.
Wrapper types various types 2, "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.
FieldMask string "f.fooBar,h" See fieldmask.proto.
ListValue array [foo, bar, …]  
Value value   Any JSON value
NullValue null   JSON null

選項

Protocol Buffer允許我們在.proto文件中定義一些常用的選項,這樣可以指示Protocol Buffer編譯器幫助我們生成更爲匹配的目標語言代碼。Protocol Buffer內置的選項被分爲以下三個級別:

文件級別,這樣的選項將影響當前文件中定義的所有消息和枚舉。
消息級別,這樣的選項僅影響某個消息及其包含的所有字段。
字段級別,這樣的選項僅僅響應與其相關的字段。

下面將給出一些常用的Protocol Buffer選項。

  • optimize_for(文件選項):可以設置的值有SPEEDCODE_SIZE 或 LITE_RUNTIME,不同的選項會以下述方式影響C++代碼的生成(option optimize_for = CODE_SIZE;)。

    SPEED (default): protocol buffer編譯器將會生成序列化,語法分析和其他高效操作消息類型的方式.這也是最高的優化選項.確定是生成的代碼比較大.
    CODE_SIZE: protocol buffer編譯器將會生成最小的類,確定是比SPEED運行要慢
    LITE_RUNTIME: protocol buffer編譯器將會生成只依賴"lite" runtime library (libprotobuf-lite instead of libprotobuf)的類. lite運行時庫比整個庫更小但是刪除了例如descriptors 和 reflection等特性. 這個選項通常用於手機平臺的優化.
  • cc_enable_arenas(文件選項):生成的C++代碼啓用arena allocation內存管理
  • deprecated(文件選項):

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章