google的protobuf協議研究,以及實現protobuf對php的支持化【一】


protobuf協議研究

  • 什么是protobuf

protocol buffer簡稱protobuf,是google開發的一種語言無關、平台無關、擴展性好的用於通信協議、數據存儲的結構化數據串行化方法。其相關資料文檔都可以在這找到https://developers.google.com/protocol-buffers

附:目前常見的數據序列化方法效率對比見這https://code.google.com/p/thrift-protobuf-compare/wiki/Benchmarking

 

  • protobuf優缺點

優點:相對於xml,json等序列化協議,protobuf更加高效、靈活、簡單,在時間解析和空間壓縮方面比其他大多數協議高效很多;支持大數據量數據傳輸;可以定義自己的數據結構,然后使用代碼生成器生成的代碼來讀寫這個數據結構。你甚至可以在無需重新部署程序的情況下更新數據結構。

缺點:由於protobuf序列化后的是二進制字節的數據,相對於與xml,json來說,視覺上不是那么友好;目前支持僅支持c++,java,python三種語言

 

  • protobuf使用及消息結構

首先定義一個.proto文件,定義你需要的串行化的數據格式,這里定義為Person.proto,表示一個人的信息;

message Person {
  required string name = 1;///sdfsfsdffds
  required int32 id = 2;
  optional string email = 3;

  enum PhoneType {
    MOBILE = 0;
    HOME = 1;
    WORK = 2;
  }
  repeated PhoneType pt = 7;
  message PhoneNumber1 {
    required string aaa = 1;
    optional uint64 bb =2;
  }
  repeated PhoneNumber phone = 4;
  repeated PhoneNumber1 phone1 = 6;
}

如你所見,protobuf文件看起來有類似於c++,什么int,enum,string;

protobuf消息結構

protobuf消息結構都已“message msgname”開頭,其中msgname是你要定義的消息段名,這里為Persson;

接下來一般每一行都是類似於 “修飾詞 數據類型 數據名字 = 序號 [default=默認值]”,

修飾詞有required,optional,repeated三種,required表示該值是必須要傳的,而且只能出現一個;optional該值可以有零個或一個,可以查詢其存在與否;repeated該值相當於一個數組或有序列表,改值可為0個、1個、或多個,可以使用選項packed = true來進行高效的編碼。

數據類型16種,其中基本數據類型15種加上message嵌套共16種;15種數據格式如下;根據需要選擇相應的數據類型

 

 

數據名字就是合法的關鍵字就行了

序號就是該字段在該消息中出現的順序編號,可以從0~2^29-1次方之間(因為每個字段類型都由一個int32的key表示,其中類型字段占后3位,剩下2^29個編號,一般從1往下順序開始,故最大可容納2^29個字段)

[default=默認值]表示可以規定默認取值,該屬性只對optional修飾的字段有效,表示如果沒使用此字段但是規定了默認值,那么序列化的時候就按默認值序列化

 

  • protobuf序列化原理

每一個消息字段序列化可以歸結為key:value的配對,key可以唯一確定該字段類型,序號;value可以確定其值

按照 “修飾詞 數據類型 數據名字 = 序號 [default=默認值]”格式定義來分析其key和value構成

key的構成: 由一個int32的4字節碼表示;其公式為:(序號 << 3) | 字段歸一類型;由varint編碼序列化;字段歸一類型如下所示

 

 

字段歸一類型由3位表示,最多能表示8種類型,由上圖知目前已使用6種類型(其中3,4已經在2.0以后棄用,其他不變):注意:在實際實現過程中,repeated fields字段並不是按照2算的,是按照其數據類型算的

 如optional int32 test = 1; 反序列化之后是:0x08=0000 1000

 如optional string test1 = 2; 反序列化之后是:0x12=0001 0010

value的序列化:采用little endian的存儲方式

 

 Base 128 Varints編碼

varint編碼使用一個字節或多個字節來表示一個整數,是邊長字節編碼,數越小,所以字節越少;每個字節都是varint類型,除了最后一個字節因為最有一個字節有空位;每個字節的低7位來存數據,最高位來表示前后兩個字節是否有關聯,為1表示后面的字節的數據和當前字節數據屬於一個整體;

對於正數編碼:如300,其二進制是100101100;其varint編碼是1010 1100 0000 0010<= 010 1100 + 000 0010 <= 0010 1100 + 0000 0001 litter endian<= 1 + 0010 1100

對於負數編碼,統一采用10個字節表示(最多能表示10*7=70位)32位和64位在最后一個字節上表現有所不同,對於32位的負數最后一個字節采用0x01填充,64采用0x0f填充

如-4,二進制補碼為0xfffffffb;采用32位vartint編碼為:0xfbffffffffffffffff01;采用64位編碼為:0xfbffffffffffffffff0f

綜上:varint適合於正整數編碼,對於負數編碼浪費空間

 

Signed Integers的ZigZag編碼

對於有符號整數,采用ZigZag編碼;其編碼轉換如下圖

 

 

即是:正數的2倍,負數的絕對值2倍-1;按照可得其公式:對於32位(n <<1)^(n >>31),相應的解碼公式是(n>>1)^(n&0x01);對於64位(n<<1)^(n>>63),相應的解碼公式是(n>>1)^(n&0x01);

在protobuf協議中,對sint32,sint64位先采用zigzag編碼, 然后在使用varint編碼得到

相對int32,int64來説,負數能節省空間;

 

固定字節編碼(fixed編碼自己取名)

采用固定4個字節:fixed32,sfixed32,float

采用固定8個字節:fixed64,sfixed64,double

 

無符號整數usint編碼

sint32采用varint編碼,所不同的是,對於32位采用5個字節最后一個字節使用0x0f,對於64位采用10個字節,最后一個自己使用0x01

相對於純int32來説,負數能節省空間;

 

string,bytes編碼(官方手冊上把repeated,embeded message也歸為這一類,個人在2.5上實現經過實踐,不是這樣)

如repeated string test = 2;test被初始化為"testing";那么其序列化后的值是12 07 74 65 73 74 69 6e 67;其中前第一個字節就是key的編碼;第二個字節是字符串長度的varint編碼;后面7個字節就是字符串的ascii

 

  • protobuf使用實例及數據結構分析

先去google開源項目上下載最新的protobuf編譯器https://code.google.com/p/protobuf/downloads/list

使用如下命令把自己的.proto文件編譯生成對應的語言源文件

protoc --proto_path=IMPORT_PATH --cpp_out=DST_DIR --java_out=DST_DIR --python_out=DST_DIR path/to/file.proto

IMPORT_PATH 指定要編譯的.proto文件目錄,默認當前路勁為指定路徑;可以使用-I=IMPORT_PATH來代替--proto_path=IMPORT_PATH;由於package指定的文件可以在多個地方,故--proto_path可以使用多次來指定多個目錄;

生成的源文件有3種選擇--cpp_out表示生成c++源文件;--java_out表示生成java源文件;--python_out表示生成python源文件;后面跟着生成文件存放的目錄

最后一個參數表示要編譯哪個.proto文件

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM