Protocol Buffer詳解

1.Protocol Buffer 概念java

  Google Protocol Buffer( 簡稱 Protobuf) 是 Google 公司內部的混合語言數據標準,目前已經正在使用的有超過 48,162 種報文格式定義和超過 12,183 個 .proto 文件。他們用於 RPC 系統和持續數據存儲系統。python

  Protocol Buffers 是一種輕便高效的結構化數據存儲格式,能夠用於結構化數據串行化,或者說序列化。它很適合作數據存儲或 RPC 數據交換格式。可用於通信協議、數據存儲等領域的語言無關、平臺無關、可擴展的序列化結構數據格式。目前提供了 C++、Java、Python 三種語言的 API。編程

2.Protocol Buffer 使用 數組

  使用ProtocolBuffer,要先編寫一個.proto文件,用這個文件來描述你但願保存的數據結構。而後用ProtocolBuffer編譯器建立一個類,這個類用高效的二進制的格式實現了ProtocolBuffer數據的自動編解碼。生成的類提供了組成ProtocolBuffer字段的getter和setter方法,以及提供了負責讀寫一個ProtocolBuffer單位的方法。重要的是ProtocolBuffer格式支持向後的兼容性,新的代碼依然能夠讀取用舊格式編碼的數據。數據結構

2.1定義第一個Protocol Buffer消息 編程語言

 建立擴展名爲.proto的文件,如:MyMessage.proto,並將如下內容存入該文件中。
      message LogonReqMessage {
          required int64 acctID = 1;
          required string passwd = 2;
      }
     
這裏將給出以上消息定義的關鍵性說明。
      1. message是消息定義的關鍵字,等同於C++中的struct/class,或是Java中的class。
      2. LogonReqMessage爲消息的名字,等同於結構體名或類名。
      3. required前綴表示該字段爲必要字段,既在序列化和反序列化以前該字段必須已經被賦值。與此同時,在Protocol Buffer中還存在另外兩個相似的關鍵字,optional和repeated,帶有這兩種限定符的消息字段則沒有required字段這樣的限制。相比於optional,repeated主要用於表示數組字段。具體的使用方式在後面的用例中均會一一列出。
      4. int64和string分別表示長整型和字符串型的消息字段,在Protocol Buffer中存在一張類型對照表,既Protocol Buffer中的數據類型與其餘編程語言(C++/Java)中所用類型的對照。該對照表中還將給出在不一樣的數據場景下,哪一種類型更爲高效。該對照表將在後面給出。
      5. acctID和passwd分別表示消息字段名,等同於Java中的域變量名,或是C++中的成員變量名。
      6. 標籤數字12則表示不一樣的字段在序列化後的二進制數據中的佈局位置。在該例中,passwd字段編碼後的數據必定位於acctID以後。須要注意的是該值在同一message中不能重複。另外,對於Protocol Buffer而言,標籤值爲1到15的字段在編碼時能夠獲得優化,既標籤值和類型信息僅佔有一個byte,標籤範圍是16到2047的將佔有兩個bytes,而Protocol Buffer能夠支持的字段數量則爲2的29次方減一。有鑑於此,咱們在設計消息結構時,能夠儘量考慮讓repeated類型的字段標籤位於1到15之間,這樣即可以有效的節省編碼後的字節數量。


2.2定義第二個(含有枚舉字段)Protocol Buffer消息
      //在定義Protocol Buffer的消息時,可使用和C++/Java代碼一樣的方式添加註釋。
      enum UserStatus {
          OFFLINE = 0;  //表示處於離線狀態的用戶
          ONLINE = 1;   //表示處於在線狀態的用戶
      }
      message UserInfo {
          required int64 acctID = 1;
          required string name = 2;
          required UserStatus status = 3;
      }
      這裏將給出以上消息定義的關鍵性說明(僅包括上一小節中沒有描述的)。
      1. enum是枚舉類型定義的關鍵字,等同於C++/Java中的enum。
      2. UserStatus爲枚舉的名字。
      3. 和C++/Java中的枚舉不一樣的是,枚舉值之間的分隔符是分號,而不是逗號。
      4. OFFLINE/ONLINE爲枚舉值。
      5. 0和1表示枚舉值所對應的實際整型值,和C/C++同樣,能夠爲枚舉值指定任意整型值,而無需老是從0開始定義。如:
      enum OperationCode {
          LOGON_REQ_CODE = 101;
          LOGOUT_REQ_CODE = 102;
          RETRIEVE_BUDDIES_REQ_CODE = 103;
    
          LOGON_RESP_CODE = 1001;
          LOGOUT_RESP_CODE = 1002;
          RETRIEVE_BUDDIES_RESP_CODE = 1003;
      }
工具


2.3定義第三個(含有嵌套消息字段)Protocol Buffer消息
      咱們能夠在同一個.proto文件中定義多個message,這樣即可以很容易的實現嵌套消息的定義。如:
      enum UserStatus {
          OFFLINE = 0;
          ONLINE = 1;
      }
      message UserInfo {
          required int64 acctID = 1;
          required string name = 2;
          required UserStatus status = 3;
      }
      message LogonRespMessage {
          required LoginResult logonResult = 1;
          required UserInfo userInfo = 2;
      }
      這裏將給出以上消息定義的關鍵性說明(僅包括上兩小節中沒有描述的)。
      1. LogonRespMessage消息的定義中包含另一個消息類型做爲其字段,如UserInfo userInfo。
      2. 上例中的UserInfo和LogonRespMessage被定義在同一個.proto文件中,那麼咱們是否能夠包含在其餘.proto文件中定義的message呢?Protocol Buffer提供了另一個關鍵字import,這樣咱們即可以將不少通用的message定義在同一個.proto文件中,而其餘消息定義文件能夠經過import的方式將該文件中定義的消息包含進來,如:
      import "myproject/CommonMessages.proto"
佈局


2.4限定符(required/optional/repeated)的基本規則
      1. 在每一個消息中必須至少留有一個required類型的字段。
      2. 每一個消息中能夠包含0個或多個optional類型的字段。
      3. repeated表示的字段能夠包含0個或多個數據。須要說明的是,這一點有別於C++/Java中的數組,由於後二者中的數組必須包含至少一個元素。
      4. 若是打算在原有消息協議中添加新的字段,同時還要保證老版本的程序可以正常讀取或寫入,那麼對於新添加的字段必須是optional或repeated。道理很是簡單,老版本程序沒法讀取或寫入新增的required限定符的字段。
2.5類型對照表
post

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

 

2.6 Protocol Buffer消息升級原則
      在實際的開發中會存在這樣一種應用場景,既消息格式由於某些需求的變化而不得不進行必要的升級,可是有些使用原有消息格式的應用程序暫時又不能被馬上升級,這便要求咱們在升級消息格式時要遵照必定的規則,從而能夠保證基於新老消息格式的新老程序同時運行。規則以下:
      1. 不要修改已經存在字段的標籤號。
      2. 任何新添加的字段必須是optional和repeated限定符,不然沒法保證新老程序在互相傳遞消息時的消息兼容性。
      3. 在原有的消息中,不能移除已經存在的required字段,optional和repeated類型的字段能夠被移除,可是他們以前使用的標籤號必須被保留,不能被新的字段重用。
      4. int3二、uint3二、int6四、uint64和bool等類型之間是兼容的,sint32和sint64是兼容的,string和bytes是兼容的,fixed32和sfixed32,以及fixed64和sfixed64之間是兼容的,這意味着若是想修改原有字段的類型時,爲了保證兼容性,只能將其修改成與其原有類型兼容的類型,不然就將打破新老消息格式的兼容性。
      5. optional和repeated限定符也是相互兼容的。
優化

2.7Packages
      咱們能夠在.proto文件中定義包名,如:
      package ourproject.lyphone;
      該包名在生成對應的C++文件時,將被替換爲名字空間名稱,既namespace ourproject { namespace lyphone。而在生成的Java代碼文件中將成爲包名。

2.8 Options
      Protocol Buffer容許咱們在.proto文件中定義一些經常使用的選項,這樣能夠指示Protocol Buffer編譯器幫助咱們生成更爲匹配的目標語言代碼。Protocol Buffer內置的選項被分爲如下三個級別:
      1. 文件級別,這樣的選項將影響當前文件中定義的全部消息和枚舉。
      2. 消息級別,這樣的選項僅影響某個消息及其包含的全部字段。
      3. 字段級別,這樣的選項僅僅響應與其相關的字段。
      下面將給出一些經常使用的Protocol Buffer選項。
      1. option java_package = "com.companyname.projectname";
      java_package是文件級別的選項,經過指定該選項可讓生成Java代碼的包名爲該選項值,如上例中的Java代碼包名爲com.companyname.projectname。與此同時,生成的Java文件也將會自動存放到指定輸出目錄下的com/companyname/projectname子目錄中。若是沒有指定該選項,Java的包名則爲package關鍵字指定的名稱。該選項對於生成C++代碼毫無影響。
      2. option java_outer_classname = "LYPhoneMessage";
      java_outer_classname是文件級別的選項,主要功能是顯示的指定生成Java代碼的外部類名稱。若是沒有指定該選項,Java代碼的外部類名稱爲當前文件的文件名部分,同時還要將文件名轉換爲駝峯格式,如:my_project.proto,那麼該文件的默認外部類名稱將爲MyProject。該選項對於生成C++代碼毫無影響。
      注:主要是由於Java中要求同一個.java文件中只能包含一個Java外部類或外部接口,而C++則不存在此限制。所以在.proto文件中定義的消息均爲指定外部類的內部類,這樣才能將這些消息生成到同一個Java文件中。在實際的使用中,爲了不老是輸入該外部類限定符,能夠將該外部類靜態引入到當前Java文件中,如:import static com.company.project.LYPhoneMessage.*
      3. option optimize_for = LITE_RUNTIME;
      optimize_for是文件級別的選項,Protocol Buffer定義三種優化級別SPEED/CODE_SIZE/LITE_RUNTIME。缺省狀況下是SPEED。
      SPEED: 表示生成的代碼運行效率高,可是由今生成的代碼編譯後會佔用更多的空間。
      CODE_SIZE: 和SPEED偏偏相反,代碼運行效率較低,可是由今生成的代碼編譯後會佔用更少的空間,一般用於資源有限的平臺,如Mobile。
      LITE_RUNTIME: 生成的代碼執行效率高,同時生成代碼編譯後的所佔用的空間也是很是少。這是以犧牲Protocol Buffer提供的反射功能爲代價的。所以咱們在C++中連接Protocol Buffer庫時僅需連接libprotobuf-lite,而非libprotobuf。在Java中僅需包含protobuf-java-2.4.1-lite.jar,而非protobuf-java-2.4.1.jar。
      注:對於LITE_MESSAGE選項而言,其生成的代碼均將繼承自MessageLite,而非Message。    
      4. [pack = true]: 由於歷史緣由,對於數值型的repeated字段,如int3二、int64等,在編碼時並無獲得很好的優化,然而在新近版本的Protocol Buffer中,可經過添加[pack=true]的字段選項,以通知Protocol Buffer在爲該類型的消息對象編碼時更加高效。如:
      repeated int32 samples = 4 [packed=true]。
      注:該選項僅適用於2.3.0以上的Protocol Buffer。
      5. [default = default_value]: optional類型的字段,若是在序列化時沒有被設置,或者是老版本的消息中根本不存在該字段,那麼在反序列化該類型的消息是,optional的字段將被賦予類型相關的缺省值,如bool被設置爲false,int32被設置爲0。Protocol Buffer也支持自定義的缺省值,如:
      optional int32 result_per_page = 3 [default = 10]。
2.9 命令行編譯工具
      protoc --proto_path=IMPORT_PATH --cpp_out=DST_DIR --java_out=DST_DIR --python_out=DST_DIR path/to/file.proto
      這裏將給出上述命令的參數解釋。
      1. protoc爲Protocol Buffer提供的命令行編譯工具。
      2. --proto_path等同於-I選項,主要用於指定待編譯的.proto消息定義文件所在的目錄,該選項能夠被同時指定多個。
      3. --cpp_out選項表示生成C++代碼,--java_out表示生成Java代碼,--python_out則表示生成Python代碼,其後的目錄爲生成後的代碼所存放的目錄。
      4. path/to/file.proto表示待編譯的消息定義文件。
      注:對於C++而言,經過Protocol Buffer編譯工具,能夠將每一個.proto文件生成出一對.h和.cc的C++代碼文件。生成後的文件能夠直接加載到應用程序所在的工程項目中。如:MyMessage.proto生成的文件爲MyMessage.pb.h和MyMessage.pb.cc。

 

參考資料:http://i.cnblogs.com/PostDone.aspx?postid=4458913&actiontip=%e5%8f%91%e5%b8%83%e6%88%90%e5%8a%9f

相關文章
相關標籤/搜索