亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 學院 > 開發設計 > 正文

Protocol Buffer技術詳解(語言規范)

2019-11-11 04:46:46
字體:
來源:轉載
供稿:網友

該系列Blog的內容主體主要源自于PRotocol Buffer的官方文檔,而代碼示例則抽取于當前正在開發的一個公司內部項目的Demo。這樣做的目的主要在于不僅可以保持Google文檔的良好風格和系統性,同時再結合一些比較實用和通用的用例,這樣就更加便于公司內部的培訓,以及和廣大網友的技術交流。需要說明的是,Blog的內容并非line by line的翻譯,其中包含一些經驗性總結,與此同時,對于一些不是非常常用的功能并未予以說明,有興趣的開發者可以直接查閱Google的官方文檔。      一、為什么使用Protocol Buffer?      在回答這個問題之前,我們還是先給出一個在實際開發中經常會遇到的系統場景。比如:我們的客戶端程序是使用java開發的,可能運行自不同的平臺,如:linux、Windows或者是Android,而我們的服務器程序通常是基于Linux平臺并使用C++開發完成的。在這兩種程序之間進行數據通訊時存在多種方式用于設計消息格式,如:      1. 直接傳遞C/C++語言中一字節對齊的結構體數據,只要結構體的聲明為定長格式,那么該方式對于C/C++程序而言就非常方便了,僅需將接收到的數據按照結構體類型強行轉換即可。事實上對于變長結構體也不會非常麻煩。在發送數據時,也只需定義一個結構體變量并設置各個成員變量的值之后,再以char*的方式將該二進制數據發送到遠端。反之,該方式對于Java開發者而言就會非常繁瑣,首先需要將接收到的數據存于ByteBuffer之中,再根據約定的字節序逐個讀取每個字段,并將讀取后的值再賦值給另外一個值對象中的域變量,以便于程序中其他代碼邏輯的編寫。對于該類型程序而言,聯調的基準是必須客戶端和服務器雙方均完成了消息報文構建程序的編寫后才能展開,而該設計方式將會直接導致Java程序開發的進度過慢。即便是Debug階段,也會經常遇到Java程序中出現各種域字段拼接的小錯誤。      2. 使用SOAP協議(WebService)作為消息報文的格式載體,由該方式生成的報文是基于文本格式的,同時還存在大量的xml描述信息,因此將會大大增加網絡IO的負擔。又由于XML解析的復雜性,這也會大幅降低報文解析的性能。總之,使用該設計方式將會使系統的整體運行性能明顯下降。      對于以上兩種方式所產生的問題,Protocol Buffer均可以很好的解決,不僅如此,Protocol Buffer還有一個非常重要的優點就是可以保證同一消息報文新舊版本之間的兼容性。至于具體的方式我們將會在后續的博客中給出。      二、定義第一個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之間,這樣便可以有效的節省編碼后的字節數量。      三、定義第二個(含有枚舉字段)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 TypeNotesC++ TypeJava Type
double  double double
float  float float
int32Uses variable-length encoding. Inefficient for encoding negative numbers – if your field is likely to have negative values, use sint32 instead. int32 int
int64Uses variable-length encoding. Inefficient for encoding negative numbers – if your field is likely to have negative values, use sint64 instead. int64 long
uint32Uses variable-length encoding. uint32 int
uint64Uses variable-length encoding. uint64 long
sint32Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int32s. int32 int
sint64Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int64s.  int64 long
fixed32Always four bytes. More efficient than uint32 if values are often greater than 228 uint32 int
fixed64Always eight bytes. More efficient than uint64 if values are often greater than 256. uint64 long
sfixed32Always four bytes. int32 int
sfixed64Always eight bytes. int64 long
bool  bool boolean
stringA string must always contain UTF-8 encoded or 7-bit ASCII text. string String
bytesMay contain any arbitrary sequence of bytes.stringByteString

      七、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文件中定義包名,如:      package ourproject.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。

http://www.cnblogs.com/stephen-liu74/archive/2013/01/02/2841485.html

Protocol Buffer技術詳解(C++實例)

這篇Blog仍然是以Google的官方文檔為主線,代碼實例則完全取自于我們正在開發的一個Demo項目,通過前一段時間的嘗試,感覺這種結合的方式比較有利于培訓和內部的技術交流。還是那句話,沒有最好的,只有最適合的。我想寫Blog也是這一道理吧,不同的技術主題可能需要采用不同的風格。好了,還是讓我們盡早切入主題吧。          一、生成目標語言代碼。      下面的命令幫助我們將MyMessage.proto文件中定義的一組Protocol Buffer格式的消息編譯成目標語言(C++)的代碼。至于消息的內容,我們會在后面以分段的形式逐一列出,同時也會在附件中給出所有源代碼。      protoc -I=./message --cpp_out=./src ./MyMessage.proto      從上面的命令行參數中可以看出,待編譯的文件為MyMessage.proto,他存放在當前目錄的message子目錄下。--cpp_out參數則指示編譯工具我們需要生成目標語言是C++,輸出目錄是當前目錄的src子目錄。在本例中,生成的目標代碼文件名是MyMessage.pb.h和MyMessage.pb.cc。          二、簡單message生成的C++代碼。      這里先定義一個最簡單的message,其中只是包含原始類型的字段。      option optimize_for = LITE_RUNTIME;      message LogonReqMessage {          required int64 acctID = 1;          required string passwd = 2;      }      由于我們在MyMessage文件中定義選項optimize_for的值為LITE_RUNTIME,因此由該.proto文件生成的所有C++類的父類均為::google::protobuf::MessageLite,而非::google::protobuf::Message。在上一篇博客中已經給出了一些簡要的說明,MessageLite類是Message的父類,在MessageLite中將缺少Protocol Buffer對反射的支持,而此類功能均在Message類中提供了具體的實現。對于我們的項目而言,整個系統相對比較封閉,不會和更多的外部程序進行交互,與此同時,我們的客戶端部分又是運行在Android平臺,有鑒于此,我們考慮使用LITE版本的Protocol Buffer。這樣不僅可以得到更高編碼效率,而且生成代碼編譯后所占用的資源也會更少,至于反射所能帶來的靈活性和極易擴展性,對于該項目而言完全可以忽略。下面我們來看一下由message LogonReqMessage生成的C++類的部分聲明,以及常用方法的說明性注釋。

復制代碼
 1     class LogonReqMessage : public ::google::protobuf::MessageLite { 2     public: 3         LogonReqMessage(); 4         virtual ~LogonReqMessage(); 5  6         // implements Message ---------------------------------------------- 7         //下面的成員函數均實現自MessageLite中的虛函數。 8         //創建一個新的LogonReqMessage對象,等同于clone。 9         LogonReqMessage* New() const;10         //用另外一個LogonReqMessage對象初始化當前對象,等同于賦值操作符重載(operator=)11         void CopyFrom(const LogonReqMessage& from);12         //清空當前對象中的所有數據,既將所有成員變量置為未初始化狀態。13         void Clear();14         //判斷當前狀態是否已經初始化。15         bool IsInitialized() const;16         //在給當前對象的所有變量賦值之后,獲取該對象序列化后所需要的字節數。17         int ByteSize() const;18         //獲取當前對象的類型名稱。19         ::std::string GetTypeName() const;20 21         // required int64 acctID = 1;22         //下面的成員函數都是因message中定義的acctID字段而生成。23         //這個靜態成員表示AcctID的標簽值。命名規則是k + FieldName(駝峰規則) + FieldNumber。24         static const int kAcctIDFieldNumber = 1;25         //如果acctID字段已經被設置返回true,否則false。26         inline bool has_acctid() const;27         //執行該函數后has_acctid函數將返回false,而下面的acctid函數則返回acctID的缺省值。28         inline void clear_acctid();29         //返回acctid字段的當前值,如果沒有設置則返回int64類型的缺省值。30         inline ::google::protobuf::int64 acctid() const;31         //為acctid字段設置新值,調用該函數后has_acctid函數將返回true。32         inline void set_acctid(::google::protobuf::int64 value);33     34         // required string passwd = 2;35         //下面的成員函數都是因message中定義的passwd字段而生成。這里生成的函數和上面acctid36         //生成的那組函數基本相似。因此這里只是列出差異部分。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         //對于字符串類型字段設置const char*類型的變量值。43         inline void set_passwd(const char* value);44         inline void set_passwd(const char* value, size_t size);45         //可以通過返回值直接給passwd對象賦值。在調用該函數之后has_passwd將返回true。46         inline ::std::string* mutable_passwd();47         //釋放當前對象對passwd字段的所有權,同時返回passwd字段對象指針。調用此函數之后,passwd字段對象48         //的所有權將移交給調用者。此后再調用has_passwd函數時將返回false。49         inline ::std::string* release_passwd();50     private:51         ... ... 52     };復制代碼

      下面是讀寫LogonReqMessage對象的C++測試代碼和說明性注釋。

復制代碼
 1     void testSimpleMessage() 2     { 3         printf("==================This is simple message.================/n"); 4         //序列化LogonReqMessage對象到指定的內存區域。 5         LogonReqMessage logonReq; 6         logonReq.set_acctid(20); 7         logonReq.set_passwd("Hello World"); 8         //提前獲取對象序列化所占用的空間并進行一次性分配,從而避免多次分配 9         //而造成的性能開銷。通過該種方式,還可以將序列化后的數據進行加密。10         //之后再進行持久化,或是發送到遠端。11         int length = logonReq.ByteSize();12         char* buf = new char[length];13         logonReq.SerializeToArray(buf,length);14         //從內存中讀取并反序列化LogonReqMessage對象,同時將結果打印出來。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消息。      }      對于上述消息生成的C++代碼,UserInfo因為只是包含了原始類型字段,因此和上例中的LogonReqMessage沒有太多的差別,這里也就不在重復列出了。由于LogonRespMessage消息中嵌套了UserInfo類型的字段,在這里我們將僅僅給出該消息生成的C++代碼和關鍵性注釋。

復制代碼
 1     class LogonRespMessage : public ::google::protobuf::MessageLite { 2     public: 3         LogonRespMessage(); 4         virtual ~LogonRespMessage(); 5      6         // implements Message ---------------------------------------------- 7         ... ... //這部分函數和之前的例子一樣。 8          9         // required .LoginResult logonResult = 1;10         //下面的成員函數都是因message中定義的logonResult字段而生成。11         //這一點和前面的例子基本相同,只是類型換做了枚舉類型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         //下面的成員函數都是因message中定義的UserInfo字段而生成。20         //這里只是列出和非消息類型字段差異的部分。21         static const int kUserInfoFieldNumber = 2;22         inline bool has_userinfo() const;23         inline void clear_userinfo();24         inline const ::UserInfo& userinfo() const;25         //可以看到該類并沒有生成用于設置和修改userInfo字段set_userinfo函數,而是將該工作26         //交給了下面的mutable_userinfo函數。因此每當調用函數之后,Protocol Buffer都會認為27         //該字段的值已經被設置了,同時has_userinfo函數亦將返回true。在實際編碼中,我們可以28         //通過該函數返回userInfo字段的內部指針,并基于該指針完成userInfo成員變量的初始化工作。29         inline ::UserInfo* mutable_userinfo();30         inline ::UserInfo* release_userinfo();31     private:32         ... ...33     };                    復制代碼

      下面是讀寫LogonRespMessage對象的C++測試代碼和說明性注釋。

復制代碼
 1     void testNestedMessage() 2     { 3         printf("==================This is nested message.================/n"); 4         LogonRespMessage logonResp; 5         logonResp.set_logonresult(LOGON_RESULT_SUCCESS); 6         //如上所述,通過mutable_userinfo函數返回userInfo字段的指針,之后再初始化該對象指針。 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;      }      對于上述消息生成的代碼,我們將只是針對RetrieveBuddiesResp消息所對應的C++代碼進行詳細說明,其余部分和前面小節的例子基本相同,可直接參照。而對于RetrieveBuddiesResp類中的代碼,我們也僅僅是對buddiesInfo字段生成的代碼進行更為詳細的解釋。

復制代碼
 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         //返回數組中成員的數量。11         inline int buddiesinfo_size() const;12         //清空數組中的所有已初始化成員,調用該函數后,buddiesinfo_size函數將返回0。13         inline void clear_buddiesinfo();14         //返回數組中指定下標所包含元素的引用。15         inline const ::BuddyInfo& buddiesinfo(int index) const;16         //返回數組中指定下標所包含元素的指針,通過該方式可直接修改元素的值信息。17         inline ::BuddyInfo* mutable_buddiesinfo(int index);18         //像數組中添加一個新元素。返回值即為新增的元素,可直接對其進行初始化。19         inline ::BuddyInfo* add_buddiesinfo();20         //獲取buddiesInfo字段所表示的容器,該函數返回的容器僅用于遍歷并讀取,不能直接修改。21         inline const ::google::protobuf::RepeatedPtrField< ::BuddyInfo >&22           buddiesinfo() const;23         //獲取buddiesInfo字段所表示的容器指針,該函數返回的容器指針可用于遍歷和直接修改。24         inline ::google::protobuf::RepeatedPtrField< ::BuddyInfo >*25           mutable_buddiesinfo();26     private:27         ... ...28     };復制代碼

      下面是讀寫RetrieveBuddiesResp對象的C++測試代碼和說明性注釋。

復制代碼
 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         //這里僅提供了通過容器迭代器的方式遍歷數組元素的測試代碼。29         //事實上,通過buddiesinfo_size和buddiesinfo函數亦可循環遍歷。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     }復制代碼

      最后需要說明的是,Protocol Buffer仍然提供了很多其它非常有用的功能,特別是針對序列化的目的地,比如文件流和網絡流等。與此同時,也提供了完整的官方文檔和規范的命名規則,在很多情況下,可以直接通過函數的名字便可獲悉函數所完成的工作。      本打算將該Blog中使用的示例代碼以附件的方式上傳,但是沒有發現此功能,望諒解。

Protocol Buffer技術詳解(Java實例)

該篇Blog和上一篇(C++實例)基本相同,只是面向于我們團隊中的Java工程師,畢竟我們項目的前端部分是基于Android開發的,而且我們研發團隊中目前主要使用的開發語言就是C++、Java和Python,其中Python主要用于編寫各種工具程序。然而為了保證該篇Blog的完整性和獨立性,我仍然會將上一篇Blog中已經出現的內容再一次贅述,同時對于Java中特有的部分也會著重介紹。          一、生成目標語言代碼。      下面的命令幫助我們將MyMessage.proto文件中定義的一組Protocol Buffer格式的消息編譯成目標語言(Java)的代碼。至于消息的內容,我們會在后面以分段的形式逐一列出,同時也會在附件中給出所有源代碼。      protoc -I=./message --java_out=./src ./MyMessage.proto      從上面的命令行參數中可以看出,待編譯的文件為MyMessage.proto,他存放在當前目錄的message子目錄下。--java_out參數則指示編譯工具我們需要生成目標語言是java,輸出目錄是當前目錄的src子目錄。這里需要補充說明的是,因為在MyMessage.proto文件中定義了option java_package = "com.lsk.lyphone"的文件級選項,所以輸出的目前是src/com/lsk/lyphone,生成的目標代碼文件名是MyMessage.java。          二、簡單message生成的Java代碼。      這里先定義一個最簡單的message,其中只是包含原始類型的字段。      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;      }      對于選項java_packagejava_outer_classname的功能,我們已經在之前的一篇Blog(語言規范)中進行了清晰的闡述,這里就不在另行介紹了。然而對于選項optimize_for,這里將結合本例給出一些實用性描述。      當前.proto文件中該選項的值為LITE_RUNTIME,因此由該.proto文件生成的所有Java類的父類均為com.google.protobuf.GeneratedMessageLite,而非com.google.protobuf.GeneratedMessage,同時與之對應的Builder類則均繼承自com.google.protobuf.MessageLiteOrBuilder,而非com.google.protobuf.MessageOrBuilder。在之前的博客中已經給出了一些簡要的說明,MessageLite接口是Message的父接口,在MessageLite中將缺少Protocol Buffer對反射的支持,而此功能均在Message接口中提供了接口規范,同時又在其實現類GeneratedMessage中給予了最小功能的實現。對于我們的項目而言,整個系統相對比較封閉,不會和更多的外部程序進行交互,與此同時,我們的客戶端部分又是運行在Android平臺,有鑒于此,我們考慮使用LITE版本的Protocol Buffer。這樣不僅可以得到更高編碼效率,而且生成代碼編譯后所占用的資源也會更少,至于反射所能帶來的靈活性和極易擴展性,對于該項目而言完全可以忽略。下面我們來看一下由message LogonReqMessage生成的Java類的部分聲明,以及常用方法的說明性注釋。      在做各種case的對比性分析之前必須要事先聲明的是,Protocol Buffer針對Java語言所生成的代碼和C++相比存在一個非常重要的差別,即為每個消息均會生成一個Builder接口和一個與消息對應的實現類,該實現類又將同時實現生成的Builder接口和擴展Protocol Buffer內置的GeneratedMessageLite(或GeneratedMessage)類。這一點對于Protocol Buffer而言,是巧妙的使用了設計模式中的Builder模式。換言之,對于所有消息字段的修改操作均需要通過與其對應的Builder接口輔助完成。相信我們會通過對下面用例的學習可以得到更為清楚的認識。

復制代碼
  1     //用于修改LogonReqMessage消息字段的輔助Builder接口。  2     //該接口會為消息中的每個字段均提供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     //該類為final類,即不可以在被子類化了。這一點在Protocol Buffer的官方文檔中給予了明確 15     //的說明,因為子類化將會破壞序列化和反序列化的過程。 16     public static final class LogonReqMessage extends 17         com.google.protobuf.GeneratedMessageLite 18         implements LogonReqMessageOrBuilder { 19          20         // Use LogonReqMessage.newBuilder() to construct. 21         // 由于所有構造函數均為私有方法,由此可見,我們不能直接new LogonReqMessage的對象 22         // 實例,而是只能通過與其對應Builder來構造,或是直接通過反序列化的方式生成。 23         private LogonReqMessage(Builder builder) { 24             super(builder); 25         } 26         //該靜態方法為該類Builder接口的工廠方法。返回的Builder實現類在完成各個字段的 27         //初始化后,通過build()方法返回與其對應的消息實現類,即LogonReqMessage。 28         public static Builder newBuilder() { return Builder.create(); } 29         //通過該類的對象獲取與其對應的Builder類對象,一般用于通過Builder類完成消息字段的修改。 30         public Builder toBuilder() { return newBuilder(this); } 31  32         private LogonReqMessage(boolean noInit) {} 33         //判斷當前對象的所有字段是否都已經被初始化。 34         public final boolean isInitialized() { 35             ... ... 36         } 37         //獲取已經被初始化后的對象序列化時所占用的字節空間。 38         public int getSerializedSize() { 39             ... ... 40         } 41         //從內存中飯序列化LogonReqMessage對象。 42         //Protocol Buffer中還提供其他一些接口方法,用于從不同的數據源反序列化對象。 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         //功能和上一個函數相同,只是輸入源改為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         // 下面的靜態變量對應于該字段在.proto中定義的標簽號。該變量的命名規則為:字段(全部大寫) + _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         //每一個Message類都會包含一個靜態內部類,即與之對應的Builder類。上面代碼中所涉及的Builder類均為該內部類。 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             //清空當前對象中的所有設置。調用該函數之后,本例中的hasAcctID和hasPasswd都會返回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             //克隆出一個Builder對象。 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類中修改外部消息類的方法。 97             // required int64 acctID = 1; 98             public boolean hasAcctID() { 99                 return ((bitField0_ & 0x00000001) == 0x00000001);100             }101             public long getAcctID() {102                 return acctID_;103             }104             //設置AcctID字段,該函數調用后hasAcctID函數將返回true。105             //這里之所以讓返回值為Builder對象,就是可以讓調用者在一條代碼中方便的連續修改多個字段,106             //如:myMessage.setAcctID(100).setPasswd("MyName");107             public Builder setAcctID(long value) {108                 bitField0_ |= 0x00000001;109                 acctID_ = value;110                 return this;111             }112             //清空AcctID字段,該函數調用后hasAcctID函數返回false。113             //這里之所以讓返回值為Builder對象,就是可以讓調用者在一條代碼中方便的連續清空多個字段,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     }            復制代碼

      在上面生成的代碼中并沒有列出與序列化相關的函數,這部分代碼基本都是在父類中實現的,我們將在下面的例子中給出一些最基本的用法,有興趣的開發者可以直接看Protocol Buffer中的源碼,這部分代碼比較通俗易懂。      下面是讀寫LogonReqMessage對象的Java測試代碼和說明性注釋。

復制代碼
 1     private static void testSimpleMessage() { 2         System.out.println("==================This is simple message.================"); 3         //如前所述,不能直接構造該消息類對象,只能通過他的內部Builder類構造并完成所有字段的初始化。 4         LogonReqMessage.Builder logonReqBuilder = LogonReqMessage.newBuilder(); 5         logonReqBuilder.setAcctID(20); 6         logonReqBuilder.setPasswd("Hello World"); 7         //builder對象初始化完畢后,再通過build方法生成與之對應的消息類對象。 8         LogonReqMessage logonReq = logonReqBuilder.build(); 9         int length = logonReq.getSerializedSize();10         System.out.println("The result length is " + length);11         //直接序列化到內存中,之后可對該內存進行二次加工后再寫到本地文件或發送到遠端,如加密。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         //需要說明的是,文件中的內容是由之前C++實例代碼寫入的,這里這樣寫主要是一種驗證。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消息。      }      對于上述消息生成的Java代碼,UserInfo因為只是包含了原始類型字段,因此和上例中的LogonReqMessage沒有太多的差別,這里也就不在重復列出了。由于LogonRespMessage消息中嵌套了UserInfo類型的字段,在這里我們將僅僅給出該消息生成的Java代碼和關鍵性注釋。

復制代碼
 1     public static final class LogonRespMessage extends 2         com.google.protobuf.GeneratedMessageLite 3         implements LogonRespMessageOrBuilder { 4          5         //Message類的通用性函數定義。 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類的通用性函數定義??蓞⒄丈弦恍」澲械拇a和注釋。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             //一些適用于絕大多數Builder對象的通用性方法。34             ... ...35             36             //當前示例中Builder生成的代碼和上一小節中生成的代碼非常類似,這里就不一一贅述了。37             //和前面的例子相比一個重要的差別是setUserInfo函數多提供了一種函數簽名,其參數為38             //UserInfo類的Builder對象。這樣調用者在使用時可以直接將Builder對象作為參數傳入。39             public Builder setUserInfo(com.lsk.lyphone.LYPhoneMessage.UserInfo.Builder builderForValue) {40                 userInfo_ = builderForValue.build();41                 bitField0_ |= 0x00000002;42                 return this;43             }44         }45     }復制代碼

      下面是讀寫LogonRespMessage對象的Java測試代碼和說明性注釋。

復制代碼
 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對象作為參數。因為LogonRespBuilder類提供了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;      }      對于上述消息生成的代碼,我們將只是針對RetrieveBuddiesResp消息所對應的Java代碼進行詳細說明,其余部分和前面小節的例子基本相同,可直接參照。而對于RetrieveBuddiesResp類中的代碼,我們也僅僅是對buddiesInfo字段生成的代碼進行更為詳細的解釋。

復制代碼
 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         //對于repeated類型的字段,均返回類型參數為字段類型的泛型容器對象。 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字段相關的方法,其他的方法和前面的例子基本一致。34             // repeated .BuddyInfo buddiesInfo = 2;35             //本來打算給出比較詳細的說明,但是看到Google為每個函數的命名之后就放棄這個想法,36             //這樣一來不僅可以避免畫蛇添足,而且也節省了時間。:)            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     }復制代碼

      下面是讀寫RetrieveBuddiesResp對象的Java測試代碼和說明性注釋。

復制代碼
 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     }復制代碼

      對于Java而言,我們可以通過Maven工具生成兩個jar包,其中一個是protobuf-java-2.4.1.jar,主要用于optimize_for選項為非LITE_RUNTIME的情況,而另一個protobuf-java-2.4.1-lite.jar文件則恰恰與之相反。另外,我通過Beyond Compare工具對這兩個jar包進行了二進制比較后發現,他們是完全相同的。這里之所以仍以LITE版本為例,主要還是因為和之前一篇Blog(C++實例)想匹配。      最后需要說明的是,Protocol Buffer仍然提供了很多其它非常有用的功能,特別是針對序列化的目的地,比如文件流和網絡流等。與此同時,也提供了完整的官方文檔和規范的命名規則,在很多情況下,可以直接通過函數的名字便可獲悉函數所完成的工作。


上一篇:1004: 產生數

下一篇:上機練習1-2

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
黑人与娇小精品av专区| 欧美成人午夜视频| 国产99在线|中文| 欧美中文在线观看| 91久久国产精品91久久性色| 欧美极品少妇xxxxⅹ裸体艺术| 亚洲国产精品99久久| 欧美日韩第一视频| 欧美激情2020午夜免费观看| 亚洲欧美激情四射在线日| 亚洲大胆人体在线| 亚洲成色999久久网站| 国内成人精品一区| 亚洲午夜激情免费视频| 久久久亚洲国产| 不卡在线观看电视剧完整版| 午夜欧美大片免费观看| 国产在线999| 久久久成人av| 45www国产精品网站| 18一19gay欧美视频网站| 日韩精品一二三四区| 日韩av免费在线播放| 欧美成人精品在线视频| 亚洲欧洲午夜一线一品| 97国产精品人人爽人人做| 久久精品国产久精国产思思| 亚洲成人三级在线| 精品动漫一区二区| 亚洲欧洲自拍偷拍| 欧美一级大片在线观看| 九色精品免费永久在线| 欧美另类老女人| 亚洲欧美国产一本综合首页| 欧美老女人www| 66m—66摸成人免费视频| 亚洲免费影视第一页| 亚洲第一视频网| 国产精品综合网站| 国产日韩欧美在线| 午夜精品福利视频| 欧美有码在线观看视频| 亚洲美女在线看| 国产精品久久久久久中文字| 欧美视频在线观看免费网址| 日韩亚洲精品电影| 中文字幕国产精品久久| 欧美极品欧美精品欧美视频| 91在线视频一区| 国产综合福利在线| 日韩一区二区精品视频| 亚洲国产精品推荐| 影音先锋日韩有码| 国产精品久久电影观看| 日韩有码视频在线| 日韩麻豆第一页| 亚洲成人久久一区| 久久九九有精品国产23| 欧美精品第一页在线播放| 久久久久久久久91| 中文字幕日韩高清| 色久欧美在线视频观看| 韩国v欧美v日本v亚洲| 亚洲免费av网址| 中文国产成人精品久久一| 亚洲精品视频免费在线观看| 尤物99国产成人精品视频| 91沈先生作品| 在线国产精品播放| 色噜噜亚洲精品中文字幕| 日韩精品极品视频免费观看| 国产精品一香蕉国产线看观看| 欧美精品在线视频观看| 美女国内精品自产拍在线播放| 久久久久久久久久久久av| 亚洲少妇中文在线| 亚洲午夜性刺激影院| 欧美日韩成人在线视频| 国语自产精品视频在线看抢先版图片| 97国产suv精品一区二区62| 91精品久久久久久久久久久| 欧美精品在线免费播放| 欧美二区在线播放| 黑人极品videos精品欧美裸| 国产一区av在线| 国产小视频国产精品| 久久久伊人日本| 色综合色综合网色综合| 久久手机免费视频| 综合网日日天干夜夜久久| 色99之美女主播在线视频| 久久久久国色av免费观看性色| 日韩在线观看电影| 欧美一级大片视频| 欧洲成人免费aa| 国产99久久精品一区二区永久免费| 欧美激情在线狂野欧美精品| 国产精品久久久999| 欧美黑人狂野猛交老妇| 久久久久中文字幕| 最新亚洲国产精品| 欧美在线一级va免费观看| 在线精品国产欧美| 久久久视频免费观看| 久久在线观看视频| 亚洲视频在线免费看| 欧美日韩中文字幕综合视频| 国产精品美女免费| 欧美日韩激情视频| 红桃av永久久久| 中文字幕日韩有码| 97视频在线观看网址| 国产裸体写真av一区二区| 日韩在线视频网站| 日韩免费在线电影| 国产欧美一区二区三区在线看| 亚洲国产福利在线| 亚洲成人av在线| 国产欧美精品日韩精品| 深夜福利日韩在线看| 亚洲天堂免费在线| 色爱av美腿丝袜综合粉嫩av| 欧美成人精品xxx| 日韩视频免费在线观看| 欧美午夜女人视频在线| 91色视频在线观看| 亚洲一区二区在线| 欧美理论在线观看| 色婷婷综合久久久久中文字幕1| 国产精品国产福利国产秒拍| 国产一区二区av| 国产精品久久久999| 久久久久久久香蕉网| 日韩免费观看av| 日韩欧美国产黄色| 琪琪第一精品导航| 欧美激情欧美狂野欧美精品| 日本高清视频精品| 日韩精品小视频| 在线中文字幕日韩| 久久免费国产精品1| 久久久久久久久久久久av| 亚洲女同精品视频| 理论片在线不卡免费观看| 国产剧情久久久久久| 国产精品自拍网| 日韩视频永久免费观看| 久久久久久91| 国产一区二区日韩精品欧美精品| 亚洲欧美日韩天堂| 欧美日韩一区二区免费视频| 午夜精品一区二区三区在线| 日韩美女av在线免费观看| 欧美日韩精品在线观看| 92看片淫黄大片看国产片| 欧美日韩亚洲国产一区| 亚洲精品ady| 久久久久久69| 欧美日韩精品在线播放| 黄色成人在线播放| 欧美成人激情视频| 精品一区二区三区电影| 欧美精品一区在线播放| 久久久最新网址|