ProtoBuf3 C++使用篇

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
View Code

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_標誌.

相關文章
相關標籤/搜索