ProtoBuf3 C++使用篇


protobuf 是用於結構化數據串行化的靈活、高效、自動化的解決方案。又如 XML,不過它更小、更快、也更簡單。你只需要按照你想要的數據存儲格式編寫一個.proto,然后使用生成器生成的代碼來讀寫這個數據結構。更重要的是,你甚至可以在無需重新部署程序的情況下更新數據結構。

在項目中使用protocol buffers需要經歷如下三步:

     (1).定義消息格式文件,最好以proto作為后綴名

     (2).使用Google提供的protocol buffers編譯器來生成代碼文件,一般為.h和.cc文件,主要是對消息格式以特定的語言方式描述

     (3).使用protocol buffers庫提供的API來編寫應用程序 

1.先來定義一個

本消息將以客戶端請求登錄和服務端返回為列

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。這樣不僅可以得到更高編碼效率,而且生成代碼編譯后所占用的資源也會更少,至於反射所能帶來的靈活性和極易擴展性,對於該項目而言完全可以忽略。

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM