上一篇博文介紹了一個綜合案例,這篇將詳細介紹protocol buffer。
為什么使用protocol buffer?首先,要使用protocolbuffer得保證maven安裝成功,maven的下載地址:http://maven.apache.org/download.cgi。
1.解壓完之后請將maven的bin目錄配置到你的環境變量當中。
2.請確保你的JAVA_HOME的變量是指向你的JDK的主目錄,如果你的系統變量中沒有JAVA_HOME這一項,請點擊新建添加。
3.打開命令行,輸入“mvn--version”如果輸出正確則表示安裝成功
安裝完maven之后就要進行protocolbuffer的安裝了,下載地址:http://code.google.com/p/protobuf/downloads/list。下載protobuf-2.4.1.zip和protoc-2.4.1-win32.zip兩個包。
1.解壓完成之后有兩種選擇,第一:將protoc-2.4.1-win32中的protoc.exe所在的目錄配置到環境變量當中,第二:將protoc.exe拷貝到c:/windows/system32目錄下,這里推薦第二種做法。
2.將proto.exe文件拷貝到解壓后的protobuf-2.4.1/src目錄中.
3.進入protobuf-2.4.1/java目錄執行mvnpackage命令編輯該包,系統將會在target目錄中生成protobuf-java-2.4.1.jar文件(注意運行時需要聯網,首次安裝可能需要一定的時間)。
4.假設你的數據文件目錄在XXX/data目錄,把上一步生成的jar拷貝到該目錄中即可。
5.進入XXX/protobuf-2.4.1/examples目錄,可以看到addressbook.proto文件,在命令行中執行protoc--java_out=.addressbook.proto命令(特別注意.Addressbook.proto中間的空格,我第一次安裝就因為沒注意而反復失?。?,如果生成com文件夾并且最終生成AddressBookProtos類則說明安裝成功。
6.打開eclipse,選擇windows-->preferences-->java-->InstalledJREs編輯你默認的java源碼包,并將上面所提到的protobuf-java-2.4.1.jar文件添加進去。
以上內容均摘抄與網絡,經驗證可正確安裝Protocol Buffer(語言規范)以下文章摘抄http://www.49028c.com/stephen-liu74/archive/2013/01/02/2841485.html
一.Protobuf 的優點Protobuf 有如 XML,不過它更小、更快、也更簡單。你可以定義自己的數據結構,然后使用代碼生成器生成的代碼來讀寫這個數據結構。你甚至可以在無需重新部署程序的情況下更新數據結構。只需使用 Protobuf 對數據結構進行一次描述,即可利用各種不同語言或從各種不同數據流中對你的結構化數據輕松讀寫。
它有一個非常棒的特性,即“向后”兼容性好,人們不必破壞已部署的、依靠“老”數據格式的程序就可以對數據結構進行升級。這樣您的程序就可以不必擔心因為消息結構的改變而造成的大規模的代碼重構或者遷移的問題。因為添加新的消息中的 field 并不會引起已經發布的程序的任何改變。
Protobuf 語義更清晰,無需類似 XML 解析器的東西(因為 Protobuf 編譯器會將 .proto 文件編譯生成對應的數據訪問類以對 Protobuf 數據進行序列化、反序列化操作)。
使用 Protobuf 無需學習復雜的文檔對象模型,Protobuf 的編程模式比較友好,簡單易學,同時它擁有良好的文檔和示例,對于喜歡簡單事物的人們而言,Protobuf 比其他的技術更加有吸引力。
二、定義第一個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之間,這樣便可以有效的節省編碼后的字節數量。 三、定義第二個(含有枚舉字段)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; } 四、定義第三個(含有嵌套消息字段)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" 五、限定符(required/optional/repeated)的基本規則。 1. 在每個消息中必須至少留有一個required類型的字段。 2. 每個消息中可以包含0個或多個optional類型的字段。 3. repeated表示的字段可以包含0個或多個數據。需要說明的是,這一點有別于C++/Java中的數組,因為后兩者中的數組必須包含至少一個元素。 4. 如果打算在原有消息協議中添加新的字段,同時還要保證老版本的程序能夠正常讀取或寫入,那么對于新添加的字段必須是optional或repeated。道理非常簡單,老版本程序無法讀取或寫入新增的required限定符的字段。 六、類型對照表。
.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 |
七、Protocol Buffer消息升級原則。 在實際的開發中會存在這樣一種應用場景,既消息格式因為某些需求的變化而不得不進行必要的升級,但是有些使用原有消息格式的應用程序暫時又不能被立刻升級,這便要求我們在升級消息格式時要遵守一定的規則,從而可以保證基于新老消息格式的新老程序同時運行。規則如下: 1. 不要修改已經存在字段的標簽號。 2. 任何新添加的字段必須是optional和repeated限定符,否則無法保證新老程序在互相傳遞消息時的消息兼容性。 3. 在原有的消息中,不能移除已經存在的required字段,optional和repeated類型的字段可以被移除,但是他們之前使用的標簽號必須被保留,不能被新的字段重用。 4. int32、uint32、int64、uint64和bool等類型之間是兼容的,sint32和sint64是兼容的,string和bytes是兼容的,fixed32和sfixed32,以及fixed64和sfixed64之間是兼容的,這意味著如果想修改原有字段的類型時,為了保證兼容性,只能將其修改為與其原有類型兼容的類型,否則就將打破新老消息格式的兼容性。 5. optional和repeated限定符也是相互兼容的。 八、Packages。 我們可以在.proto文件中定義包名,如: packageourproject.lyphone; 該包名在生成對應的C++文件時,將被替換為名字空間名稱,既namespace ourproject { namespace lyphone。而在生成的Java代碼文件中將成為包名。 九、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字段,如int32、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]。 十、命令行編譯工具。 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。
新聞熱點
疑難解答