使用protobuf (proto3, C++和go語言)


在這里,我先講述C++使用protobuf,之后,會補充使用go語言使用protobuf

使用protobuf需要有如下步驟:

  1. .proto文件中定義消息(message)格式。
  2. 使用protobuf的編譯器編譯.proto文件成為相應的語言代碼。
  3. 使用對應語言的protobuf API讀寫消息。
  4. 在這里,我直接使用了官方的示例,之后打算使用grpc簡單轉寫這個示例。官方示例實現了一個稱為addressbook的功能,具體包括兩部分,第一部分是向addressbook中添加個人信息,第二部分是,讀取個人信息。在這里實現的第一步是在.proto中定義個人的結構,當然,如果你想采取自頂向下設計的話,可能會先定義對用戶接口。

下面我們看一下定義的.proto的文件的源代碼:

// [START declaration]
syntax = "proto3";
package tutorial;

import "google/protobuf/timestamp.proto";
// [END declaration]
 
// [START messages]
message Person {
  string name = 1;
  int32 id = 2;  // Unique ID number for this person.
  string email = 3;

  enum PhoneType {
    MOBILE = 0;
    HOME = 1;
    WORK = 2;
  }

  message PhoneNumber {
    string number = 1;
    PhoneType type = 2;
  }

  repeated PhoneNumber phones = 4;
  google.protobuf.Timestamp last_updated = 5;
}

// Our address book file is just one of these.
message AddressBook {
  repeated Person people = 1;
}
// [END messages]

這里,我們對.proto文件所使用的語法進行簡單講解。

1protobuf使用的.proto文件以包聲明開始,包聲明和C++中的namespace對應,在某個包聲明中定義的消息,會出現在對應的namespace命名空間中。import語句用來導入其他.proto文件中的消息定義,這樣就可以在多個.proto文件中定義消息,然后關聯使用了。

2)然后,你需要定義消息結構。一個消息包括多個帶類型的成員。protobuf有許多標准的簡單數據類型,包括bool, int32, floatdouble以及string, protobuf自帶的.proto文件中也有一些消息結構定義,例如上面出現的google.protobuf.Timestamp。當然,你也可以根據這些類型,進一步構造其他消息,例如上面的Person包含了PhoneNumber消息,AddressBook包含了Person消息。你也可以在其他消息中定義消息類型,例如上面出現在PhoneNUmberPerson中進行定義。你還可以定義enum類型,例如上面的PhoneType,包含MOBILE,HOMEWORK三個可選值。

=1”, “=2”是用來在二進制編碼中標識對應字段的tagtag1-15范圍內只需要一個byte來編碼,而較大的數字需要兩個byte來編碼,所以對於常用的那些字段,可以使用1-15范圍內的tag

另外,每一個tag可以使用如下修飾符修飾:

1singular: 表示這個字段可以有一個,也可以沒有。如果沒有的話,在編碼的時候,不會占用空間。

2repeated: 表示這個字段會重復0次或者更多次,這個字段里的值會按照順序編碼。

2. 定義完了.proto文件,下一步就是編譯這個proto文件,我們假設這個proto文件名為addressbook.proto。為了編譯這個文件,運行如下的語句:

protoc -I=$SRC_DIR --cpp_out=$DST_DIR $SRC_DIR/address.proto

其中-I指定proto文件所在的位置,$DST_DIR指定生成文件所在的位置,這里--cpp_out表示生成文件為C++文件,生成目錄在$DST_DIR$SRC_DIR/addressbook.proto

如果你在proto所在文件調用上述命令,可以簡寫如下:

protoc --cpp_out=. addressbook.proto

調用上述命令,生成的文件為addressbook.pb.haddressbook.pb.cc。可以推測,對於xxx.proto,生成文件應該為xxx.pb.hxxx.pb.cc

 

下面簡單查看一些類的定義:

class Person_PhoneNumber : public ::google::protobuf::Message /* @@protoc_insertion_point(class_definition::tutorial.Person.PhoneNumber) */ {
public:
  Person_PhoneNumber();
  virtual ~Person_PhoneNumber();

  static const ::google::protobuf::Descriptor* descriptor() {
    return default_instance().GetDescriptor();
  }

  // accessors ----------------------------------------------------------------
  // string number = 1;
  void clear_number();
  const ::std::string& number() const;
  void set_number(const ::std::string& value);
  void set_number(::std::string&& value);
  void set_number(const char* value);
  void set_number(const char* value, size_t size);
  ::std::string* mutable_number();
  ::std::string* release_number();
  void set_allocated_number(::std::string* number);

  // .tutorial.Person.PhoneType type = 2;
  void clear_type();
  ::tutorial::Person_PhoneType type() const;
  void set_type(::tutorial::Person_PhoneType value);
};

這里的descriptor函數,可以用於反射處理。proto文件在編譯時,會提供比較詳細的操作和獲取函數,當做普通類處理,也會很方便。另外注意這個函數的命令Person_PhoneNumber。在proto文件中,Person為外部類,PhoneNumber是內嵌在Person中的類,對應生成的類名就是按照上面的規則。注意下mutable_number方法,這個方法在沒有設置number的時候也可以調用,在調用時,number會被初始化為空字符串。

enum Person_PhoneType {
  Person_PhoneType_MOBILE = 0,
  Person_PhoneType_HOME = 1,
  Person_PhoneType_WORK = 2,
  ...
};

class Person : public ::google::protobuf::Message /* @@protoc_insertion_point(class_definition: tutorial.Person) */ {
public:
  Person();
  virtual ~Person();

  static const ::google::protobuf::Descriptor* descriptor() {
    return default_instance().GetDescriptor();
  }

  typedef Person_PhoneNumber PhoneNumber;
  typedef Person_PhoneType PhoneType;

  static const PhoneType MOBILE = Person_PhoneType_MOBILE;
  static const PhoneType HOME = Person_PhoneType_HOME;
  static const PhoneType WORK = Person_PhoneType_WORK;

  static inline bool PhoneType_IsValid(int value) {
    return Person_PhoneType_IsValid(value);
  }
  static inline const ::std::string& PhoneType_Name(PhoneType value) {
    return Person_PhoneType_Name(value);
  }
  static inline bool PhoneType_Parse(const ::std::string& name, PhoneType* value) {
    return Person_PhoneType_Parse(name, value);
  }

  // accessors -------------------------------------------
  // repeated .tutorial.Person.PhoneNumber phones = 4;
  int phones_size() const;
  void clear_phones();
  ::tutorial::Person_PhoneNumber* mutable_phones(int index);
  ::google::protobuf::RepeatedPtrField<::tutorial::Person_PhoneNumber>* mutable_phones();
  const ::tutorial::Person_PhoneNumber& phones(int index) const;
  ::tutorial::Person_PhoneNumber* add_phones();
  const ::google::protobuf::RepeatedPtrField<::tutorial::Person_PhoneNumber>& phones() const;

  // string name = 1;
  // string email = 3;

  // .google.protobuf.Timestamp last_updated = 5;
  bool has_last_updated() const;
  void clear_last_updated();
  const ::google::protobuf::Timestamp& last_updated() const;
  ::google::protobuf::Timestamp* release_last_updated();
  ::google::protobuf::Timestamp* mutable_last_updated();
  void set_allocated_last_updated(::google::protobuf::Timestamp* last_updated);

  // int32 id = 2;
  void clear_id();
  ::google::protobuf::int32 id() const;
  void set_id(::google::protobuf::int32 value);
};

這個類的定義和上面的Person_PhoneNumber沒有太大的差別,其中的typedef類型重定義和const定義,通過這種方式,來使得PhoneNumber一類的內嵌類使用起來更加自然,更符合.proto文件中的定義。可以查看一下不同類型的成員的不同操作方法。同一個類型的成員,提供的操作方法基本相同。另外注意一點,Person_PhoneNumberPerson類都繼承於::google::protobuf::Message

 

標准的Message方法

每一個消息類都有很多別的方法,讓你來檢查或者操作整個消息,消息類有這些方法,因為繼承於Message類,或者直接使用下面的方法,或者重寫了虛函數。

1) bool IsInitialialized() const; : 檢查是不是所有必需的字段都已經設置, 這個函數是虛函數。

2) string DebugString() const; : 返回一個可讀的消息表示,很適合用於調試。這個函數的實現如下:

string Message::DebugString() const {
  string debug_string;
  TextFormat::Printer printer;
  printer.SetExpandAny(true);
  printer.PrintToString(*this, &debug_string);
  return debug_string;
}

輸出的大致內容可以參考下面的函數:

void TextFormat::Printer::Print(const Message& message, TextGenerator* generator) const {
  const Descriptor* descriptor = message.GetDescriptor();
  auto itr = custom_message_printers_.find(descriptor);

  if (itr != custom_message_printers_.end()) {
    itr->second->Print(message, single_line_mode_, generator);
    return;
  }

  const Reflection* reflection = message.GetReflection();
  if (descriptor->full_name() == internal::kAnyFullTypeName && expand_any_ &&
      PrintAny(message, generator)) {
    return;
  }

  std::vector<const FieldDescriptor*> fields;

  if (descriptor->options().map_entry()) {
    fields.push_back(descriptor->field(0));
    fields.push_back(descriptor->field(1));
  } else {
    reflection->ListFields(message, &fields);
  }

  if (print_message_fields_in_index_order_) {
    std::sort(fields.begin(), fields.end(), FieldIndexSorter());
  }

  for (int i = 0; i < fields.size(); i++) {
    PrintField(message, reflection, fields[i], generator);
  }

  if (!hide_unknown_fields_) {
    PrintUnknownFields(reflection->GetUnknownFields(message), generator);
  }
}

1) void CopyFrom(const Person& from); : 使用from的值來覆蓋現有值,這個函數是虛函數。

2) void Clear(); 清理所有的元素,將消息重置為空值狀態,這個函數是虛函數。

 

消息的解析和序列號

每一個消息類都有方法用protobuf二進制格式寫入到string或者輸出流,也可以從string或者輸入流讀取數據,來設置值。這些方法都是來自於Message類(或者間接來自於MessageLite)。這些方法包括:

1)bool SerializeToString(string* output) const; :將消息轉化成protobuf二進制存儲到string中,注意存儲的是二進制,而不是文本。

2)bool ParseFromString(const string& data); : 從給定的string中解析消息。

3)bool SerializeToOstream(ostream* output) const; : 將消息寫入到給定的C++ ostream中。

4)bool ParseFromIstream(istream* input); : C++ istream中解析消息。

還有一些用於解析和序列號的函數,可以自行查看。

 

3. 使用proto文件編譯生成的源碼和protobuf官方提供的API接口進行操作

我們先查看一下添加個人的應用:

#include <ctime>
#include <fstream>
#include <google/protobuf/util/time_util.h>
#include <iostream>
#include <string>

#include "addressbook.pb.h"

using namespace std;

using google::protobuf::util::TimeUtil;

// This function fills in a Person message based on user input.
void PromptForAddress(tutorial::Person* person) {
  cout << "Enter person ID number: ";
  int id;
  cin >> id;
  person->set_id(id);
  cin.ignore(256, '\n');

  cout << "Enter name: ";
  getline(cin, *person->mutable_name());

  cout << "Enter email address (blank for none): ";
  string email;
  getline(cin, email);
  if (!email.empty()) {
    person->set_email(email);
  }

  while (true) {
    cout << "Enter a phone number (or leave blank to finish): ";
    string number;
    getline(cin, number);
    if (number.empty()) {
      break;
    }

    tutorial::Person::PhoneNumber* phone_number = person->add_phones();
    phone_number->set_number(number);

    cout << "Is this a mobile, home, or work phone? ";
    string type;
    getline(cin, type);
    if (type == "mobile") {
      phone_number->set_type(tutorial::Person::MOBILE);
    } else if (type == "home") {
      phone_number->set_type(tutorial::Person::HOME);
    } else if (type == "work") {
      phone_number->set_type(tutorial::Person::WORK);
    } else {
      cout << "Unknown phone type.  Using default." << endl;
    }
  }
  *person->mutable_last_updated() = TimeUtil::SecondsToTimestamp(time(NULL));
}

// Main function:  Reads the entire address book from a file,
//   adds one person based on user input, then writes it back out to the same
//   file.
int main(int argc, char* argv[]) {
  // Verify that the version of the library that we linked against is
  // compatible with the version of the headers we compiled against.
  GOOGLE_PROTOBUF_VERIFY_VERSION;

  if (argc != 2) {
    cerr << "Usage:  " << argv[0] << " ADDRESS_BOOK_FILE" << endl;
    return -1;
  }

  tutorial::AddressBook address_book;

  {
    // Read the existing address book.
    fstream input(argv[1], ios::in | ios::binary);
    if (!input) {
      cout << argv[1] << ": File not found.  Creating a new file." << endl;
    } else if (!address_book.ParseFromIstream(&input)) {
      cerr << "Failed to parse address book." << endl;
      return -1;
    }
  }

  // Add an address.
  PromptForAddress(address_book.add_people());

  {
    // Write the new address book back to disk.
    fstream output(argv[1], ios::out | ios::trunc | ios::binary);
    if (!address_book.SerializeToOstream(&output)) {
      cerr << "Failed to write address book." << endl;
      return -1;
    }
  }

  // Optional:  Delete all global objects allocated by libprotobuf.
  google::protobuf::ShutdownProtobufLibrary();

  return 0;
}

這段代碼應該並不難理解,所以,我不打算講解這個代碼的邏輯,這個程序實現的就是根據用戶輸入的一個個人信息,構建一個Person對象,然后將這個對象信息存儲到addressbook文件中。

注意其中的GOOGLE_PROTOBUF_VERIFY_VERSION宏。在使用C++protobuf庫之前調用這個宏是一個好的習慣,這個宏可以用來確保你所鏈接的庫與你編譯時使用的頭文件版本一致。如果發現了版本不一致,程序就會退出。所有的.pb.cc文件開始都會調用這個宏。

另外,關注一下程序結尾處的ShutdownProtobufLibrary()函數調用。這個函數用來刪除protobuf庫分配的所有全局對象。對於大多數應用來說,這個操作是不必要的,因為程序退出后,系統會回收所有的內存。但是,如果你使用了內存泄露檢測,或者說你在寫一個會被加載和卸載很多次的庫,那么你就可以使用這個函數來清理protobuf分配的資源。

 

讀取個人信息的程序,讀取上一個程序生成的protobuf序列化文件,然后在控制台輸出個人信息。具體代碼如下:

#include <fstream>
#include <google/protobuf/util/time_util.h>
#include <iostream>
#include <string>

#include "addressbook.pb.h"

using namespace std;

using google::protobuf::util::TimeUtil;

// Iterates though all people in the AddressBook and prints info about them.
void ListPeople(const tutorial::AddressBook& address_book) {
  for (int i = 0; i < address_book.people_size(); i++) {
    const tutorial::Person& person = address_book.people(i);

    cout << "Person ID: " << person.id() << endl;
    cout << "  Name: " << person.name() << endl;
    if (person.email() != "") {
      cout << "  E-mail address: " << person.email() << endl;
    }

    for (int j = 0; j < person.phones_size(); j++) {
      const tutorial::Person::PhoneNumber& phone_number = person.phones(j);

      switch (phone_number.type()) {
        case tutorial::Person::MOBILE:
          cout << "  Mobile phone #: ";
          break;
        case tutorial::Person::HOME:
          cout << "  Home phone #: ";
          break;
        case tutorial::Person::WORK:
          cout << "  Work phone #: ";
          break;
        default:
          cout << "  Unknown phone #: ";
          break;
      }
      cout << phone_number.number() << endl;
    }
    if (person.has_last_updated()) {
      cout << "  Updated: " << TimeUtil::ToString(person.last_updated()) << endl;
    }
  }
}

// Main function:  Reads the entire address book from a file and prints all
//   the information inside.
int main(int argc, char* argv[]) {
  // Verify that the version of the library that we linked against is
  // compatible with the version of the headers we compiled against.
  GOOGLE_PROTOBUF_VERIFY_VERSION;

  if (argc != 2) {
    cerr << "Usage:  " << argv[0] << " ADDRESS_BOOK_FILE" << endl;
    return -1;
  }

  tutorial::AddressBook address_book;

  {
    // Read the existing address book.
    fstream input(argv[1], ios::in | ios::binary);
    if (!address_book.ParseFromIstream(&input)) {
      cerr << "Failed to parse address book." << endl;
      return -1;
    }
  }

  ListPeople(address_book);

  // Optional:  Delete all global objects allocated by libprotobuf.
  google::protobuf::ShutdownProtobufLibrary();

  return 0;
}

下面給出Makefile文件:

.PHONY: all cpp clean

all: cpp
 
cpp:    add_person_cpp    list_people_cpp
go:     add_person_go     list_people_go
gotest: add_person_gotest list_people_gotest

clean:
    rm -f add_person_cpp list_people_cpp     
rm -f protoc_middleman addressbook.pb.cc addressbook.pb.h
    rm -f protoc_middleman_go tutorial/*.pb.go add_person_go list_people_go
    rmdir tutorial 2>/dev/null || true

protoc_middleman: addressbook.proto
    protoc $$PROTO_PATH --cpp_out=. addressbook.proto
    @touch protoc_middleman

protoc_middleman_go: addressbook.proto
    mkdir -p tutorial # make directory for go package
    protoc $$PROTO_PATH --go_out=tutorial addressbook.proto
    @touch protoc_middleman_go

add_person_cpp: add_person.cc protoc_middleman
    pkg-config --cflags protobuf  # fails if protobuf is not installed
    c++ add_person.cc addressbook.pb.cc -o add_person_cpp `pkg-config --cflags --libs protobuf`

list_people_cpp: list_people.cc protoc_middleman
    pkg-config --cflags protobuf  # fails if protobuf is not installed
    c++ list_people.cc addressbook.pb.cc -o list_people_cpp `pkg-config --cflags --libs protobuf`
 
add_person_go: add_person.go protoc_middleman_go
    go build -o add_person_go add_person.go

add_person_gotest: add_person_test.go add_person_go
    go test add_person.go add_person_test.go

編譯上述c++程序很簡單,在應用程序源碼所在的文件夾(同時也是Makefile所在的文件夾)調用make cpp,會創建兩個應用:add_person_cpplist_people_cpp。使用方法如下:

$ ./add_person_cpp addressbook.data

$ ./list_people_cpp addressbook.data

程序運行過程中,有提示信息,同時也可以查看源碼了解應用,所以就不解釋了。

 

擴展protobuf

如果你想要修改protobuf消息結構的定義,並且你希望新的消息可以向后兼容,以前的消息可以向前兼容,那么你需要注意一下幾點:

(1)不要改變已有成員的tag數值

(2)你可以添加新的成員,但是必須使用新的tag數值(完全沒用過的tag數值,如果有成員被刪除,這個成員的tag數值也不可以再用)

如果你遵循這些規則,那么以前的代碼可以讀取新的消息,雖然會忽略掉新的成員。對於以前的代碼,刪除掉的singular字段每次都是默認值,刪除調用的repeated字段會為空。新的代碼可以讀取以前的消息,只不過新的singular字段都為默認值,新的repeated字段都為空。

 

關於使用go語言處理protobuf

C++一致的地方,就忽略不講了,有不太懂的地方,可以參考上面講解C++的部分。

編譯proto文件的命令如下:

protoc -I=$SRC_DIR --go_out=$DST_DIR $SRC_DIR/addressbook.proto

如果編譯在proto文件目錄進行,同時想編譯到proto目錄,可以使用如下命令:

protoc --go_out=. addressbook.proto

編譯會生成一個addressbook.pb.go文件。我們簡單查看一下addressbook.pb.go文件:

package tutorial

type Person_PhoneType int32

const (
  Person_MOBILE Person_PhoneType = 0
  Person_HOME  Person_PhoneType = 1
  Person_WORK Person_PhoneType = 2
)
 

var Person_PhoneType_name = map[int32]string {
  0: “MOBILE”,
  1: “HOME”,
  2: “WORK”,
}

var Person_PhoneType_value = map[string]int32 {
  “MOBILE”: 0,
  “HOME”: 1,
  “WORK”: 2,
}

// [START messages]
type Person struct {
  Name string `protobuf:”bytes,1,opt,name=name,proto3” json:”name, omitempty”`
  Id int32 `protobuf:”varint,2,opt,name=id,proto3” json:”id,omitempty”`
  Email string `protobuf:”bytes,3,opt,name=email,proto3” json:”email,omitempty”`
  Phones []*Person_PhoneNumber `protobuf:”bytes,4,rep,name=phones,proto3” json:”phones,omitempty”`
  LastUpdated *timestamp.Timestamp `protobuf:”bytes,5,opt,name=last_updated,json=lastUpdated,proto3” json:”last_updated,omitempty”`
  ...
}

func (m *Person) Reset() { *m = Person{} }
func (m *Person) String() string { return proto.CompactTextString(m) }

func (m *Person) XXX_Unmarshal(b []byte) error {
  return xxx_messageInfo_Person.Unmarshal(m, b)
}
func (m *Person) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
  return xxx_messageInfo_Person.Marshal(b, m, deterministic)
}
func (m *Person) XXX_Size() int {
  return xxx_messageInfo_Person.Size(m)
}

var xxx_messageInfo_Person proto.InternalMessageInfo

func (m *Person) GetName() string {
  if m != nil {
    return m.Name
  }
  return “”
}

func (m *Person) GetId() int32 {
  if m != nil {
    return m.Id
  }
  return 0
}

func (m *Person) GetPhones() []*Person_PhoneNumber {
  if m != nil {
    return m.Phones
  }
  return nil
}
 
func (m *Person) GetLastUpdated() *timestamp.Timestamp {
  if m != nil {
    return m_lastUpdated
  }
  return nil
}

type Person_PhoneNumber struct {
  Number string `protobuf:”bytes,1,opt,name=number,proto3” json:”number,omitempty”`
  Type Person_PhoneType `protobuf:”varint,2,opt,name=type,proto3,enum=tutorial.Person_PhoneType” json:”type,omitempty”`
}

type AddressBook struct {
  People []*Person `protobuf:”bytes,1,rep,name=people,proto3” json:”people,omitempty”`
}

其中在proto文件中的package對應於go語言中的package。生成的go代碼比較簡單,結構體的命名方式和C++中一致,提供的函數很少,甚至連操作成員的函數都沒有,這個可能是因為go語言中經常直接訪問數據成員的緣故,不過結構體的命名確實不符合go語言的規范。go生成的結構體中的成員tag內容很多,可以用於反射。關於生成go代碼的結構體的使用可以查看下面的代碼:

p := pb.Person{
        Id:    1234,
        Name:  "John Doe",
        Email: "jdoe@example.com",
        Phones: []*pb.Person_PhoneNumber{
                {Number: "555-4321", Type: pb.Person_HOME},
        },
}

關於上面出現的XXX_UnmarshalXXX_Marshal函數,可以參考以下的代碼:

// Marshal takes a protocol buffer message
// and encodes it into the wire format, returning the data.
// This is the main entry point.
func Marshal(pb Message) ([]byte, error) {
    if m, ok := pb.(newMarshaler); ok {
        siz := m.XXX_Size()
        b := make([]byte, 0, siz)
        return m.XXX_Marshal(b, false)
    }    

    if m, ok := pb.(Marshaler); ok {
        // If the message can marshal itself, let it do it, for compatibility.
        // NOTE: This is not efficient.
        return m.Marshal()
    }    

    // in case somehow we didn't generate the wrapper
    if pb == nil {
        return nil, ErrNil
    }    

    var info InternalMessageInfo
    siz := info.Size(pb)
    b := make([]byte, 0, siz)
    return info.Marshal(b, pb, false)
}

 

// Unmarshal parses the protocol buffer representation in buf and places the
// decoded result in pb.  If the struct underlying pb does not match
// the data in buf, the results can be unpredictable.
//
// Unmarshal resets pb before starting to unmarshal, so any
// existing data in pb is always removed. Use UnmarshalMerge
// to preserve and append to existing data.
func Unmarshal(buf []byte, pb Message) error {
    pb.Reset()
    if u, ok := pb.(newUnmarshaler); ok {
        return u.XXX_Unmarshal(buf)
    }   

    if u, ok := pb.(Unmarshaler); ok {
        return u.Unmarshal(buf)
    }   

    return NewBuffer(buf).Unmarshal(pb)
}

查看一下添加個人的應用代碼。在go語言中,我們使用proto包中的Marshal函數來序列號protobuf數據,指向消息結構體的指針實現了proto.Message的接口,所以可以對消息結構體的指針調用proto.Marshal來實現編碼操作。

 

package main

import (
	"bufio"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"os"
	"strings"

	"github.com/golang/protobuf/proto"
	pb "github.com/protocolbuffers/protobuf/examples/tutorial"
)

func promptForAddress(r io.Reader) (*pb.Person, error) {
	// A protocol buffer can be created like any struct.
	p := &pb.Person{}

	rd := bufio.NewReader(r)
	fmt.Print("Enter person ID number: ")
	// An int32 field in the .proto file is represented as an int32 field
	// in the generated Go struct.
	if _, err := fmt.Fscanf(rd, "%d\n", &p.Id); err != nil {
		return p, err
	}

	fmt.Print("Enter name: ")
	name, err := rd.ReadString('\n')
	if err != nil {
		return p, err
	}
	// A string field in the .proto file results in a string field in Go.
	// We trim the whitespace because rd.ReadString includes the trailing
	// newline character in its output.
	p.Name = strings.TrimSpace(name)

	fmt.Print("Enter email address (blank for none): ")
	email, err := rd.ReadString('\n')
	if err != nil {
		return p, err
	}
	p.Email = strings.TrimSpace(email)

	for {
		fmt.Print("Enter a phone number (or leave blank to finish): ")
		phone, err := rd.ReadString('\n')
		if err != nil {
			return p, err
		}
		phone = strings.TrimSpace(phone)
		if phone == "" {
			break
		}
		// The PhoneNumber message type is nested within the Person
		// message in the .proto file.  This results in a Go struct
		// named using the name of the parent prefixed to the name of
		// the nested message.  Just as with pb.Person, it can be
		// created like any other struct.
		pn := &pb.Person_PhoneNumber{
			Number: phone,
		}

		fmt.Print("Is this a mobile, home, or work phone? ")
		ptype, err := rd.ReadString('\n')
		if err != nil {
			return p, err
		}
		ptype = strings.TrimSpace(ptype)

		// A proto enum results in a Go constant for each enum value.
		switch ptype {
		case "mobile":
			pn.Type = pb.Person_MOBILE
		case "home":
			pn.Type = pb.Person_HOME
		case "work":
			pn.Type = pb.Person_WORK
		default:
			fmt.Printf("Unknown phone type %q.  Using default.\n", ptype)
		}

		// A repeated proto field maps to a slice field in Go.  We can
		// append to it like any other slice.
		p.Phones = append(p.Phones, pn)
	}

	return p, nil
}

// Main reads the entire address book from a file, adds one person based on
// user input, then writes it back out to the same file.
func main() {
	if len(os.Args) != 2 {
		log.Fatalf("Usage:  %s ADDRESS_BOOK_FILE\n", os.Args[0])
	}
	fname := os.Args[1]

	// Read the existing address book.
	in, err := ioutil.ReadFile(fname)
	if err != nil {
		if os.IsNotExist(err) {
			fmt.Printf("%s: File not found.  Creating new file.\n", fname)
		} else {
			log.Fatalln("Error reading file:", err)
		}
	}

	// [START marshal_proto]
	book := &pb.AddressBook{}
	// [START_EXCLUDE]
	if err := proto.Unmarshal(in, book); err != nil {
		log.Fatalln("Failed to parse address book:", err)
	}

	// Add an address.
	addr, err := promptForAddress(os.Stdin)
	if err != nil {
		log.Fatalln("Error with address:", err)
	}
	book.People = append(book.People, addr)
	// [END_EXCLUDE]

	// Write the new address book back to disk.
	out, err := proto.Marshal(book)
	if err != nil {
		log.Fatalln("Failed to encode address book:", err)
	}
	if err := ioutil.WriteFile(fname, out, 0644); err != nil {
		log.Fatalln("Failed to write address book:", err)
	}
	// [END marshal_proto]
}

讀取個人信息的應用,通過使用proto包中的Unmarshal函數來解碼protobuf二進制信息。調用這個函數將傳入的buf的參數,解析到pb對象中,pb的實際值是一個消息的指針。下面查看代碼:

 

package main

import (
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"os"

	"github.com/golang/protobuf/proto"
	pb "github.com/protocolbuffers/protobuf/examples/tutorial"
)

func writePerson(w io.Writer, p *pb.Person) {
	fmt.Fprintln(w, "Person ID:", p.Id)
	fmt.Fprintln(w, "  Name:", p.Name)
	if p.Email != "" {
		fmt.Fprintln(w, "  E-mail address:", p.Email)
	}

	for _, pn := range p.Phones {
		switch pn.Type {
		case pb.Person_MOBILE:
			fmt.Fprint(w, "  Mobile phone #: ")
		case pb.Person_HOME:
			fmt.Fprint(w, "  Home phone #: ")
		case pb.Person_WORK:
			fmt.Fprint(w, "  Work phone #: ")
		}
		fmt.Fprintln(w, pn.Number)
	}
}

func listPeople(w io.Writer, book *pb.AddressBook) {
	for _, p := range book.People {
		writePerson(w, p)
	}
}

// Main reads the entire address book from a file and prints all the
// information inside.
func main() {
	if len(os.Args) != 2 {
		log.Fatalf("Usage:  %s ADDRESS_BOOK_FILE\n", os.Args[0])
	}
	fname := os.Args[1]

	// [START unmarshal_proto]
	// Read the existing address book.
	in, err := ioutil.ReadFile(fname)
	if err != nil {
		log.Fatalln("Error reading file:", err)
	}
	book := &pb.AddressBook{}
	if err := proto.Unmarshal(in, book); err != nil {
		log.Fatalln("Failed to parse address book:", err)
	}
	// [END unmarshal_proto]

	listPeople(os.Stdout, book)
}

 

 以上就是關於go語言的講解,如果有不清楚的地方,可以參考一下C++的講解。

下面附帶官方的一些網站:

https://developers.google.com/protocol-buffers/docs/cpptutorial (C++使用簡介)

https://developers.google.com/protocol-buffers/docs/gotutorial(go語言使用簡介)

https://developers.google.com/protocol-buffers/docs/proto3(proto3語法說明)

https://github.com/protocolbuffers/protobuf(protobuf官方github.com倉庫)

https://github.com/protocolbuffers/protobuf/tree/master/examples(protobuf官方github.com倉庫的示例位置)

 


免責聲明!

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



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