protobuf 是用於結構化數據串行化的靈活、高效、自動化的解決方案。又如 XML,不過它更小、更快、也更簡單。你只須要按照你想要的數據存儲格式編寫一個.proto,而後使用生成器生成的代碼來讀寫這個數據結構。更重要的是,你甚至能夠在無需從新部署程序的狀況下更新數據結構。ios
在項目中使用protocol buffers須要經歷以下三步:數組
(1).定義消息格式文件,最好以proto做爲後綴名數據結構
(2).使用Google提供的protocol buffers編譯器來生成代碼文件,通常爲.h和.cc文件,主要是對消息格式以特定的語言方式描述ide
(3).使用protocol buffers庫提供的API來編寫應用程序 函數
1.先來定義一個ui
本消息將以客戶端請求登陸和服務端返回爲列google
syntax = "proto3"; package pt; option optimize_for = LITE_RUNTIME; message req_login { string username = 1; string password = 2; } message obj_user_info { string nickname = 1; //暱稱 string icon = 2; //頭像 int64 coin = 3; //金幣 string location = 4; //所屬地 } //遊戲數據統計 message obj_user_game_record { string time = 1; int32 kill = 2; //擊殺數 int32 dead = 3; //死亡數 int32 assist= 4; //助攻數 } message rsp_login { enum RET { SUCCESS = 0; ACCOUNT_NULL = 1; //帳號不存在 ACCOUNT_LOCK = 2; //帳號鎖定 PASSWORD_ERROR = 3; //密碼錯誤 ERROR = 10; } int32 ret = 1; obj_user_info user_info = 2; repeated obj_user_game_record record = 3; }
2.生成目標語言代碼
下面的命令幫助咱們將game.proto文件中定義的Protocol Buffer格式的消息編譯成C++代碼文件。編碼
//SRC_DIR .proto文件存放目錄 //--cpp_out 指示編譯器生成C++代碼,DST_DIR爲生成文件存放目錄 //game.proto 待編譯的協議文件 protoc -I=$SRC_DIR --cpp_out=$DST_DIR $SRC_DIR/game.proto
因爲咱們在game.proto文件中定義選項optimize_for=LITE_RUNTIME,所以由該文件內生成的全部C++類的父類均爲::google::protobuf::MessageLite,而非::google::protobuf::Message。MessageLite類是Message的父類,MessageLite中缺乏對反射的支持,而此類功能均在Message類中提供了具體的實現。
對於咱們的項目而言,整個系統相對比較封閉,不會和外部程序進行交互,與此同時,咱們的客戶端部分又是運行在Android平臺,有鑑於此,咱們考慮使用LITE版本的Protocol Buffer。這樣不只能夠獲得更高編碼效率,並且生成代碼編譯後所佔用的資源也會更少,至於反射所能帶來的靈活性和極易擴展性,對於該項目而言徹底能夠忽略。spa
3.protobuf自動生成的API指針
class rsp_login : public ::google::protobuf::MessageLite { public: //每個message類都包含如下方法供你檢測或操做 void CopyFrom(const rsp_login& from); void MergeFrom(const rsp_login& from); void Clear(); //清除全部字段內容,並置爲空狀態 bool IsInitialized() const; //檢測全部required 是否初始化
int ByteSize() const; //類所佔字節數 //整形變量只提供獲取、修改、清除 void clear_ret(); ::google::protobuf::int32 ret() const; void set_ret(::google::protobuf::int32 value); //自定義類類型user_info bool has_user_info() const; void clear_user_info(); const ::pt::obj_user_info& user_info() const; //自定義類型,並沒提供set方法,而是經過mutable_接口返回user_info的指針,可根據此指針進行賦值操做 ::pt::obj_user_info* mutable_user_info(); //返回user_info字段指針,將全部權移交給此指針,並將user_info字段置爲empty狀態 ::pt::obj_user_info* release_user_info(); //使用set_allocated要當心,傳入的參數須要顯示allocate,設置後函數內部維護此指針 void set_allocated_user_info(::pt::obj_user_info* user_info); //record爲repeated的類數組類型 int record_size() const; void clear_record(); //根據id索引,返回記錄的引用,const不可修改內容 const ::pt::obj_user_game_record& record(int index) const; //根據id索引,返回記錄的指針,以供查看、修改 ::pt::obj_user_game_record* mutable_record(int index); //repeated類型提供add接口增長一條記錄,並返回此記錄的指針,以便對其賦值 ::pt::obj_user_game_record* add_record(); //提供mutable接口,並返回record字段的容器指針,可根據此指針遍歷、修改 ::google::protobuf::RepeatedPtrField< ::pt::obj_user_game_record >* mutable_record(); //返回record字段的容器引用,const不可修改內容 const ::google::protobuf::RepeatedPtrField< ::pt::obj_user_game_record >& record() const; }
4.protobuf讀和寫
#include <iostream> #include <string> #include "game.pb.h" int main() { pt::rsp_login rsp{}; rsp.set_ret(pt::rsp_login_RET_SUCCESS); auto user_info = rsp.mutable_user_info(); user_info->set_nickname("dsw"); user_info->set_icon("345DS55GF34D774S"); user_info->set_coin(2000); user_info->set_location("zh"); for (int i = 0; i < 5; i++) { auto record = rsp.add_record(); record->set_time("2017/4/13 12:22:11"); record->set_kill(i * 4); record->set_dead(i * 2); record->set_assist(i * 5); } std::string buff{}; rsp.SerializeToString(&buff); //------------------解析---------------------- pt::rsp_login rsp2{}; if (!rsp2.ParseFromString(buff)) { std::cout << "parse error\n"; } auto temp_user_info = rsp2.user_info(); std::cout << "nickname:" << temp_user_info.nickname() << std::endl; std::cout << "coin:" << temp_user_info.coin() << std::endl; for (int m = 0; m < rsp2.record_size(); m++) { auto temp_record = rsp2.record(m); std::cout << "time:" << temp_record.time() << " kill:" << temp_record.kill() << " dead:" << temp_record.dead() << " assist:" << temp_record.assist() << std::endl; } }
輸出以下:
nickname:dsw coin:2000 time:2017/4/13 12:22:11 kill:0 dead:0 assist:0 time:2017/4/13 12:22:11 kill:4 dead:2 assist:5 time:2017/4/13 12:22:11 kill:8 dead:4 assist:10 time:2017/4/13 12:22:11 kill:12 dead:6 assist:15 time:2017/4/13 12:22:11 kill:16 dead:8 assist:20
4.擴展protoobuf在你發佈了使用Protocol-Buffers的代碼以後, 你一定會想"改進"message的定義. 若是你想讓你的新message向後兼容, 而且舊的message可以向前兼容。你必定但願如此,那麼你在新的Person 中就要遵照其餘的一些規則了: (1).對已存在的任何字段, 你都不能更改其標識tag號。 (2).你絕對不能添加或刪除任何required的字段。 (3).你能夠添加新的optional或repeated的字段, 可是你必須使用新的標識tag號(此message中從未使用過的標識號,甚至連已經被刪除過標識號也不行)。 (4).有一些例外狀況, 可是它們不多使用。 若是你遵照這些規則, 老代碼將能很好地解析新的消息, 並忽略掉任何新的字段. 對老代碼來講, 已經被刪除的optional字段將被賦予默認值. 已被刪除的repeated字段將是空的. 新的代碼也可以透明地讀取舊的消息. 可是請牢記心中: 新的optional字段將不會出如今舊的消息中, 若是沒有爲一個optional項指定默認值, 那麼就會使用與特定類型相關的默認值: 對string(null)、boolean(false)、數值類型(0)。還要注意: 若是你添加了一個新的repeated字段, 你的新代碼將沒法告訴你它是否被留空了(被新代碼), 或者是否從未被置值(被舊代碼), 這是由於它沒有has_標誌.