本教程使用proto3版本的protocol buffer语言,提供了一个基本的在Go程序中使用protocol buffer的介绍。经过建立一个简单的示例应用程序,向你展现如何git

.proto

它不是一个全面的在Go中使用protocol buffer的指南,更详细的参考信息请查看前面的两个教程。github

为何使用protocol buffer

咱们将要使用的示例是一个很是简单的“地址簿”应用程序,能够在文件中读取和写入人员的联系人详细信息。地址簿中的每一个人都有姓名,ID,电子邮件地址和联系电话号码。数组

如何序列化和检索这样的结构化数据?有几种方法能够解决这个问题:数据结构

  • 使用gobs(Go中自定义的序列化编码格式)序列化Go数据结构。这是Go特定环境中的一个很好的解决方案,但若是须要与为其余平台编写的应用程序共享数据,它将没法正常工做。
  • 能够发明一种特殊的方法将数据项编码为单个字符串 - 例如将4个整数编码为“12:3:-23:67”。这是一种简单而灵活的方法,虽然它确实须要编写一次性编码和解析代码,而且解析会产生较小的运行时成本。这最适合编码很是简单的数据。
  • 将数据序列化为XML。这种方法很是有吸引力,由于XML(有点)是人类可读懂的,而且有许多语言都有相应的类库。若是您想与其余应用程序/项目共享数据,这多是一个不错的选择。然而,XML是众所周知的空间密集型,而且编码/解码它会对应用程序形成巨大的性能损失。此外,导航XML DOM树比一般在类中导航简单字段要复杂得多。
.proto

得到示例程序

add_person_golist_people_go

下载这些文件到你的项目目录中:优化

定义协议格式

.proto

.proto文件以包声明开头,这有助于防止不一样项目之间的命名冲突。

syntax = "proto3";
package tutorial;

import "google/protobuf/timestamp.proto";
.proto

接下来,是消息定义。消息只是包含一组类型字段的聚合。许多标准的简单数据类型均可用做字段类型,包括bool,int32,float,double和string。您还可使用其余消息类型做为字段类型,为消息添加更多结构。

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;
}
PersonPhoneNumberAddressBookPersonPhoneNumberPersonMOBILEHOMEWORK

每一个元素上的“= 1”,“= 2”标记标识该字段在二进制编码中使用的惟一“标记”。标签号1-15编码时比更大编号少须要一个字节,所以做为优化,您能够决定将这些标签用于经常使用或重复的元素,将标签16和更高标签留给不太经常使用的可选元素。重复字段中的每一个元素都须要从新编码标记号,所以重复字段特别适合此优化。

若是未设置字段值,则使用默认值:数字类型为零,字符串为空字符串,bools为false。对于嵌入式消息,默认值始终是消息的“默认实例”或“原型”,其中没有设置其字段。调用访问器以获取还没有显式设置的字段的值始终返回该字段的默认值。

若是一个字段是可重复的,该字段能够重复任意次数(包括零)。重复值的顺序将保留在protocol buffer中。将可重复字段视为变长数组。

您将在Protobuf语言指南中找到编写.proto文件的完整指南 - 包括全部可能的字段类型。不要去寻找类继承相似的东西,protocol buffer不支持这些。

编译protocol buffers

.proto
go get -u github.com/golang/protobuf/protoc-gen-go
protoc -I=$SRC_DIR --go_out=$DST_DIR $SRC_DIR/addressbook.proto
pb.gopb "github.com/protocolbuffers/protobuf/examples/tutorial"
protoc --go_out=$GOPATH/src/github.com/protocolbuffers/protobuf/examples/tutorial ./addressbook.proto
$GOPATH/src/github.com/protocolbuffers/protobuf/examples/tutorial

Protocol buffer API

生成addressbook.pb.go提供如下有用类型:

  • 拥有有People字段的AddressBook结构体。
  • 拥有Name,Id,Email和Phones字段的Person结构体。
  • Person_PhoneNumber结构体,包含Number和Type字段。
  • 类型Person_PhoneType和为Person.PhoneType枚举中的每一个值定义的常量。

能够阅读更多有关“生成代码”指南中生成的内容的详细信息,但在大多数状况下,您能够将这些视为彻底普通的Go类型。

addressbook.pb.go

下面是如何建立Person实例的示例:

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

在Go中序列化protocol buffer数据

protoMarshalproto.Messageproto.Marshal
book := &pb.AddressBook{}
// ...

// 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)
}

在Go中解析protocol buffer

protoUnmarshal
// 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)
}

运行Go应用程序

go build add_person.gogo build list_people.goadd_personlist_people./add_person ADDRESS_BOOKADDRESS_BOOK./list_peopleADDRESS_BOOKPerson