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. 標籤數字1和2則表示不一樣的字段在序列化後的二進制數據中的佈局位置。在該例中,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