使用protobuf (proto3, C++和go语言)
在这里,我先讲述C++使用protobuf,之后,会补充使用go语言使用protobuf。
使用protobuf需要有如下步骤:
- 在.proto文件中定义消息(message)格式。
- 使用protobuf的编译器编译.proto文件成为相应的语言代码。
- 使用对应语言的protobuf API读写消息。
- 在这里,我直接使用了官方的示例,之后打算使用grpc简单转写这个示例。官方示例实现了一个称为addressbook的功能,具体包括两部分,第一部分是向addressbook中添加个人信息,第二部分是,读取个人信息。在这里实现的第一步是在.proto中定义个人的结构,当然,如果你想采取自顶向下设计的话,可能会先定义对用户接口。
下面我们看一下定义的.proto的文件的源代码:
// [START declaration]
syntax = "proto3";
package tutorial; import "google/protobuf/timestamp.proto";
// [END declaration] // [START messages]
message Person {
string name = ;
int32 id = ; // Unique ID number for this person.
string email = ; enum PhoneType {
MOBILE = ;
HOME = ;
WORK = ;
} message PhoneNumber {
string number = ;
PhoneType type = ;
} repeated PhoneNumber phones = ;
google.protobuf.Timestamp last_updated = ;
} // Our address book file is just one of these.
message AddressBook {
repeated Person people = ;
}
// [END messages]
这里,我们对.proto文件所使用的语法进行简单讲解。
1)protobuf使用的.proto文件以包声明开始,包声明和C++中的namespace对应,在某个包声明中定义的消息,会出现在对应的namespace命名空间中。import语句用来导入其他.proto文件中的消息定义,这样就可以在多个.proto文件中定义消息,然后关联使用了。
2)然后,你需要定义消息结构。一个消息包括多个带类型的成员。protobuf有许多标准的简单数据类型,包括bool, int32, float,double以及string, protobuf自带的.proto文件中也有一些消息结构定义,例如上面出现的google.protobuf.Timestamp。当然,你也可以根据这些类型,进一步构造其他消息,例如上面的Person包含了PhoneNumber消息,AddressBook包含了Person消息。你也可以在其他消息中定义消息类型,例如上面出现在PhoneNUmber在Person中进行定义。你还可以定义enum类型,例如上面的PhoneType,包含MOBILE,HOME和WORK三个可选值。
“=1”, “=2”是用来在二进制编码中标识对应字段的tag。tag在1-15范围内只需要一个byte来编码,而较大的数字需要两个byte来编码,所以对于常用的那些字段,可以使用1-15范围内的tag。
另外,每一个tag可以使用如下修饰符修饰:
(1)singular: 表示这个字段可以有一个,也可以没有。如果没有的话,在编码的时候,不会占用空间。
(2)repeated: 表示这个字段会重复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.h和addressbook.pb.cc。可以推测,对于xxx.proto,生成文件应该为xxx.pb.h和xxx.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 = ,
Person_PhoneType_HOME = ,
Person_PhoneType_WORK = ,
...
}; 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_PhoneNumber和Person类都继承于::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());
fields.push_back(descriptor->field());
} else {
reflection->ListFields(message, &fields);
} if (print_message_fields_in_index_order_) {
std::sort(fields.begin(), fields.end(), FieldIndexSorter());
} for (int i = ; 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(, '\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 != ) {
cerr << "Usage: " << argv[] << " ADDRESS_BOOK_FILE" << endl;
return -;
} tutorial::AddressBook address_book; {
// Read the existing address book.
fstream input(argv[], ios::in | ios::binary);
if (!input) {
cout << argv[] << ": File not found. Creating a new file." << endl;
} else if (!address_book.ParseFromIstream(&input)) {
cerr << "Failed to parse address book." << endl;
return -;
}
} // Add an address.
PromptForAddress(address_book.add_people()); {
// Write the new address book back to disk.
fstream output(argv[], ios::out | ios::trunc | ios::binary);
if (!address_book.SerializeToOstream(&output)) {
cerr << "Failed to write address book." << endl;
return -;
}
} // Optional: Delete all global objects allocated by libprotobuf.
google::protobuf::ShutdownProtobufLibrary(); return ;
}
这段代码应该并不难理解,所以,我不打算讲解这个代码的逻辑,这个程序实现的就是根据用户输入的一个个人信息,构建一个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 = ; 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 = ; 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 != ) {
cerr << "Usage: " << argv[] << " ADDRESS_BOOK_FILE" << endl;
return -;
} tutorial::AddressBook address_book; {
// Read the existing address book.
fstream input(argv[], ios::in | ios::binary);
if (!address_book.ParseFromIstream(&input)) {
cerr << "Failed to parse address book." << endl;
return -;
}
} ListPeople(address_book); // Optional: Delete all global objects allocated by libprotobuf.
google::protobuf::ShutdownProtobufLibrary(); return ;
}
下面给出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_cpp和list_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_Unmarshal和XXX_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仓库的示例位置)
使用protobuf (proto3, C++和go语言)的更多相关文章
- Golang里面使用protobuf(proto3)
参考文章:https://developers.google.com/protocol-buffers/docs/gotutorial 1.执行指令: go envgo get github.com/ ...
- Netty之ProtoBuf(六)
Protocol Buffer的基本使用(六) 一.简介 Protocol Buffer(简称ProtoBuf)是google的一个语言中立,平台中立,可扩展的对结构化的数据进行序列化的一种机制,和X ...
- 通讯协议序列化解读(一) Protobuf详解教程
前言:说到JSON可能大家很熟悉,是目前应用最广泛的一种序列化格式,它使用起来简单方便,而且拥有超高的可读性.但是在越来越多的应用场景里,JSON冗长的缺点导致它并不是一种最优的选择. 一.常用序列化 ...
- protobuf使用简介
官网:https://github.com/google/protobuf 环境:windows,java 1. protobuf概述protobuf是Google开发一种数据描述格式,能够将结构化数 ...
- protobuf初识
protobuf是一种高效的数据格式,平台无关.语言无关.可扩展,可用于 RPC 系统和持续数据存储系统. protobuf protobuf介绍 Protobuf是Protocol Buffer的简 ...
- 深入理解 ProtoBuf 原理与工程实践(概述)
ProtoBuf 作为一种跨平台.语言无关.可扩展的序列化结构数据的方法,已广泛应用于网络数据交换及存储.随着互联网的发展,系统的异构性会愈发突出,跨语言的需求会愈加明显,同时 gRPC 也大有取代R ...
- 第三方数据格式库protobuf
protobuf初识 protobuf是一种高效的数据格式,平台无关.语言无关.可扩展,可用于 RPC 系统和持续数据存储系统. protobuf protobuf介绍 Protobuf是Protoc ...
- 前后端数据交互利器--Protobuf
Protobuf 介绍 Protocol Buffers(又名 protobuf)是 Google 的语言中立.平台中立.可扩展的结构化数据序列化机制. https://github.com/prot ...
- Cocos2d-JS/Ajax用Protobuf与NodeJS/Java通信
原文地址:http://www.iclojure.com/blog/articles/2016/04/29/cocos2d-js-ajax-protobuf-nodejs-java Google的Pr ...
随机推荐
- 如何开发一个npm包并发布到npm中央仓库
转自: https://liaolongdong.com/2019/01/24/publish-public-npm.html 如何开发一个npm包并发布到npm中央仓库需求背景:平时在项目工作中可能 ...
- Cucumber介绍
Cucumber是一个提供能让我们都理解的普通语言,通过普通语言来描述的测试用例,并支持行为驱动开发的测试工具.Cucumber支持大多数变成语言,如Ruby.Java和Python等. 官方地址:h ...
- Spring cloud微服务安全实战-7-3prometheus环境搭建
Prmetheus 主要用来做来Metrics的监控和报警,这张图是官方的架构图. 这是他的核心 它的作用是根据我们的配置去完成数据的采集.服务的发现,以及数据的存储. 这是服务的发现,通过Servi ...
- k8s记录-yum本地仓库部署
#1.安装插件yum install -y yum-plugin-downloadonly createrepo rsync #2.创建仓库目录mkdir -p /mirrors/centos#3.下 ...
- React之改变页面上方图标
首先在public/index.html中 <!-- <link rel="shortcut icon" href="%PUBLIC_URL%/bitbug6 ...
- Laya微信小游戏的动态资源
版本2.1.1. 由于有4m包限制,所以一般小游戏项目分成主包+远程资源. 现在我新建一个remote目录,该目录下资源不发布到微信小游戏目录下,而是服务器远程加载. 发布时,勾线是否提取本地包 远程 ...
- [LeetCode] 231. Power of Two 2的次方数
Given an integer, write a function to determine if it is a power of two. Example 1: Input: 1 Output: ...
- mysql类型转换函数convert与cast的用法
原文地址:https://blog.csdn.net/kouwoo/article/details/45535733 简单介绍下mysql数据库中用于类型转换的二个函数,convert与cast函数, ...
- PHP实现的MongoDB数据增删改查
原文地址:https://www.mongodb.org.cn/drivers/2.html (该网站为mongoDB官方网站) php中使用mongodb你必须使用 mongodb 的 php驱 ...
- [转] Win10插入U盘后双击无法打开,无法访问,显示设备未就绪;驱动哥帮你解决
说起U盘,相信大家都不陌生. 这个不起眼的小东西在我们日常生活息息相关,一旦损坏可能就会造成一些不必要的麻烦. 最近驱动哥就收到了一大批用户关于U盘问题的相关咨询,一起来看看到底是什么情况吧! 据其中 ...