該系列Blog的內(nèi)容主體主要源自于PRotocol Buffer的官方文檔,而代碼示例則抽取于當(dāng)前正在開(kāi)發(fā)的一個(gè)公司內(nèi)部項(xiàng)目的Demo。這樣做的目的主要在于不僅可以保持Google文檔的良好風(fēng)格和系統(tǒng)性,同時(shí)再結(jié)合一些比較實(shí)用和通用的用例,這樣就更加便于公司內(nèi)部的培訓(xùn),以及和廣大網(wǎng)友的技術(shù)交流。需要說(shuō)明的是,Blog的內(nèi)容并非line by line的翻譯,其中包含一些經(jīng)驗(yàn)性總結(jié),與此同時(shí),對(duì)于一些不是非常常用的功能并未予以說(shuō)明,有興趣的開(kāi)發(fā)者可以直接查閱Google的官方文檔。 一、為什么使用Protocol Buffer? 在回答這個(gè)問(wèn)題之前,我們還是先給出一個(gè)在實(shí)際開(kāi)發(fā)中經(jīng)常會(huì)遇到的系統(tǒng)場(chǎng)景。比如:我們的客戶端程序是使用java開(kāi)發(fā)的,可能運(yùn)行自不同的平臺(tái),如:linux、Windows或者是Android,而我們的服務(wù)器程序通常是基于Linux平臺(tái)并使用C++開(kāi)發(fā)完成的。在這兩種程序之間進(jìn)行數(shù)據(jù)通訊時(shí)存在多種方式用于設(shè)計(jì)消息格式,如: 1. 直接傳遞C/C++語(yǔ)言中一字節(jié)對(duì)齊的結(jié)構(gòu)體數(shù)據(jù),只要結(jié)構(gòu)體的聲明為定長(zhǎng)格式,那么該方式對(duì)于C/C++程序而言就非常方便了,僅需將接收到的數(shù)據(jù)按照結(jié)構(gòu)體類(lèi)型強(qiáng)行轉(zhuǎn)換即可。事實(shí)上對(duì)于變長(zhǎng)結(jié)構(gòu)體也不會(huì)非常麻煩。在發(fā)送數(shù)據(jù)時(shí),也只需定義一個(gè)結(jié)構(gòu)體變量并設(shè)置各個(gè)成員變量的值之后,再以char*的方式將該二進(jìn)制數(shù)據(jù)發(fā)送到遠(yuǎn)端。反之,該方式對(duì)于Java開(kāi)發(fā)者而言就會(huì)非常繁瑣,首先需要將接收到的數(shù)據(jù)存于ByteBuffer之中,再根據(jù)約定的字節(jié)序逐個(gè)讀取每個(gè)字段,并將讀取后的值再賦值給另外一個(gè)值對(duì)象中的域變量,以便于程序中其他代碼邏輯的編寫(xiě)。對(duì)于該類(lèi)型程序而言,聯(lián)調(diào)的基準(zhǔn)是必須客戶端和服務(wù)器雙方均完成了消息報(bào)文構(gòu)建程序的編寫(xiě)后才能展開(kāi),而該設(shè)計(jì)方式將會(huì)直接導(dǎo)致Java程序開(kāi)發(fā)的進(jìn)度過(guò)慢。即便是Debug階段,也會(huì)經(jīng)常遇到Java程序中出現(xiàn)各種域字段拼接的小錯(cuò)誤。 2. 使用SOAP協(xié)議(WebService)作為消息報(bào)文的格式載體,由該方式生成的報(bào)文是基于文本格式的,同時(shí)還存在大量的xml描述信息,因此將會(huì)大大增加網(wǎng)絡(luò)IO的負(fù)擔(dān)。又由于XML解析的復(fù)雜性,這也會(huì)大幅降低報(bào)文解析的性能。總之,使用該設(shè)計(jì)方式將會(huì)使系統(tǒng)的整體運(yùn)行性能明顯下降。 對(duì)于以上兩種方式所產(chǎn)生的問(wèn)題,Protocol Buffer均可以很好的解決,不僅如此,Protocol Buffer還有一個(gè)非常重要的優(yōu)點(diǎn)就是可以保證同一消息報(bào)文新舊版本之間的兼容性。至于具體的方式我們將會(huì)在后續(xù)的博客中給出。 二、定義第一個(gè)Protocol Buffer消息。 創(chuàng)建擴(kuò)展名為.proto的文件,如:MyMessage.proto,并將以下內(nèi)容存入該文件中。 message LogonReqMessage { required int64 acctID = 1; required string passwd = 2; } 這里將給出以上消息定義的關(guān)鍵性說(shuō)明。 1. message是消息定義的關(guān)鍵字,等同于C++中的struct/class,或是Java中的class。 2. LogonReqMessage為消息的名字,等同于結(jié)構(gòu)體名或類(lèi)名。 3. required前綴表示該字段為必要字段,既在序列化和反序列化之前該字段必須已經(jīng)被賦值。與此同時(shí),在Protocol Buffer中還存在另外兩個(gè)類(lèi)似的關(guān)鍵字,optional和repeated,帶有這兩種限定符的消息字段則沒(méi)有required字段這樣的限制。相比于optional,repeated主要用于表示數(shù)組字段。具體的使用方式在后面的用例中均會(huì)一一列出。 4. int64和string分別表示長(zhǎng)整型和字符串型的消息字段,在Protocol Buffer中存在一張類(lèi)型對(duì)照表,既Protocol Buffer中的數(shù)據(jù)類(lèi)型與其他編程語(yǔ)言(C++/Java)中所用類(lèi)型的對(duì)照。該對(duì)照表中還將給出在不同的數(shù)據(jù)場(chǎng)景下,哪種類(lèi)型更為高效。該對(duì)照表將在后面給出。 5. acctID和passwd分別表示消息字段名,等同于Java中的域變量名,或是C++中的成員變量名。 6. 標(biāo)簽數(shù)字1和2則表示不同的字段在序列化后的二進(jìn)制數(shù)據(jù)中的布局位置。在該例中,passwd字段編碼后的數(shù)據(jù)一定位于acctID之后。需要注意的是該值在同一message中不能重復(fù)。另外,對(duì)于Protocol Buffer而言,標(biāo)簽值為1到15的字段在編碼時(shí)可以得到優(yōu)化,既標(biāo)簽值和類(lèi)型信息僅占有一個(gè)byte,標(biāo)簽范圍是16到2047的將占有兩個(gè)bytes,而Protocol Buffer可以支持的字段數(shù)量則為2的29次方減一。有鑒于此,我們?cè)谠O(shè)計(jì)消息結(jié)構(gòu)時(shí),可以盡可能考慮讓repeated類(lèi)型的字段標(biāo)簽位于1到15之間,這樣便可以有效的節(jié)省編碼后的字節(jié)數(shù)量。 三、定義第二個(gè)(含有枚舉字段)Protocol Buffer消息。 //在定義Protocol Buffer的消息時(shí),可以使用和C++/Java代碼同樣的方式添加注釋。 enum UserStatus { OFFLINE = 0; //表示處于離線狀態(tài)的用戶 ONLINE = 1; //表示處于在線狀態(tài)的用戶 } message UserInfo { required int64 acctID = 1; required string name = 2; required UserStatus status = 3; } 這里將給出以上消息定義的關(guān)鍵性說(shuō)明(僅包括上一小節(jié)中沒(méi)有描述的)。 1. enum是枚舉類(lèi)型定義的關(guān)鍵字,等同于C++/Java中的enum。 2. UserStatus為枚舉的名字。 3. 和C++/Java中的枚舉不同的是,枚舉值之間的分隔符是分號(hào),而不是逗號(hào)。 4. OFFLINE/ONLINE為枚舉值。 5. 0和1表示枚舉值所對(duì)應(yīng)的實(shí)際整型值,和C/C++一樣,可以為枚舉值指定任意整型值,而無(wú)需總是從0開(kāi)始定義。如: 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; } 四、定義第三個(gè)(含有嵌套消息字段)Protocol Buffer消息。 我們可以在同一個(gè).proto文件中定義多個(gè)message,這樣便可以很容易的實(shí)現(xiàn)嵌套消息的定義。如: 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; } 這里將給出以上消息定義的關(guān)鍵性說(shuō)明(僅包括上兩小節(jié)中沒(méi)有描述的)。 1. LogonRespMessage消息的定義中包含另外一個(gè)消息類(lèi)型作為其字段,如UserInfo userInfo。 2. 上例中的UserInfo和LogonRespMessage被定義在同一個(gè).proto文件中,那么我們是否可以包含在其他.proto文件中定義的message呢?Protocol Buffer提供了另外一個(gè)關(guān)鍵字import,這樣我們便可以將很多通用的message定義在同一個(gè).proto文件中,而其他消息定義文件可以通過(guò)import的方式將該文件中定義的消息包含進(jìn)來(lái),如: import "myproject/CommonMessages.proto" 五、限定符(required/optional/repeated)的基本規(guī)則。 1. 在每個(gè)消息中必須至少留有一個(gè)required類(lèi)型的字段。 2. 每個(gè)消息中可以包含0個(gè)或多個(gè)optional類(lèi)型的字段。 3. repeated表示的字段可以包含0個(gè)或多個(gè)數(shù)據(jù)。需要說(shuō)明的是,這一點(diǎn)有別于C++/Java中的數(shù)組,因?yàn)楹髢烧咧械臄?shù)組必須包含至少一個(gè)元素。 4. 如果打算在原有消息協(xié)議中添加新的字段,同時(shí)還要保證老版本的程序能夠正常讀取或?qū)懭耄敲磳?duì)于新添加的字段必須是optional或repeated。道理非常簡(jiǎn)單,老版本程序無(wú)法讀取或?qū)懭胄略龅膔equired限定符的字段。 六、類(lèi)型對(duì)照表。
.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消息升級(jí)原則。 在實(shí)際的開(kāi)發(fā)中會(huì)存在這樣一種應(yīng)用場(chǎng)景,既消息格式因?yàn)槟承┬枨蟮淖兓坏貌贿M(jìn)行必要的升級(jí),但是有些使用原有消息格式的應(yīng)用程序暫時(shí)又不能被立刻升級(jí),這便要求我們?cè)谏?jí)消息格式時(shí)要遵守一定的規(guī)則,從而可以保證基于新老消息格式的新老程序同時(shí)運(yùn)行。規(guī)則如下: 1. 不要修改已經(jīng)存在字段的標(biāo)簽號(hào)。 2. 任何新添加的字段必須是optional和repeated限定符,否則無(wú)法保證新老程序在互相傳遞消息時(shí)的消息兼容性。 3. 在原有的消息中,不能移除已經(jīng)存在的required字段,optional和repeated類(lèi)型的字段可以被移除,但是他們之前使用的標(biāo)簽號(hào)必須被保留,不能被新的字段重用。 4. int32、uint32、int64、uint64和bool等類(lèi)型之間是兼容的,sint32和sint64是兼容的,string和bytes是兼容的,fixed32和sfixed32,以及fixed64和sfixed64之間是兼容的,這意味著如果想修改原有字段的類(lèi)型時(shí),為了保證兼容性,只能將其修改為與其原有類(lèi)型兼容的類(lèi)型,否則就將打破新老消息格式的兼容性。 5. optional和repeated限定符也是相互兼容的。 八、Packages。 我們可以在.proto文件中定義包名,如: package ourproject.lyphone; 該包名在生成對(duì)應(yīng)的C++文件時(shí),將被替換為名字空間名稱(chēng),既namespace ourproject { namespace lyphone。而在生成的Java代碼文件中將成為包名。 九、Options。 Protocol Buffer允許我們?cè)?proto文件中定義一些常用的選項(xiàng),這樣可以指示Protocol Buffer編譯器幫助我們生成更為匹配的目標(biāo)語(yǔ)言代碼。Protocol Buffer內(nèi)置的選項(xiàng)被分為以下三個(gè)級(jí)別: 1. 文件級(jí)別,這樣的選項(xiàng)將影響當(dāng)前文件中定義的所有消息和枚舉。 2. 消息級(jí)別,這樣的選項(xiàng)僅影響某個(gè)消息及其包含的所有字段。 3. 字段級(jí)別,這樣的選項(xiàng)僅僅響應(yīng)與其相關(guān)的字段。 下面將給出一些常用的Protocol Buffer選項(xiàng)。 1. option java_package = "com.companyname.projectname"; java_package是文件級(jí)別的選項(xiàng),通過(guò)指定該選項(xiàng)可以讓生成Java代碼的包名為該選項(xiàng)值,如上例中的Java代碼包名為com.companyname.projectname。與此同時(shí),生成的Java文件也將會(huì)自動(dòng)存放到指定輸出目錄下的com/companyname/projectname子目錄中。如果沒(méi)有指定該選項(xiàng),Java的包名則為package關(guān)鍵字指定的名稱(chēng)。該選項(xiàng)對(duì)于生成C++代碼毫無(wú)影響。 2. option java_outer_classname = "LYPhoneMessage"; java_outer_classname是文件級(jí)別的選項(xiàng),主要功能是顯示的指定生成Java代碼的外部類(lèi)名稱(chēng)。如果沒(méi)有指定該選項(xiàng),Java代碼的外部類(lèi)名稱(chēng)為當(dāng)前文件的文件名部分,同時(shí)還要將文件名轉(zhuǎn)換為駝峰格式,如:my_project.proto,那么該文件的默認(rèn)外部類(lèi)名稱(chēng)將為MyProject。該選項(xiàng)對(duì)于生成C++代碼毫無(wú)影響。 注:主要是因?yàn)镴ava中要求同一個(gè).java文件中只能包含一個(gè)Java外部類(lèi)或外部接口,而C++則不存在此限制。因此在.proto文件中定義的消息均為指定外部類(lèi)的內(nèi)部類(lèi),這樣才能將這些消息生成到同一個(gè)Java文件中。在實(shí)際的使用中,為了避免總是輸入該外部類(lèi)限定符,可以將該外部類(lèi)靜態(tài)引入到當(dāng)前Java文件中,如:import static com.company.project.LYPhoneMessage.*。 3. option optimize_for = LITE_RUNTIME; optimize_for是文件級(jí)別的選項(xiàng),Protocol Buffer定義三種優(yōu)化級(jí)別SPEED/CODE_SIZE/LITE_RUNTIME。缺省情況下是SPEED。 SPEED: 表示生成的代碼運(yùn)行效率高,但是由此生成的代碼編譯后會(huì)占用更多的空間。 CODE_SIZE: 和SPEED恰恰相反,代碼運(yùn)行效率較低,但是由此生成的代碼編譯后會(huì)占用更少的空間,通常用于資源有限的平臺(tái),如Mobile。 LITE_RUNTIME: 生成的代碼執(zhí)行效率高,同時(shí)生成代碼編譯后的所占用的空間也是非常少。這是以犧牲Protocol Buffer提供的反射功能為代價(jià)的。因此我們?cè)贑++中鏈接Protocol Buffer庫(kù)時(shí)僅需鏈接libprotobuf-lite,而非libprotobuf。在Java中僅需包含protobuf-java-2.4.1-lite.jar,而非protobuf-java-2.4.1.jar。 注:對(duì)于LITE_MESSAGE選項(xiàng)而言,其生成的代碼均將繼承自MessageLite,而非Message。 4. [pack = true]: 因?yàn)闅v史原因,對(duì)于數(shù)值型的repeated字段,如int32、int64等,在編碼時(shí)并沒(méi)有得到很好的優(yōu)化,然而在新近版本的Protocol Buffer中,可通過(guò)添加[pack=true]的字段選項(xiàng),以通知Protocol Buffer在為該類(lèi)型的消息對(duì)象編碼時(shí)更加高效。如: repeated int32 samples = 4 [packed=true]。 注:該選項(xiàng)僅適用于2.3.0以上的Protocol Buffer。 5. [default = default_value]: optional類(lèi)型的字段,如果在序列化時(shí)沒(méi)有被設(shè)置,或者是老版本的消息中根本不存在該字段,那么在反序列化該類(lèi)型的消息是,optional的字段將被賦予類(lèi)型相關(guān)的缺省值,如bool被設(shè)置為false,int32被設(shè)置為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 這里將給出上述命令的參數(shù)解釋。 1. protoc為Protocol Buffer提供的命令行編譯工具。 2. --proto_path等同于-I選項(xiàng),主要用于指定待編譯的.proto消息定義文件所在的目錄,該選項(xiàng)可以被同時(shí)指定多個(gè)。 3. --cpp_out選項(xiàng)表示生成C++代碼,--java_out表示生成Java代碼,--python_out則表示生成Python代碼,其后的目錄為生成后的代碼所存放的目錄。 4. path/to/file.proto表示待編譯的消息定義文件。
注:對(duì)于C++而言,通過(guò)Protocol Buffer編譯工具,可以將每個(gè).proto文件生成出一對(duì).h和.cc的C++代碼文件。生成后的文件可以直接加載到應(yīng)用程序所在的工程項(xiàng)目中。如:MyMessage.proto生成的文件為MyMessage.pb.h和MyMessage.pb.cc。
http://www.cnblogs.com/stephen-liu74/archive/2013/01/02/2841485.html
這篇Blog仍然是以Google的官方文檔為主線,代碼實(shí)例則完全取自于我們正在開(kāi)發(fā)的一個(gè)Demo項(xiàng)目,通過(guò)前一段時(shí)間的嘗試,感覺(jué)這種結(jié)合的方式比較有利于培訓(xùn)和內(nèi)部的技術(shù)交流。還是那句話,沒(méi)有最好的,只有最適合的。我想寫(xiě)B(tài)log也是這一道理吧,不同的技術(shù)主題可能需要采用不同的風(fēng)格。好了,還是讓我們盡早切入主題吧。 一、生成目標(biāo)語(yǔ)言代碼。 下面的命令幫助我們將MyMessage.proto文件中定義的一組Protocol Buffer格式的消息編譯成目標(biāo)語(yǔ)言(C++)的代碼。至于消息的內(nèi)容,我們會(huì)在后面以分段的形式逐一列出,同時(shí)也會(huì)在附件中給出所有源代碼。 protoc -I=./message --cpp_out=./src ./MyMessage.proto 從上面的命令行參數(shù)中可以看出,待編譯的文件為MyMessage.proto,他存放在當(dāng)前目錄的message子目錄下。--cpp_out參數(shù)則指示編譯工具我們需要生成目標(biāo)語(yǔ)言是C++,輸出目錄是當(dāng)前目錄的src子目錄。在本例中,生成的目標(biāo)代碼文件名是MyMessage.pb.h和MyMessage.pb.cc。 二、簡(jiǎn)單message生成的C++代碼。 這里先定義一個(gè)最簡(jiǎn)單的message,其中只是包含原始類(lèi)型的字段。 option optimize_for = LITE_RUNTIME; message LogonReqMessage { required int64 acctID = 1; required string passwd = 2; } 由于我們?cè)贛yMessage文件中定義選項(xiàng)optimize_for的值為L(zhǎng)ITE_RUNTIME,因此由該.proto文件生成的所有C++類(lèi)的父類(lèi)均為::google::protobuf::MessageLite,而非::google::protobuf::Message。在上一篇博客中已經(jīng)給出了一些簡(jiǎn)要的說(shuō)明,MessageLite類(lèi)是Message的父類(lèi),在MessageLite中將缺少Protocol Buffer對(duì)反射的支持,而此類(lèi)功能均在Message類(lèi)中提供了具體的實(shí)現(xiàn)。對(duì)于我們的項(xiàng)目而言,整個(gè)系統(tǒng)相對(duì)比較封閉,不會(huì)和更多的外部程序進(jìn)行交互,與此同時(shí),我們的客戶端部分又是運(yùn)行在Android平臺(tái),有鑒于此,我們考慮使用LITE版本的Protocol Buffer。這樣不僅可以得到更高編碼效率,而且生成代碼編譯后所占用的資源也會(huì)更少,至于反射所能帶來(lái)的靈活性和極易擴(kuò)展性,對(duì)于該項(xiàng)目而言完全可以忽略。下面我們來(lái)看一下由message LogonReqMessage生成的C++類(lèi)的部分聲明,以及常用方法的說(shuō)明性注釋。
1 class LogonReqMessage : public ::google::protobuf::MessageLite { 2 public: 3 LogonReqMessage(); 4 virtual ~LogonReqMessage(); 5 6 // implements Message ---------------------------------------------- 7 //下面的成員函數(shù)均實(shí)現(xiàn)自MessageLite中的虛函數(shù)。 8 //創(chuàng)建一個(gè)新的LogonReqMessage對(duì)象,等同于clone。 9 LogonReqMessage* New() const;10 //用另外一個(gè)LogonReqMessage對(duì)象初始化當(dāng)前對(duì)象,等同于賦值操作符重載(operator=)11 void CopyFrom(const LogonReqMessage& from);12 //清空當(dāng)前對(duì)象中的所有數(shù)據(jù),既將所有成員變量置為未初始化狀態(tài)。13 void Clear();14 //判斷當(dāng)前狀態(tài)是否已經(jīng)初始化。15 bool IsInitialized() const;16 //在給當(dāng)前對(duì)象的所有變量賦值之后,獲取該對(duì)象序列化后所需要的字節(jié)數(shù)。17 int ByteSize() const;18 //獲取當(dāng)前對(duì)象的類(lèi)型名稱(chēng)。19 ::std::string GetTypeName() const;20 21 // required int64 acctID = 1;22 //下面的成員函數(shù)都是因message中定義的acctID字段而生成。23 //這個(gè)靜態(tài)成員表示AcctID的標(biāo)簽值。命名規(guī)則是k + FieldName(駝峰規(guī)則) + FieldNumber。24 static const int kAcctIDFieldNumber = 1;25 //如果acctID字段已經(jīng)被設(shè)置返回true,否則false。26 inline bool has_acctid() const;27 //執(zhí)行該函數(shù)后has_acctid函數(shù)將返回false,而下面的acctid函數(shù)則返回acctID的缺省值。28 inline void clear_acctid();29 //返回acctid字段的當(dāng)前值,如果沒(méi)有設(shè)置則返回int64類(lèi)型的缺省值。30 inline ::google::protobuf::int64 acctid() const;31 //為acctid字段設(shè)置新值,調(diào)用該函數(shù)后has_acctid函數(shù)將返回true。32 inline void set_acctid(::google::protobuf::int64 value);33 34 // required string passwd = 2;35 //下面的成員函數(shù)都是因message中定義的passwd字段而生成。這里生成的函數(shù)和上面acctid36 //生成的那組函數(shù)基本相似。因此這里只是列出差異部分。37 static const int kPasswdFieldNumber = 2;38 inline bool has_passwd() const;39 inline void clear_passwd();40 inline const ::std::string& passwd() const;41 inline void set_passwd(const ::std::string& value);42 //對(duì)于字符串類(lèi)型字段設(shè)置const char*類(lèi)型的變量值。43 inline void set_passwd(const char* value);44 inline void set_passwd(const char* value, size_t size);45 //可以通過(guò)返回值直接給passwd對(duì)象賦值。在調(diào)用該函數(shù)之后has_passwd將返回true。46 inline ::std::string* mutable_passwd();47 //釋放當(dāng)前對(duì)象對(duì)passwd字段的所有權(quán),同時(shí)返回passwd字段對(duì)象指針。調(diào)用此函數(shù)之后,passwd字段對(duì)象48 //的所有權(quán)將移交給調(diào)用者。此后再調(diào)用has_passwd函數(shù)時(shí)將返回false。49 inline ::std::string* release_passwd();50 private:51 ... ... 52 };下面是讀寫(xiě)LogonReqMessage對(duì)象的C++測(cè)試代碼和說(shuō)明性注釋。
1 void testSimpleMessage() 2 { 3 printf("==================This is simple message.================/n"); 4 //序列化LogonReqMessage對(duì)象到指定的內(nèi)存區(qū)域。 5 LogonReqMessage logonReq; 6 logonReq.set_acctid(20); 7 logonReq.set_passwd("Hello World"); 8 //提前獲取對(duì)象序列化所占用的空間并進(jìn)行一次性分配,從而避免多次分配 9 //而造成的性能開(kāi)銷(xiāo)。通過(guò)該種方式,還可以將序列化后的數(shù)據(jù)進(jìn)行加密。10 //之后再進(jìn)行持久化,或是發(fā)送到遠(yuǎn)端。11 int length = logonReq.ByteSize();12 char* buf = new char[length];13 logonReq.SerializeToArray(buf,length);14 //從內(nèi)存中讀取并反序列化LogonReqMessage對(duì)象,同時(shí)將結(jié)果打印出來(lái)。15 LogonReqMessage logonReq2;16 logonReq2.ParseFromArray(buf,length);17 printf("acctID = %I64d, passWord = %s/n",logonReq2.acctid(),logonReq2.passwd().c_str());18 delete [] buf;19 }三、嵌套message生成的C++代碼。 enum UserStatus { OFFLINE = 0; ONLINE = 1; } enum LoginResult { LOGON_RESULT_SUCCESS = 0; LOGON_RESULT_NOTEXIST = 1; LOGON_RESULT_ERROR_PASSWD = 2; LOGON_RESULT_ALREADY_LOGON = 3; LOGON_RESULT_SERVER_ERROR = 4; } message UserInfo { required int64 acctID = 1; required string name = 2; required UserStatus status = 3; } message LogonRespMessage { required LoginResult logonResult = 1; required UserInfo userInfo = 2; //這里嵌套了UserInfo消息。 } 對(duì)于上述消息生成的C++代碼,UserInfo因?yàn)橹皇前嗽碱?lèi)型字段,因此和上例中的LogonReqMessage沒(méi)有太多的差別,這里也就不在重復(fù)列出了。由于LogonRespMessage消息中嵌套了UserInfo類(lèi)型的字段,在這里我們將僅僅給出該消息生成的C++代碼和關(guān)鍵性注釋。
1 class LogonRespMessage : public ::google::protobuf::MessageLite { 2 public: 3 LogonRespMessage(); 4 virtual ~LogonRespMessage(); 5 6 // implements Message ---------------------------------------------- 7 ... ... //這部分函數(shù)和之前的例子一樣。 8 9 // required .LoginResult logonResult = 1;10 //下面的成員函數(shù)都是因message中定義的logonResult字段而生成。11 //這一點(diǎn)和前面的例子基本相同,只是類(lèi)型換做了枚舉類(lèi)型LoginResult。 12 static const int kLogonResultFieldNumber = 1;13 inline bool has_logonresult() const;14 inline void clear_logonresult();15 inline LoginResult logonresult() const;16 inline void set_logonresult(LoginResult value);17 18 // required .UserInfo userInfo = 2;19 //下面的成員函數(shù)都是因message中定義的UserInfo字段而生成。20 //這里只是列出和非消息類(lèi)型字段差異的部分。21 static const int kUserInfoFieldNumber = 2;22 inline bool has_userinfo() const;23 inline void clear_userinfo();24 inline const ::UserInfo& userinfo() const;25 //可以看到該類(lèi)并沒(méi)有生成用于設(shè)置和修改userInfo字段set_userinfo函數(shù),而是將該工作26 //交給了下面的mutable_userinfo函數(shù)。因此每當(dāng)調(diào)用函數(shù)之后,Protocol Buffer都會(huì)認(rèn)為27 //該字段的值已經(jīng)被設(shè)置了,同時(shí)has_userinfo函數(shù)亦將返回true。在實(shí)際編碼中,我們可以28 //通過(guò)該函數(shù)返回userInfo字段的內(nèi)部指針,并基于該指針完成userInfo成員變量的初始化工作。29 inline ::UserInfo* mutable_userinfo();30 inline ::UserInfo* release_userinfo();31 private:32 ... ...33 };下面是讀寫(xiě)LogonRespMessage對(duì)象的C++測(cè)試代碼和說(shuō)明性注釋。
1 void testNestedMessage() 2 { 3 printf("==================This is nested message.================/n"); 4 LogonRespMessage logonResp; 5 logonResp.set_logonresult(LOGON_RESULT_SUCCESS); 6 //如上所述,通過(guò)mutable_userinfo函數(shù)返回userInfo字段的指針,之后再初始化該對(duì)象指針。 7 UserInfo* userInfo = logonResp.mutable_userinfo(); 8 userInfo->set_acctid(200); 9 userInfo->set_name("Tester");10 userInfo->set_status(OFFLINE);11 int length = logonResp.ByteSize();12 char* buf = new char[length];13 logonResp.SerializeToArray(buf,length);14 15 LogonRespMessage logonResp2;16 logonResp2.ParseFromArray(buf,length);17 printf("LogonResult = %d, UserInfo->acctID = %I64d, UserInfo->name = %s, UserInfo->status = %d/n"18 ,logonResp2.logonresult(),logonResp2.userinfo().acctid(),logonResp2.userinfo().name().c_str(),logonResp2.userinfo().status());19 delete [] buf;20 }四、repeated嵌套message生成的C++代碼。 message BuddyInfo { required UserInfo userInfo = 1; required int32 groupID = 2; } message RetrieveBuddiesResp { required int32 buddiesCnt = 1; repeated BuddyInfo buddiesInfo = 2; } 對(duì)于上述消息生成的代碼,我們將只是針對(duì)RetrieveBuddiesResp消息所對(duì)應(yīng)的C++代碼進(jìn)行詳細(xì)說(shuō)明,其余部分和前面小節(jié)的例子基本相同,可直接參照。而對(duì)于RetrieveBuddiesResp類(lèi)中的代碼,我們也僅僅是對(duì)buddiesInfo字段生成的代碼進(jìn)行更為詳細(xì)的解釋。
1 class RetrieveBuddiesResp : public ::google::protobuf::MessageLite { 2 public: 3 RetrieveBuddiesResp(); 4 virtual ~RetrieveBuddiesResp(); 5 6 ... ... //其余代碼的功能性注釋均可參照前面的例子。 7 8 // repeated .BuddyInfo buddiesInfo = 2; 9 static const int kBuddiesInfoFieldNumber = 2;10 //返回?cái)?shù)組中成員的數(shù)量。11 inline int buddiesinfo_size() const;12 //清空數(shù)組中的所有已初始化成員,調(diào)用該函數(shù)后,buddiesinfo_size函數(shù)將返回0。13 inline void clear_buddiesinfo();14 //返回?cái)?shù)組中指定下標(biāo)所包含元素的引用。15 inline const ::BuddyInfo& buddiesinfo(int index) const;16 //返回?cái)?shù)組中指定下標(biāo)所包含元素的指針,通過(guò)該方式可直接修改元素的值信息。17 inline ::BuddyInfo* mutable_buddiesinfo(int index);18 //像數(shù)組中添加一個(gè)新元素。返回值即為新增的元素,可直接對(duì)其進(jìn)行初始化。19 inline ::BuddyInfo* add_buddiesinfo();20 //獲取buddiesInfo字段所表示的容器,該函數(shù)返回的容器僅用于遍歷并讀取,不能直接修改。21 inline const ::google::protobuf::RepeatedPtrField< ::BuddyInfo >&22 buddiesinfo() const;23 //獲取buddiesInfo字段所表示的容器指針,該函數(shù)返回的容器指針可用于遍歷和直接修改。24 inline ::google::protobuf::RepeatedPtrField< ::BuddyInfo >*25 mutable_buddiesinfo();26 private:27 ... ...28 };下面是讀寫(xiě)RetrieveBuddiesResp對(duì)象的C++測(cè)試代碼和說(shuō)明性注釋。
1 void testRepeatedMessage() 2 { 3 printf("==================This is repeated message.================/n"); 4 RetrieveBuddiesResp retrieveResp; 5 retrieveResp.set_buddiescnt(2); 6 BuddyInfo* buddyInfo = retrieveResp.add_buddiesinfo(); 7 buddyInfo->set_groupid(20); 8 UserInfo* userInfo = buddyInfo->mutable_userinfo(); 9 userInfo->set_acctid(200);10 userInfo->set_name("user1");11 userInfo->set_status(OFFLINE);12 13 buddyInfo = retrieveResp.add_buddiesinfo();14 buddyInfo->set_groupid(21);15 userInfo = buddyInfo->mutable_userinfo();16 userInfo->set_acctid(201);17 userInfo->set_name("user2");18 userInfo->set_status(ONLINE);19 20 int length = retrieveResp.ByteSize();21 char* buf = new char[length];22 retrieveResp.SerializeToArray(buf,length);23 24 RetrieveBuddiesResp retrieveResp2;25 retrieveResp2.ParseFromArray(buf,length);26 printf("BuddiesCount = %d/n",retrieveResp2.buddiescnt());27 printf("Repeated Size = %d/n",retrieveResp2.buddiesinfo_size());28 //這里僅提供了通過(guò)容器迭代器的方式遍歷數(shù)組元素的測(cè)試代碼。29 //事實(shí)上,通過(guò)buddiesinfo_size和buddiesinfo函數(shù)亦可循環(huán)遍歷。30 RepeatedPtrField<BuddyInfo>* buddiesInfo = retrieveResp2.mutable_buddiesinfo();31 RepeatedPtrField<BuddyInfo>::iterator it = buddiesInfo->begin();32 for (; it != buddiesInfo->end(); ++it) {33 printf("BuddyInfo->groupID = %d/n", it->groupid());34 printf("UserInfo->acctID = %I64d, UserInfo->name = %s, UserInfo->status = %d/n"35 , it->userinfo().acctid(), it->userinfo().name().c_str(),it->userinfo().status());36 }37 delete [] buf;38 }最后需要說(shuō)明的是,Protocol Buffer仍然提供了很多其它非常有用的功能,特別是針對(duì)序列化的目的地,比如文件流和網(wǎng)絡(luò)流等。與此同時(shí),也提供了完整的官方文檔和規(guī)范的命名規(guī)則,在很多情況下,可以直接通過(guò)函數(shù)的名字便可獲悉函數(shù)所完成的工作。 本打算將該Blog中使用的示例代碼以附件的方式上傳,但是沒(méi)有發(fā)現(xiàn)此功能,望諒解。
Protocol Buffer技術(shù)詳解(Java實(shí)例)
該篇Blog和上一篇(C++實(shí)例)基本相同,只是面向于我們團(tuán)隊(duì)中的Java工程師,畢竟我們項(xiàng)目的前端部分是基于Android開(kāi)發(fā)的,而且我們研發(fā)團(tuán)隊(duì)中目前主要使用的開(kāi)發(fā)語(yǔ)言就是C++、Java和Python,其中Python主要用于編寫(xiě)各種工具程序。然而為了保證該篇Blog的完整性和獨(dú)立性,我仍然會(huì)將上一篇Blog中已經(jīng)出現(xiàn)的內(nèi)容再一次贅述,同時(shí)對(duì)于Java中特有的部分也會(huì)著重介紹。 一、生成目標(biāo)語(yǔ)言代碼。 下面的命令幫助我們將MyMessage.proto文件中定義的一組Protocol Buffer格式的消息編譯成目標(biāo)語(yǔ)言(Java)的代碼。至于消息的內(nèi)容,我們會(huì)在后面以分段的形式逐一列出,同時(shí)也會(huì)在附件中給出所有源代碼。 protoc -I=./message --java_out=./src ./MyMessage.proto 從上面的命令行參數(shù)中可以看出,待編譯的文件為MyMessage.proto,他存放在當(dāng)前目錄的message子目錄下。--java_out參數(shù)則指示編譯工具我們需要生成目標(biāo)語(yǔ)言是java,輸出目錄是當(dāng)前目錄的src子目錄。這里需要補(bǔ)充說(shuō)明的是,因?yàn)樵贛yMessage.proto文件中定義了option java_package = "com.lsk.lyphone"的文件級(jí)選項(xiàng),所以輸出的目前是src/com/lsk/lyphone,生成的目標(biāo)代碼文件名是MyMessage.java。 二、簡(jiǎn)單message生成的Java代碼。 這里先定義一個(gè)最簡(jiǎn)單的message,其中只是包含原始類(lèi)型的字段。 option java_package = "com.lsk.lyphone"; option java_outer_classname = "LYPhoneMessage"; option optimize_for = LITE_RUNTIME; message LogonReqMessage { required int64 acctID = 1; required string passwd = 2; } 對(duì)于選項(xiàng)java_package和java_outer_classname的功能,我們已經(jīng)在之前的一篇Blog(語(yǔ)言規(guī)范)中進(jìn)行了清晰的闡述,這里就不在另行介紹了。然而對(duì)于選項(xiàng)optimize_for,這里將結(jié)合本例給出一些實(shí)用性描述。 當(dāng)前.proto文件中該選項(xiàng)的值為LITE_RUNTIME,因此由該.proto文件生成的所有Java類(lèi)的父類(lèi)均為com.google.protobuf.GeneratedMessageLite,而非com.google.protobuf.GeneratedMessage,同時(shí)與之對(duì)應(yīng)的Builder類(lèi)則均繼承自com.google.protobuf.MessageLiteOrBuilder,而非com.google.protobuf.MessageOrBuilder。在之前的博客中已經(jīng)給出了一些簡(jiǎn)要的說(shuō)明,MessageLite接口是Message的父接口,在MessageLite中將缺少Protocol Buffer對(duì)反射的支持,而此功能均在Message接口中提供了接口規(guī)范,同時(shí)又在其實(shí)現(xiàn)類(lèi)GeneratedMessage中給予了最小功能的實(shí)現(xiàn)。對(duì)于我們的項(xiàng)目而言,整個(gè)系統(tǒng)相對(duì)比較封閉,不會(huì)和更多的外部程序進(jìn)行交互,與此同時(shí),我們的客戶端部分又是運(yùn)行在Android平臺(tái),有鑒于此,我們考慮使用LITE版本的Protocol Buffer。這樣不僅可以得到更高編碼效率,而且生成代碼編譯后所占用的資源也會(huì)更少,至于反射所能帶來(lái)的靈活性和極易擴(kuò)展性,對(duì)于該項(xiàng)目而言完全可以忽略。下面我們來(lái)看一下由message LogonReqMessage生成的Java類(lèi)的部分聲明,以及常用方法的說(shuō)明性注釋。 在做各種case的對(duì)比性分析之前必須要事先聲明的是,Protocol Buffer針對(duì)Java語(yǔ)言所生成的代碼和C++相比存在一個(gè)非常重要的差別,即為每個(gè)消息均會(huì)生成一個(gè)Builder接口和一個(gè)與消息對(duì)應(yīng)的實(shí)現(xiàn)類(lèi),該實(shí)現(xiàn)類(lèi)又將同時(shí)實(shí)現(xiàn)生成的Builder接口和擴(kuò)展Protocol Buffer內(nèi)置的GeneratedMessageLite(或GeneratedMessage)類(lèi)。這一點(diǎn)對(duì)于Protocol Buffer而言,是巧妙的使用了設(shè)計(jì)模式中的Builder模式。換言之,對(duì)于所有消息字段的修改操作均需要通過(guò)與其對(duì)應(yīng)的Builder接口輔助完成。相信我們會(huì)通過(guò)對(duì)下面用例的學(xué)習(xí)可以得到更為清楚的認(rèn)識(shí)。
1 //用于修改LogonReqMessage消息字段的輔助Builder接口。 2 //該接口會(huì)為消息中的每個(gè)字段均提供getter和setter方法。 3 public interface LogonReqMessageOrBuilder 4 extends com.google.protobuf.MessageLiteOrBuilder { 5 6 // required int64 acctID = 1; 7 boolean hasAcctID(); 8 long getAcctID(); 9 10 // required string passwd = 2; 11 boolean haspasswd(); 12 String getPasswd(); 13 } 14 //該類(lèi)為final類(lèi),即不可以在被子類(lèi)化了。這一點(diǎn)在Protocol Buffer的官方文檔中給予了明確 15 //的說(shuō)明,因?yàn)樽宇?lèi)化將會(huì)破壞序列化和反序列化的過(guò)程。 16 public static final class LogonReqMessage extends 17 com.google.protobuf.GeneratedMessageLite 18 implements LogonReqMessageOrBuilder { 19 20 // Use LogonReqMessage.newBuilder() to construct. 21 // 由于所有構(gòu)造函數(shù)均為私有方法,由此可見(jiàn),我們不能直接new LogonReqMessage的對(duì)象 22 // 實(shí)例,而是只能通過(guò)與其對(duì)應(yīng)Builder來(lái)構(gòu)造,或是直接通過(guò)反序列化的方式生成。 23 private LogonReqMessage(Builder builder) { 24 super(builder); 25 } 26 //該靜態(tài)方法為該類(lèi)Builder接口的工廠方法。返回的Builder實(shí)現(xiàn)類(lèi)在完成各個(gè)字段的 27 //初始化后,通過(guò)build()方法返回與其對(duì)應(yīng)的消息實(shí)現(xiàn)類(lèi),即LogonReqMessage。 28 public static Builder newBuilder() { return Builder.create(); } 29 //通過(guò)該類(lèi)的對(duì)象獲取與其對(duì)應(yīng)的Builder類(lèi)對(duì)象,一般用于通過(guò)Builder類(lèi)完成消息字段的修改。 30 public Builder toBuilder() { return newBuilder(this); } 31 32 private LogonReqMessage(boolean noInit) {} 33 //判斷當(dāng)前對(duì)象的所有字段是否都已經(jīng)被初始化。 34 public final boolean isInitialized() { 35 ... ... 36 } 37 //獲取已經(jīng)被初始化后的對(duì)象序列化時(shí)所占用的字節(jié)空間。 38 public int getSerializedSize() { 39 ... ... 40 } 41 //從內(nèi)存中飯序列化LogonReqMessage對(duì)象。 42 //Protocol Buffer中還提供其他一些接口方法,用于從不同的數(shù)據(jù)源反序列化對(duì)象。 43 public static com.lsk.lyphone.LYPhoneMessage.LogonReqMessage parseFrom(byte[] data) 44 throws com.google.protobuf.InvalidProtocolBufferException { 45 return newBuilder().mergeFrom(data).buildParsed(); 46 } 47 //功能和上一個(gè)函數(shù)相同,只是輸入源改為InputStream接口。 48 public static com.lsk.lyphone.LYPhoneMessage.LogonReqMessage parseFrom(java.io.InputStream input) 49 throws java.io.IOException { 50 return newBuilder().mergeFrom(input).buildParsed(); 51 } 52 53 // required int64 acctID = 1; 54 // 下面的靜態(tài)變量對(duì)應(yīng)于該字段在.proto中定義的標(biāo)簽號(hào)。該變量的命名規(guī)則為:字段(全部大寫(xiě)) + _FIELD_NUMBER。 55 public static final int ACCTID_FIELD_NUMBER = 1; 56 public boolean hasAcctID() { 57 return ((bitField0_ & 0x00000001) == 0x00000001); 58 } 59 public long getAcctID() { 60 return acctID_; 61 } 62 63 // required string passwd = 2; 64 public static final int PASSWD_FIELD_NUMBER = 2; 65 public boolean hasPasswd() { 66 return ((bitField0_ & 0x00000002) == 0x00000002); 67 } 68 public String getPasswd() { 69 ... ... 70 } 71 //每一個(gè)Message類(lèi)都會(huì)包含一個(gè)靜態(tài)內(nèi)部類(lèi),即與之對(duì)應(yīng)的Builder類(lèi)。上面代碼中所涉及的Builder類(lèi)均為該內(nèi)部類(lèi)。 72 public static final class Builder extends 73 com.google.protobuf.GeneratedMessageLite.Builder< 74 com.lsk.lyphone.LYPhoneMessage.LogonReqMessage, Builder> 75 implements com.lsk.lyphone.LYPhoneMessage.LogonReqMessageOrBuilder { 76 //清空當(dāng)前對(duì)象中的所有設(shè)置。調(diào)用該函數(shù)之后,本例中的hasAcctID和hasPasswd都會(huì)返回false。 77 public Builder clear() { 78 super.clear(); 79 acctID_ = 0L; 80 bitField0_ = (bitField0_ & ~0x00000001); 81 passwd_ = ""; 82 bitField0_ = (bitField0_ & ~0x00000002); 83 return this; 84 } 85 //克隆出一個(gè)Builder對(duì)象。 86 public Builder clone() { 87 return create().mergeFrom(buildPartial()); 88 } 89 public com.lsk.lyphone.LYPhoneMessage.LogonReqMessage build() { 90 com.lsk.lyphone.LYPhoneMessage.LogonReqMessage result = buildPartial(); 91 if (!result.isInitialized()) { 92 throw newUninitializedMessageException(result); 93 } 94 return result; 95 } 96 // Builder類(lèi)中修改外部消息類(lèi)的方法。 97 // required int64 acctID = 1; 98 public boolean hasAcctID() { 99 return ((bitField0_ & 0x00000001) == 0x00000001);100 }101 public long getAcctID() {102 return acctID_;103 }104 //設(shè)置AcctID字段,該函數(shù)調(diào)用后hasAcctID函數(shù)將返回true。105 //這里之所以讓返回值為Builder對(duì)象,就是可以讓調(diào)用者在一條代碼中方便的連續(xù)修改多個(gè)字段,106 //如:myMessage.setAcctID(100).setPasswd("MyName");107 public Builder setAcctID(long value) {108 bitField0_ |= 0x00000001;109 acctID_ = value;110 return this;111 }112 //清空AcctID字段,該函數(shù)調(diào)用后hasAcctID函數(shù)返回false。113 //這里之所以讓返回值為Builder對(duì)象,就是可以讓調(diào)用者在一條代碼中方便的連續(xù)清空多個(gè)字段,114 //如:myMessage.clearAcctID().clearPasswd();115 public Builder clearAcctID() {116 bitField0_ = (bitField0_ & ~0x00000001);117 acctID_ = 0L;118 return this;119 }120 121 // required string passwd = 2;122 public boolean hasPasswd() {123 return ((bitField0_ & 0x00000002) == 0x00000002);124 }125 public String getPasswd() {126 ... ... 127 }128 public Builder setPasswd(String value) {129 ... ...130 }131 public Builder clearPasswd() {132 bitField0_ = (bitField0_ & ~0x00000002);133 passwd_ = getDefaultInstance().getPasswd();134 return this;135 }136 void setPasswd(com.google.protobuf.ByteString value) {137 bitField0_ |= 0x00000002;138 passwd_ = value;139 }140 }141 }在上面生成的代碼中并沒(méi)有列出與序列化相關(guān)的函數(shù),這部分代碼基本都是在父類(lèi)中實(shí)現(xiàn)的,我們將在下面的例子中給出一些最基本的用法,有興趣的開(kāi)發(fā)者可以直接看Protocol Buffer中的源碼,這部分代碼比較通俗易懂。 下面是讀寫(xiě)LogonReqMessage對(duì)象的Java測(cè)試代碼和說(shuō)明性注釋。
1 private static void testSimpleMessage() { 2 System.out.println("==================This is simple message.================"); 3 //如前所述,不能直接構(gòu)造該消息類(lèi)對(duì)象,只能通過(guò)他的內(nèi)部Builder類(lèi)構(gòu)造并完成所有字段的初始化。 4 LogonReqMessage.Builder logonReqBuilder = LogonReqMessage.newBuilder(); 5 logonReqBuilder.setAcctID(20); 6 logonReqBuilder.setPasswd("Hello World"); 7 //builder對(duì)象初始化完畢后,再通過(guò)build方法生成與之對(duì)應(yīng)的消息類(lèi)對(duì)象。 8 LogonReqMessage logonReq = logonReqBuilder.build(); 9 int length = logonReq.getSerializedSize();10 System.out.println("The result length is " + length);11 //直接序列化到內(nèi)存中,之后可對(duì)該內(nèi)存進(jìn)行二次加工后再寫(xiě)到本地文件或發(fā)送到遠(yuǎn)端,如加密。12 byte[] buf = logonReq.toByteArray();13 14 try {15 LogonReqMessage logonReq2 = LogonReqMessage.parseFrom(buf);16 System.out.println("acctID = " + logonReq2.getAcctID() + "/tpassword = " + logonReq2.getPasswd());17 } catch (InvalidProtocolBufferException e) {18 e.printStackTrace();19 }20 //需要說(shuō)明的是,文件中的內(nèi)容是由之前C++實(shí)例代碼寫(xiě)入的,這里這樣寫(xiě)主要是一種驗(yàn)證。21 System.out.println("Reading data from local file generated by C++");22 try {23 LogonReqMessage logonReq3 = LogonReqMessage.parseFrom(new FileInputStream("C:/Mine/LogonReq.dat"));24 System.out.println("acctID = " + logonReq3.getAcctID() + "/tpassword = " + logonReq3.getPasswd());25 } catch (FileNotFoundException e) {26 e.printStackTrace();27 } catch (IOException e) {28 e.printStackTrace();29 }30 }三、嵌套message生成的Java代碼。 enum UserStatus { OFFLINE = 0; ONLINE = 1; } enum LoginResult { LOGON_RESULT_SUCCESS = 0; LOGON_RESULT_NOTEXIST = 1; LOGON_RESULT_ERROR_PASSWD = 2; LOGON_RESULT_ALREADY_LOGON = 3; LOGON_RESULT_SERVER_ERROR = 4; } message UserInfo { required int64 acctID = 1; required string name = 2; required UserStatus status = 3; } message LogonRespMessage { required LoginResult logonResult = 1; required UserInfo userInfo = 2; //這里嵌套了UserInfo消息。 } 對(duì)于上述消息生成的Java代碼,UserInfo因?yàn)橹皇前嗽碱?lèi)型字段,因此和上例中的LogonReqMessage沒(méi)有太多的差別,這里也就不在重復(fù)列出了。由于LogonRespMessage消息中嵌套了UserInfo類(lèi)型的字段,在這里我們將僅僅給出該消息生成的Java代碼和關(guān)鍵性注釋。
1 public static final class LogonRespMessage extends 2 com.google.protobuf.GeneratedMessageLite 3 implements LogonRespMessageOrBuilder { 4 5 //Message類(lèi)的通用性函數(shù)定義。 6 ... ... 7 8 // required .LoginResult logonResult = 1; 9 public static final int LOGONRESULT_FIELD_NUMBER = 1;10 public boolean hasLogonResult() {11 return ((bitField0_ & 0x00000001) == 0x00000001);12 }13 public com.lsk.lyphone.LYPhoneMessage.LoginResult getLogonResult() {14 return logonResult_;15 }16 17 // required .UserInfo userInfo = 2;18 public static final int USERINFO_FIELD_NUMBER = 2;19 public boolean hasUserInfo() {20 return ((bitField0_ & 0x00000002) == 0x00000002);21 }22 public com.lsk.lyphone.LYPhoneMessage.UserInfo getUserInfo() {23 return userInfo_;24 }25 //Message類(lèi)的通用性函數(shù)定義。可參照上一小節(jié)中的代碼和注釋。26 ... ...27 28 public static final class Builder extends29 com.google.protobuf.GeneratedMessageLite.Builder<30 com.lsk.lyphone.LYPhoneMessage.LogonRespMessage, Builder>31 implements com.lsk.lyphone.LYPhoneMessage.LogonRespMessageOrBuilder {32 33 //一些適用于絕大多數(shù)Builder對(duì)象的通用性方法。34 ... ...35 36 //當(dāng)前示例中Builder生成的代碼和上一小節(jié)中生成的代碼非常類(lèi)似,這里就不一一贅述了。37 //和前面的例子相比一個(gè)重要的差別是setUserInfo函數(shù)多提供了一種函數(shù)簽名,其參數(shù)為38 //UserInfo類(lèi)的Builder對(duì)象。這樣調(diào)用者在使用時(shí)可以直接將Builder對(duì)象作為參數(shù)傳入。39 public Builder setUserInfo(com.lsk.lyphone.LYPhoneMessage.UserInfo.Builder builderForValue) {40 userInfo_ = builderForValue.build();41 bitField0_ |= 0x00000002;42 return this;43 }44 }45 }下面是讀寫(xiě)LogonRespMessage對(duì)象的Java測(cè)試代碼和說(shuō)明性注釋。
1 private static void testNestedMessage() { 2 System.out.println("==================This is nested message.================"); 3 LogonRespMessage.Builder logonRespBuilder = LogonRespMessage.newBuilder(); 4 logonRespBuilder.setLogonResult(LoginResult.LOGON_RESULT_SUCCESS); 5 UserInfo.Builder userInfo = UserInfo.newBuilder(); 6 userInfo.setAcctID(200); 7 userInfo.setName("Tester"); 8 userInfo.setStatus(UserStatus.OFFLINE); 9 //這里也可以直接傳遞userInfo對(duì)象作為參數(shù)。因?yàn)長(zhǎng)ogonRespBuilder類(lèi)提供了setUserInfo的方法重載。10 logonRespBuilder.setUserInfo(userInfo.build());11 LogonRespMessage logonResp = logonRespBuilder.build();12 int length = logonResp.getSerializedSize();13 System.out.println("The result length is " + length);14 byte[] buf = logonResp.toByteArray();15 16 try {17 LogonRespMessage logonResp2 = LogonRespMessage.parseFrom(buf);18 UserInfo userInfo2 = logonResp2.getUserInfo();19 System.out.println("LogonResult = " + logonResp2.getLogonResult().toString() + " acctID = " 20 + userInfo2.getAcctID() + " name = " + userInfo2.getName() + " status = " + userInfo2.getStatus().toString());21 } catch (InvalidProtocolBufferException e) {22 e.printStackTrace();23 }24 System.out.println("Reading data from local file generated by C++");25 try {26 LogonRespMessage logonResp3 = LogonRespMessage.parseFrom(new FileInputStream("C:/Mine/LogonResp.dat"));27 UserInfo userInfo3 = logonResp3.getUserInfo();28 System.out.println("LogonResult = " + logonResp3.getLogonResult().toString() + " acctID = " 29 + userInfo3.getAcctID() + " name = " + userInfo3.getName() + " status = " + userInfo3.getStatus().toString());30 } catch (FileNotFoundException e) {31 e.printStackTrace();32 } catch (IOException e) {33 e.printStackTrace();34 }35 }四、repeated嵌套message生成的Java代碼。 message BuddyInfo { required UserInfo userInfo = 1; required int32 groupID = 2; } message RetrieveBuddiesResp { required int32 buddiesCnt = 1; repeated BuddyInfo buddiesInfo = 2; } 對(duì)于上述消息生成的代碼,我們將只是針對(duì)RetrieveBuddiesResp消息所對(duì)應(yīng)的Java代碼進(jìn)行詳細(xì)說(shuō)明,其余部分和前面小節(jié)的例子基本相同,可直接參照。而對(duì)于RetrieveBuddiesResp類(lèi)中的代碼,我們也僅僅是對(duì)buddiesInfo字段生成的代碼進(jìn)行更為詳細(xì)的解釋。
1 public static final class RetrieveBuddiesResp extends 2 com.google.protobuf.GeneratedMessageLite 3 implements RetrieveBuddiesRespOrBuilder { 4 //這里均為Protocol Buffer生成的通用性代碼。 5 ... ... 6 // repeated .BuddyInfo buddiesInfo = 2; 7 public static final int BUDDIESINFO_FIELD_NUMBER = 2; 8 //對(duì)于repeated類(lèi)型的字段,均返回類(lèi)型參數(shù)為字段類(lèi)型的泛型容器對(duì)象。 9 public java.util.List<com.lsk.lyphone.LYPhoneMessage.BuddyInfo> getBuddiesInfoList() {10 return buddiesInfo_;11 }12 public java.util.List<? extends com.lsk.lyphone.LYPhoneMessage.BuddyInfoOrBuilder> getBuddiesInfoOrBuilderList() {13 return buddiesInfo_;14 }15 public int getBuddiesInfoCount() {16 return buddiesInfo_.size();17 }18 public com.lsk.lyphone.LYPhoneMessage.BuddyInfo getBuddiesInfo(int index) {19 return buddiesInfo_.get(index);20 }21 public com.lsk.lyphone.LYPhoneMessage.BuddyInfoOrBuilder getBuddiesInfoOrBuilder(int index) {22 return buddiesInfo_.get(index);23 }24 25 //這里仍有一些Protocol Buffer生成的通用性代碼。26 ... ...27 28 public static final class Builder extends29 com.google.protobuf.GeneratedMessageLite.Builder<30 com.lsk.lyphone.LYPhoneMessage.RetrieveBuddiesResp, Builder>31 implements com.lsk.lyphone.LYPhoneMessage.RetrieveBuddiesRespOrBuilder {32 33 //這里僅列出和操作repeated字段相關(guān)的方法,其他的方法和前面的例子基本一致。34 // repeated .BuddyInfo buddiesInfo = 2;35 //本來(lái)打算給出比較詳細(xì)的說(shuō)明,但是看到Google為每個(gè)函數(shù)的命名之后就放棄這個(gè)想法,36 //這樣一來(lái)不僅可以避免畫(huà)蛇添足,而且也節(jié)省了時(shí)間。:) 37 public java.util.List<com.lsk.lyphone.LYPhoneMessage.BuddyInfo> getBuddiesInfoList() {38 return java.util.Collections.unmodifiableList(buddiesInfo_);39 }40 public int getBuddiesInfoCount() {41 return buddiesInfo_.size();42 }43 public com.lsk.lyphone.LYPhoneMessage.BuddyInfo getBuddiesInfo(int index) {44 return buddiesInfo_.get(index);45 }46 public Builder setBuddiesInfo(int index, com.lsk.lyphone.LYPhoneMessage.BuddyInfo value) {47 ... ...48 }49 public Builder setBuddiesInfo(int index, com.lsk.lyphone.LYPhoneMessage.BuddyInfo.Builder builderForValue) {50 ... ...51 }52 public Builder addBuddiesInfo(com.lsk.lyphone.LYPhoneMessage.BuddyInfo value) {53 ... ...54 }55 public Builder addBuddiesInfo(int index, com.lsk.lyphone.LYPhoneMessage.BuddyInfo value) {56 ... ...57 }58 public Builder addBuddiesInfo(com.lsk.lyphone.LYPhoneMessage.BuddyInfo.Builder builderForValue) {59 ... ...60 }61 public Builder addBuddiesInfo(62 int index, com.lsk.lyphone.LYPhoneMessage.BuddyInfo.Builder builderForValue) {63 ... ...64 }65 public Builder addAllBuddiesInfo(66 java.lang.Iterable<? extends com.lsk.lyphone.LYPhoneMessage.BuddyInfo> values) {67 ... ...68 }69 public Builder clearBuddiesInfo() {70 ... ...71 }72 public Builder removeBuddiesInfo(int index) {73 ... ...74 }75 }76 }下面是讀寫(xiě)RetrieveBuddiesResp對(duì)象的Java測(cè)試代碼和說(shuō)明性注釋。
1 private static void testRepeatedMessage() { 2 System.out.println("==================This is repeated message.================"); 3 RetrieveBuddiesResp.Builder retrieveBuddiesBuilder = RetrieveBuddiesResp.newBuilder(); 4 retrieveBuddiesBuilder.setBuddiesCnt(2); 5 BuddyInfo.Builder buddyInfoBuilder = BuddyInfo.newBuilder(); 6 buddyInfoBuilder.setGroupID(20); 7 UserInfo.Builder userInfoBuilder = UserInfo.newBuilder(); 8 userInfoBuilder.setAcctID(200); 9 userInfoBuilder.setName("user1");10 userInfoBuilder.setStatus(UserStatus.OFFLINE);11 buddyInfoBuilder.setUserInfo(userInfoBuilder.build());12 retrieveBuddiesBuilder.addBuddiesInfo(buddyInfoBuilder.build());13 14 buddyInfoBuilder = BuddyInfo.newBuilder();15 buddyInfoBuilder.setGroupID(21);16 userInfoBuilder = UserInfo.newBuilder();17 userInfoBuilder.setAcctID(201);18 userInfoBuilder.setName("user2");19 userInfoBuilder.setStatus(UserStatus.ONLINE);20 buddyInfoBuilder.setUserInfo(userInfoBuilder);21 retrieveBuddiesBuilder.addBuddiesInfo(buddyInfoBuilder);22 RetrieveBuddiesResp buddiesResp = retrieveBuddiesBuilder.build();23 24 int length = buddiesResp.getSerializedSize();25 System.out.println("The result length is " + length);26 byte[] buf = buddiesResp.toByteArray();27 28 try {29 RetrieveBuddiesResp buddiesResp2 = RetrieveBuddiesResp.parseFrom(buf);30 System.out.println("BuddiesCount = " + buddiesResp2.getBuddiesCnt());31 System.out.println("Repeated Size = " + buddiesResp2.getBuddiesInfoCount());32 for (int i = 0; i < buddiesResp2.getBuddiesInfoCount(); ++i) {33 BuddyInfo buddyInfo = buddiesResp2.getBuddiesInfo(i);34 UserInfo userInfo = buddyInfo.getUserInfo();35 System.out.println("GroupID = " + buddyInfo.getGroupID() + " UserInfo.acctID = " + userInfo.getAcctID()36 + " UserInfo.name = " + userInfo.getName() + " UserInfo.status = " + userInfo.getStatus());37 }38 39 } catch (InvalidProtocolBufferException e) {40 e.printStackTrace();41 }42 System.out.println("Reading data from local file generated by C++");43 try {44 RetrieveBuddiesResp buddiesResp3 = RetrieveBuddiesResp.parseFrom(new FileInputStream("C:/Mine/RetrieveBuddiesResp.dat"));45 System.out.println("BuddiesCount = " + buddiesResp3.getBuddiesCnt());46 System.out.println("Repeated Size = " + buddiesResp3.getBuddiesInfoCount());47 List<BuddyInfo> buddiesInfo = buddiesResp3.getBuddiesInfoList();48 for (BuddyInfo buddyInfo : buddiesInfo) {49 UserInfo userInfo = buddyInfo.getUserInfo();50 System.out.println("GroupID = " + buddyInfo.getGroupID() + " UserInfo.acctID = " + userInfo.getAcctID()51 + " UserInfo.name = " + userInfo.getName() + " UserInfo.status = " + userInfo.getStatus());52 }53 } catch (FileNotFoundException e) {54 e.printStackTrace();55 } catch (IOException e) {56 e.printStackTrace();57 }58 }對(duì)于Java而言,我們可以通過(guò)Maven工具生成兩個(gè)jar包,其中一個(gè)是protobuf-java-2.4.1.jar,主要用于optimize_for選項(xiàng)為非LITE_RUNTIME的情況,而另一個(gè)protobuf-java-2.4.1-lite.jar文件則恰恰與之相反。另外,我通過(guò)Beyond Compare工具對(duì)這兩個(gè)jar包進(jìn)行了二進(jìn)制比較后發(fā)現(xiàn),他們是完全相同的。這里之所以仍以LITE版本為例,主要還是因?yàn)楹椭耙黄狟log(C++實(shí)例)想匹配。 最后需要說(shuō)明的是,Protocol Buffer仍然提供了很多其它非常有用的功能,特別是針對(duì)序列化的目的地,比如文件流和網(wǎng)絡(luò)流等。與此同時(shí),也提供了完整的官方文檔和規(guī)范的命名規(guī)則,在很多情況下,可以直接通過(guò)函數(shù)的名字便可獲悉函數(shù)所完成的工作。
新聞熱點(diǎn)
疑難解答
圖片精選
網(wǎng)友關(guān)注