1. 程式人生 > >Protocol Buffers 介紹(一)

Protocol Buffers 介紹(一)

轉載請註明出處:http://blog.csdn.net/manchew/article/details/39494901

這段時間專案上接解到了Protocol Buffers, 目前的專案中被用於網路上傳輸交通訊息,之所以選擇Protocol Buffers我覺得是有一定的歷史緣由,因為早些時間,行動網路傳輸頻寬不是很高,如果傳輸大量資料會產生比較嚴重的lag, 再一個原因是Protocol Buffers不像xml那樣在解析欄位上面,需要花費很高的代價。說到 這裡,可以會一頭霧水,下面就來簡單介紹一下protocol buffers到底是幹什麼用的。

Protocol buffers是由Google公司啟動的開源專案,並且專案的設計開發編碼權權由Google大牛完成,可想而知它的健壯性和效率是不容小看的。起初,Protocol buffers的設計主要被用於Google內部使用來處理協議升級帶來的困惑,例如:A模組升級後增加了欄位F,如果B模組作為接收者也需要對欄位F進行處理,程式碼上表現為:

if(F1)
{
    // handle field F1
}
如果F1欄位裡面又增加了新的描述F2
if(F1)
{
    // handle field F1
    if(F2)
    {
        // handle field F2
    }
}

這樣的程式碼顯得非常難看,我們希望的情況是,當A模組升級後,要做到向後相容,那麼B模組對A模組中新增的欄位忽略掉就可以了。這時Protocol buffers應運而生,也是為什麼叫Protocol buffers的原因, 而不叫其它的什麼buffers。

說簡單點,protocol buffers就是對資訊進行序列化,之後用它提供的介面直接操作二進位制流,所以效率和效能比起直接解析xml檔案來講要好的多。

Protocol Buffers的使用非常簡單,首先我們需要定義.proto檔案用來描述Message型別,然後呼叫Protocol buffers提供的編譯工具來生成對應語言的source檔案,如果你當前的工作語言是c++,那麼編譯後會生成類似.pb.h 和.pb.cc的檔案,前者是標頭檔案,後者是原始檔。接下來只需要在程式碼中使用它們提供的介面就可以輕鬆完成欄位的存取工具。

說了這麼多,我們以一個例子來說明如何正確使用,下面的例子是一個簡單的編解碼過程,我們會把Message序列化到磁碟中,然後再從磁碟中還原出真實的資訊。

  1. 定義Message的描述檔案 .proto
    package lm;
    message helloword
    {
    required int32 id = 1;
    required string str = 2;
    optional int32 opt = 3;
    }
    這裡定義了名為helloword的訊息,它有三個欄位,分別為id,str,opt,required表示欄位在初始化的時候必須給值,否則會出現錯誤,optional為可選,可以給值,也可以忽略。package lm 表示在生成程式碼的時候自動放置到lm的namespace下。
  2. 編譯自己的Protocol buffers首先,需要到https://developers.google.com/protocol-buffers/docs/downloads去下載編譯工具,google提供了linux和windows的工具支援,如果你是在windows工作平臺,只需要執行google提供的可執行檔案protoc.exe就可以,linux下按照google提供的安裝方法也比較容易設定自己的編譯環境。編譯命令為:protoc -I=$SRC_DIR --cpp_out=$DST_DIR $SRC_DIR/dst.proto, 如果你的helloworld.proto放在d:/下,生成的class檔案放在e:/下,則你只需要命令列下執行protoc -I=d:/ --cpp_out=e:/ d:/helloworld.proto ,這樣在e:/下面就會生成helloworld.pb.cc和helloworld.pb.h檔案了。
  3. 在你的工具中使用編譯生成的class檔案
    include "helloworld.pb.h"
    …
    
     int main(void) 
     { 
      
      lm::helloworld msg1; 
      msg1.set_id(100); 
      msg1.set_str(“helloworld”); 
         
      fstream output("./out", ios::out | ios::trunc | ios::binary); 
            
      if (!msg1.SerializeToOstream(&output)) { 
          cerr << "Failed to write msg." << endl; 
          return -1; 
      }         
      return 0; 
     }
    先定義了msg1,然後呼叫set_id(), set_str()對欄位進行賦值,最後再呼叫SerializeToOstream()序列化到output流。奇怪怎麼會有,SerializeToOstream()方法,這就要看看helloworld.pb.h長什麼樣子了。
    // Generated by the protocol buffer compiler.  DO NOT EDIT!
    // source: helloworld.proto
    #ifndef PROTOBUF_helloworld_2eproto__INCLUDED
    #define PROTOBUF_helloworld_2eproto__INCLUDED
    #include <string>
    #include <google protobuf="" stubs="" common="" h="">
    #if GOOGLE_PROTOBUF_VERSION < 2005000
    #error This file was generated by a newer version of protoc which is
    #error incompatible with your Protocol Buffer headers.  Please update
    #error your headers.
    #endif
    #if 2005000 < GOOGLE_PROTOBUF_MIN_PROTOC_VERSION
    #error This file was generated by an older version of protoc which is
    #error incompatible with your Protocol Buffer headers.  Please
    #error regenerate this file with a newer version of protoc.
    #endif
    #include <google protobuf="" generated_message_util="" h="">
    #include <google protobuf="" message="" h="">
    #include <google protobuf="" repeated_field="" h="">
    #include <google protobuf="" extension_set="" h="">
    #include <google protobuf="" unknown_field_set="" h="">
    // @@protoc_insertion_point(includes)
    namespace lm {
    // Internal implementation detail -- do not call these.
    void  protobuf_AddDesc_helloworld_2eproto();
    void protobuf_AssignDesc_helloworld_2eproto();
    void protobuf_ShutdownFile_helloworld_2eproto();
    class helloworld;
    // ===================================================================
    class helloworld : public ::google::protobuf::Message {
     public:
      helloworld();
      virtual ~helloworld();
      helloworld(const helloworld& from);
      inline helloworld& operator=(const helloworld& from) {
        CopyFrom(from);
        return *this;
      }
      inline const ::google::protobuf::UnknownFieldSet& unknown_fields() const {
        return _unknown_fields_;
      }
      inline ::google::protobuf::UnknownFieldSet* mutable_unknown_fields() {
        return &_unknown_fields_;
      }
      static const ::google::protobuf::Descriptor* descriptor();
      static const helloworld& default_instance();
      void Swap(helloworld* other);
      // implements Message ----------------------------------------------
      helloworld* New() const;
      void CopyFrom(const ::google::protobuf::Message& from);
      void MergeFrom(const ::google::protobuf::Message& from);
      void CopyFrom(const helloworld& from);
      void MergeFrom(const helloworld& from);
      void Clear();
      bool IsInitialized() const;
      int ByteSize() const;
      bool MergePartialFromCodedStream(
          ::google::protobuf::io::CodedInputStream* input);
      void SerializeWithCachedSizes(
          ::google::protobuf::io::CodedOutputStream* output) const;
      ::google::protobuf::uint8* SerializeWithCachedSizesToArray(::google::protobuf::uint8* output) const;
      int GetCachedSize() const { return _cached_size_; }
      private:
      void SharedCtor();
      void SharedDtor();
      void SetCachedSize(int size) const;
      public:
      ::google::protobuf::Metadata GetMetadata() const;
      // nested types ----------------------------------------------------
      // accessors -------------------------------------------------------
      // required int32 id = 1;
      inline bool has_id() const;
      inline void clear_id();
      static const int kIdFieldNumber = 1;
      inline ::google::protobuf::int32 id() const;
      inline void set_id(::google::protobuf::int32 value);
      // required string str = 2;
      inline bool has_str() const;
      inline void clear_str();
      static const int kStrFieldNumber = 2;
      inline const ::std::string& str() const;
      inline void set_str(const ::std::string& value);
      inline void set_str(const char* value);
      inline void set_str(const char* value, size_t size);
      inline ::std::string* mutable_str();
      inline ::std::string* release_str();
      inline void set_allocated_str(::std::string* str);
      // optional int32 opt = 3;
      inline bool has_opt() const;
      inline void clear_opt();
      static const int kOptFieldNumber = 3;
      inline ::google::protobuf::int32 opt() const;
      inline void set_opt(::google::protobuf::int32 value);
      // @@protoc_insertion_point(class_scope:lm.helloworld)
     private:
      inline void set_has_id();
      inline void clear_has_id();
      inline void set_has_str();
      inline void clear_has_str();
      inline void set_has_opt();
      inline void clear_has_opt();
      ::google::protobuf::UnknownFieldSet _unknown_fields_;
      ::std::string* str_;
      ::google::protobuf::int32 id_;
      ::google::protobuf::int32 opt_;
      mutable int _cached_size_;
      ::google::protobuf::uint32 _has_bits_[(3 + 31) / 32];
      friend void  protobuf_AddDesc_helloworld_2eproto();
      friend void protobuf_AssignDesc_helloworld_2eproto();
      friend void protobuf_ShutdownFile_helloworld_2eproto();
      void InitAsDefaultInstance();
      static helloworld* default_instance_;
    };
    // ===================================================================
    
    // ===================================================================
    // helloworld
    // required int32 id = 1;
    inline bool helloworld::has_id() const {
      return (_has_bits_[0] & 0x00000001u) != 0;
    }
    inline void helloworld::set_has_id() {
      _has_bits_[0] |= 0x00000001u;
    }
    inline void helloworld::clear_has_id() {
      _has_bits_[0] &= ~0x00000001u;
    }
    inline void helloworld::clear_id() {
      id_ = 0;
      clear_has_id();
    }
    inline ::google::protobuf::int32 helloworld::id() const {
      return id_;
    }
    inline void helloworld::set_id(::google::protobuf::int32 value) {
      set_has_id();
      id_ = value;
    }
    // required string str = 2;
    inline bool helloworld::has_str() const {
      return (_has_bits_[0] & 0x00000002u) != 0;
    }
    inline void helloworld::set_has_str() {
      _has_bits_[0] |= 0x00000002u;
    }
    inline void helloworld::clear_has_str() {
      _has_bits_[0] &= ~0x00000002u;
    }
    inline void helloworld::clear_str() {
      if (str_ != &::google::protobuf::internal::kEmptyString) {
        str_->clear();
      }
      clear_has_str();
    }
    inline const ::std::string& helloworld::str() const {
      return *str_;
    }
    inline void helloworld::set_str(const ::std::string& value) {
      set_has_str();
      if (str_ == &::google::protobuf::internal::kEmptyString) {
        str_ = new ::std::string;
      }
      str_->assign(value);
    }
    inline void helloworld::set_str(const char* value) {
      set_has_str();
      if (str_ == &::google::protobuf::internal::kEmptyString) {
        str_ = new ::std::string;
      }
      str_->assign(value);
    }
    inline void helloworld::set_str(const char* value, size_t size) {
      set_has_str();
      if (str_ == &::google::protobuf::internal::kEmptyString) {
        str_ = new ::std::string;
      }
      str_->assign(reinterpret_cast<const char="">(value), size);
    }
    inline ::std::string* helloworld::mutable_str() {
      set_has_str();
      if (str_ == &::google::protobuf::internal::kEmptyString) {
        str_ = new ::std::string;
      }
      return str_;
    }
    inline ::std::string* helloworld::release_str() {
      clear_has_str();
      if (str_ == &::google::protobuf::internal::kEmptyString) {
        return NULL;
      } else {
        ::std::string* temp = str_;
        str_ = const_cast< ::std::string*>(&::google::protobuf::internal::kEmptyString);
        return temp;
      }
    }
    inline void helloworld::set_allocated_str(::std::string* str) {
      if (str_ != &::google::protobuf::internal::kEmptyString) {
        delete str_;
      }
      if (str) {
        set_has_str();
        str_ = str;
      } else {
        clear_has_str();
        str_ = const_cast< ::std::string*>(&::google::protobuf::internal::kEmptyString);
      }
    }
    // optional int32 opt = 3;
    inline bool helloworld::has_opt() const {
      return (_has_bits_[0] & 0x00000004u) != 0;
    }
    inline void helloworld::set_has_opt() {
      _has_bits_[0] |= 0x00000004u;
    }
    inline void helloworld::clear_has_opt() {
      _has_bits_[0] &= ~0x00000004u;
    }
    inline void helloworld::clear_opt() {
      opt_ = 0;
      clear_has_opt();
    }
    inline ::google::protobuf::int32 helloworld::opt() const {
      return opt_;
    }
    inline void helloworld::set_opt(::google::protobuf::int32 value) {
      set_has_opt();
      opt_ = value;
    }
    
    // @@protoc_insertion_point(namespace_scope)
    }  // namespace lm
    #ifndef SWIG
    namespace google {
    namespace protobuf {
    
    }  // namespace google
    }  // namespace protobuf
    #endif  // SWIG
    // @@protoc_insertion_point(global_scope)
    #endif  // PROTOBUF_helloworld_2eproto__INCLUDED
    </const></google></google></google></google></google></google></string>
    還是沒有找到SerializeToOstream(),看30行,helloworld 繼承了::google::protobuf::Message,檢視它的原始檔可以看到:
    namespace google {
        namespace protobuf {
            ...
            class Message;
            ...
            class LIBPROTOBUF_EXPORT Message : public MessageLite {
                ...
                bool SerializeToOstream(ostream* output) const;
                bool ParseFromIstream(istream* input);
                ...
            }
        }
    }
    
    看到了吧。
  4. 反序列化
     #include "helloworld.pb.h" 
    …
     void OutputMsg(const lm::helloworld & msg) { 
      cout << msg.id() << endl; 
      cout << msg.str() << endl; 
     } 
     
     int main(int argc, char* argv[]) { 
    
      lm::helloworld msg1; 
     
      { 
        fstream input("./out", ios::in | ios::binary); 
        if (!msg1.ParseFromIstream(&input)) { 
          cerr << "Failed to parse data." << endl; 
          return -1; 
        } 
      } 
    
    <pre name="code" class="cpp">OutputMsg(msg1);
    … } 同樣,我們可以用ParseFromIstream()來反序列化,得到message的欄位內容。當然protocol buffers提供給我們很多種介面來呼叫,就看你的需求了。