Protocol Buffer Basics: Go

为什么使用 protocol buffer?

我们将要使用的示例是一个非常简单的“地址簿”应用程序,它可以在文件中读取和写入人们的联系方式。
地址簿中的每个人都有一个姓名、一个 ID、一个电子邮件地址、一个联系电话号码。

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

  • 使用 gobs 序列化 Go 数据结构
    这在特定于 Go 的环境中是一个很好的解决方案,但是如果您需要与为其他平台编写的应用程序共享数据,它就不能很好地工作。

  • 您可以发明一种特殊方式将数据项编码为单个字符串
    例如将 4 个整数编码为“12:3:-23:67”。 这是一种简单而灵活的方法,尽管它确实需要编写一次性的编码和解析代码,并且解析会产生很小的运行时成本。 这最适合编码非常简单的数据。

  • 将数据序列化为 XML
    这种方法可能非常有吸引力,因为 XML(某种程度)是人类可读的,并且有许多语言的绑定库。 如果您想与其他应用程序/项目共享数据,这可能是一个不错的选择。 然而,众所周知,XML 是空间密集型的,对它进行编码/解码会对应用程序造成巨大的性能损失。

protocol buffer 是解决这个问题的灵活、高效、自动化的解决方案。
使用 protocol buffer,您可以编写要存储的数据结构的 .proto 描述
由此,protocol buffer 编译器创建了一个类,该类以高效的二进制格式实现 protocol buffer 数据的自动编码和解析。
生成的类为组成 protocol buffer 的字段提供 getter 和 setter,并将读取和写入 protocol buffer 的细节作为一个单元处理。
重要的是,protocol buffer格式支持随着时间的推移扩展格式的想法,这样代码仍然可以读取用旧格式编码的数据。

示例

https://github.com/protocolbuffers/protobuf/tree/main/examples

定义proto

要创建地址簿应用程序,您需要从 .proto 文件开始。
.proto 文件中的定义很简单:为每个要序列化的数据结构添加一条消息,然后为消息中的每个字段指定名称和类型。
在示例中,定义消息的 .proto 文件是 addressbook.proto。

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

syntax = "proto3";
package tutorial;

import "google/protobuf/timestamp.proto";

go_package 选项定义包的导入路径,该路径将包含此文件的所有生成代码。
Go 包名称将是导入路径的最后一个路径组件。
例如,示例将使用包名“tutorialpb”。

option go_package = "github.com/protocolbuffers/protobuf/examples/go/tutorialpb";

接下来,定义 message。 message 是包含一组类型字段的聚合。
许多标准的简单数据类型可用作字段类型,包括 bool、int32、float、double 和 string。还可以通过使用其它 message 类型作为字段类型,来为 message 添加更多结构。

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

编译 protocol buffers

现在您已经有了一个 .proto,接下来您需要生成读取和写入 AddressBook(以及由此产生的 Person 和 PhoneNumber)消息所需的类。 为此,您需要在 .proto 上运行protocol buffers 编译器 protoc:

  1. 如果您尚未安装编译器,请下载软件包并按照 README 中的说明进行操作。
    https://developers.google.cn/protocol-buffers/docs/downloads

  2. 运行以下命令安装 Go protocol buffers 插件

go install google.golang.org/protobuf/cmd/protoc-gen-go@latest

编译器插件 protoc-gen-go 将安装在 $GOBIN 中,默认为 $GOPATH/bin。 它必须在您的 $PATH 中,编译器 protoc 才能找到它。

  1. 现在运行编译器,指定源目录(应用程序的源代码所在的位置。如果不提供值,则使用当前目录)、目标目录(您希望生成的代码所在的位置;通常与 $SRC_DIR 相同),以及 .proto 的路径。
protoc -I=$SRC_DIR --go_out=$DST_DIR $SRC_DIR/addressbook.proto

这会在您指定的目标目录中生成 addressbook.pb.go。

The Protocol Buffer API

生成的 addressbook.pb.go 为您提供以下有用的类型:

  • AddressBook 结构体,包含 People;
  • Person 结构体, 包含 Name, Id, Email, Phones;
  • Person_PhoneNumber 结构体, 包含 Number, Type;
  • The type Person_PhoneType and a value defined for each value in the Person.PhoneType enum.
p := pb.Person{
        Id:    1234,
        Name:  "John Doe",
        Email: "[email protected]",
        Phones: []*pb.Person_PhoneNumber{
                {Number: "555-4321", Type: pb.Person_HOME},
        },
}

序列化

使用 protocol buffer 的目的是序列化您的数据,以便可以在其他地方对其进行解析。 在 Go 中,您使用 proto 库的 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)
}

反序列化

要解析编码消息,请使用 proto 库的 Unmarshal 函数。
调用它会将 in 中的数据解析为 protocol buffer 并将结果放入 book 中。

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

扩展 protocol buffer

在你发布使用你的 protocol buffer 的代码之后,迟早会想要“改进” protocol buffer 的定义。 如果您希望新buffer 向后兼容,并且您的旧 buffer 向前兼容。那么您需要遵循一些规则。 在新版本的协议缓冲区中:

  • 您不得更改任何现有字段的标签号。
  • 您可以删除字段。
  • 您可以添加新字段,但必须使用新的标签号(即从未在此 protocol buffer 中使用过的标签号,即使已删除的字段也不使用)。

如果您遵循这些规则,旧代码将愉快地阅读新消息并忽略任何新字段。 对于旧代码,已删除的单个字段将仅具有其默认值,而删除的重复字段将为空。 新代码也将透明地读取旧消息。

但是,请记住,旧消息中不会出现新字段,因此您需要对默认值做一些合理的事情。 使用特定类型的默认值:对于字符串,默认值为空字符串。 对于布尔值,默认值为 false。 对于数字类型,默认值为零。

你可能感兴趣的:(Protocol Buffer Basics: Go)