Go Protocol Buffer

[2023-11] 更新:本文整理自 Proto 官网,可以学习 go 如何使用 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";

接下来,您有消息定义。消息只是包含一组类型化字段的聚合。许多标准简单数据类型都可用作字段类型,包括 boolint32floatdoublestring。您还可以通过使用其他消息类型作为字段类型来向消息添加进一步的结构。

message Person {
  string name = 1;
  int32 id = 2;  // Unique ID number for this person.
  string email = 3;

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

  repeated PhoneNumber phones = 4;

  google.protobuf.Timestamp last_updated = 5;
}

enum PhoneType {
  PHONE_TYPE_UNSPECIFIED = 0;
  PHONE_TYPE_MOBILE = 1;
  PHONE_TYPE_HOME = 2;
  PHONE_TYPE_WORK = 3;
}

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

在上面的例子中,Person 消息包含 PhoneNumber 消息,而 AddressBook 消息包含 Person 消息。您甚至可以定义嵌套在其他消息中的消息类型 -​​ 正如您所看到的,类型 PhoneNumber 是在 Person

PhoneType 使用了 enum 类型,这是一种枚举类型。

如果字段为 repeated,则该字段可以重复任意次数(包括零次)。重复字段视为切片。

每个元素上的“= 1”、“= 2”标记标识该字段在二进制编码中使用的唯一“标记”。标签编号 1-15 需要比编号更高的标签少一个字节进行编码,因此作为一种优化,您可以决定将这些标签用于常用或重复的元素,而将标签 16 和更高的标签留给不太常用的可选元素。重复字段中的每个元素都需要重新编码标签编号,因此重复字段特别适合此优化。

如果未设置字段值, 则使用 默认值:数字类型为零,字符串为空字符串,布尔值为 false。对于嵌入消息,默认值始终是消息的“默认实例”或“原型”,其未设置任何字段。调用访问器来获取尚未显式设置的字段的值始终返回该字段的默认值。

您将在Protocol Buffer Language Guide 中找到编写文件的完整指南 .proto- 包括所有可能的字段类型 。不过,不要去寻找类似于类继承的设施——协议缓冲区不会这样做。

# 编译

现在您已经有了 .proto,接下来需要做的就是生成读取和写入 AddressBook(以及 Person 和 PhoneNumber)消息所需的类。为此,您需要编译 .proto

  1. 安装编译器, 请下载软件包

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

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

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

  3. 现在运行编译器,指定源目录(应用程序源代码所在的位置 - 如果不提​​供值,则使用当前目录)、目标目录(您希望生成的代码所在的位置;通常与 相同 $SRC_DIR) ,以及通往您的 的路径 .proto。在这种情况下,您将调用:

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

    因为您需要 Go 代码,所以您使用该 --go_out 选项 - 为其他支持的语言提供了类似的选项。

 protoc -I . helloworld.proto --go_out=plugins=grpc:.

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

# API

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

  • AddressBook 具有字段的结构 People
  • Person 具有 NameIdEmail 字段的结构 Phones
  • 一个 Person_PhoneNumber 结构体,包含 Number 和字段 Type
  • 为枚举中的每个值定义的类型 Person_PhoneType 和值 Person.PhoneType

您可以在Go 生成代码指南 中阅读更多有关生成内容的详细信息 ,但在大多数情况下,您可以将它们视为完全普通的 Go 类型。

list_people以下是命令单元测试 中的示例 ,说明如何创建 Person 的实例:

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

# 写入消息

使用协议缓冲区的全部目的是序列化数据,以便可以在其他地方解析它。在 Go 中,您可以使用 proto 库的 Marshal 函数来序列化协议缓冲区数据。指向协议缓冲区消息的指针 struct 实现该 proto.Message 接口。调用 proto.Marshal 返回协议缓冲区,以其有线格式编码。例如,我们在 add_person命令 中使用这个函数:

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 为协议缓冲区并将结果放入 中 book。因此,要解析 list_people命令 中的文件 ,我们使用:

// 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 中:

  • 不得更改任何现有字段的标签号。
  • 可以删除字段。
  • 可以添加新字段,但必须使用新的标签号(即在此协议缓冲区中从未使用过的标签号,甚至删除的字段也不行)。
使用 Hugo 构建
主题 StackJimmy 设计