Protocol Buffer Basics: Go

Protocol Buffer Basics: Go

Mac安装protobuf

  1. 安装protobuf
wget https://raw.githubusercontent.com/Homebrew/homebrew-core/b89ad43b8f71daa8455b71b35a316abc549f0057/Formula/protobuf.rb
brew install ./protobuf.rb

# 查看版本
➜ protoc --version                        
libprotoc 3.21.9

mac使用brew安装指定版本的 protoc

  1. 安装其他依赖
# 安装protoc-gen-go
go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.28.1 
# 安装protoc-gen-go-prpc
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
# 安装protoc-gen-go-http
go install github.com/go-kratos/kratos/cmd/protoc-gen-go-http/v2@latest
# 安装protoc-gen-openapi
go install github.com/google/gnostic/cmd/protoc-gen-openapi@latest


# 查看版本
➜ protoc-gen-go --version                                        
protoc-gen-go v1.28.1

原文:

机器人
摘要
lomtom

这篇文章介绍了如何使用 Protocol Buffers(协议缓冲区)的 proto3 🔗版本来编写 Go 程序。通过创建一个简单的示例应用程序,展示了如何

  • 定义 .proto 文件中的消息格式
  • 使用协议缓冲区编译器
  • 使用 Go 协议缓冲区 API 来读写消息。

这不是一个全面的 Go 中使用协议缓冲区的指南,更详细的参考资料请参阅 Protocol Buffer Language Guide 🔗Go API Reference 🔗Go Generated Code Guide 🔗Encoding Reference 🔗

简介

我们将使用一个非常简单的“地址簿”应用程序作为示例,该程序可以读取和写入人员的联系信息到文件中。地址簿中的每个人都有一个名字、一个 ID、一个电子邮件地址和一个联系电话。

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

  • 使用gobs 🔗序列化 Go 数据结构。这是在纯 Go 环境中的一个好解决方案,但如果你需要与其他平台的应用程序共享数据,则不太适用。
  • 你可以发明一种临时的方法将数据项编码到一个单独的字符串中——例如将 4 个整数编码为“12:3:-23:67”。这是一种简单灵活的方法,尽管它需要编写一次性的编码和解析代码,并且解析会带来少量的运行时开销。这最适合编码非常简单的数据。
  • 将数据序列化为 XML。这种方法非常有吸引力,因为 XML(某种程度上)是人类可读的,并且有许多语言的绑定库。如果你想与其他应用程序/项目共享数据,这可能是一个不错的选择。然而,XML 以占用空间大而闻名,编码/解码它可能会对应用程序造成巨大的性能损失。此外,导航 XML DOM 树比通常在类中导航简单字段要复杂得多。

协议缓冲区是解决这个问题的灵活、高效、自动化的方案。使用协议缓冲区,你编写一个 .proto 描述文件来描述你想要存储的数据结构。从这个文件中,协议缓冲区编译器会创建一个类,该类自动实现协议缓冲区数据的编码和解析,使用高效的二进制格式。生成的类为构成协议缓冲区的字段提供 getter 和 setter,并且处理读取和写入协议缓冲区作为一个单元的细节。重要的是,协议缓冲区格式支持随时间扩展格式的概念,这样代码仍然可以读取使用旧格式编码的数据。

示例代码

我们的示例是一组命令行应用程序,用于管理使用协议缓冲区编码的地址簿数据文件。add_person_go 命令向数据文件添加新条目。list_people_go 命令解析数据文件并将数据打印到控制台。

你可以在 GitHub 仓库的 examples 🔗中目录中找到完整的示例。

定义Protocol Format

要创建你的地址簿应用程序,你需要从一个 .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";

接下来,你有你的消息定义。消息只是一个聚合,包含一组类型化的字段。许多标准简单数据类型可作为字段类型,包括 bool、int32、float、double 和 string。你还可以通过使用其他消息类型作为字段类型来给你的消息添加进一步的结构。

message Person {
  string name = 1;
  int32 id = 2;  // 这个人的唯一 ID 号码。
  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;
}

// 我们的地址簿文件只是其中之一。
message AddressBook {
  repeated Person people = 1;
}

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

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

如果字段是repeated,字段可以重复任意次数(包括零)。在协议缓冲区中,重复值的顺序将被保留。将重复字段视为动态大小的数组。

你可以在 Protocol Buffer Language Guide 🔗. 中找到编写 .proto 文件的完整指南——包括所有可能的字段类型。

不要寻找类似类继承的功能,因为协议缓冲区不提供该功能。

编译 Protocol Buffers

现在您有了.proto,接下来需要做的是生成读取和写入AddressBook(以及PersonPhoneNumber)消息所需的类。为此,您需要protoc在 上运行协议缓冲区编译器.proto

  1. 如果你还没有安装编译器,请下载软件包并按照 README 中的说明进行操作。

  2. 运行以下命令安装 Go 协议缓冲区插件:

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

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

这将在你指定的目标目录中生成 github.com/protocolbuffers/protobuf/examples/go/tutorialpb/addressbook.pb.go

Protocol Buffer API

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

  • 一个带有 People 字段的 AddressBook 结构。
  • 一个带有 Name、Id、Email 和 Phones 字段的 Person 结构。
  • 一个带有 Number 和 Type 字段的 Person_PhoneNumber 结构。
  • 类型 Person_PhoneType 以及 Person.PhoneType 枚举中的每个值定义。

你可以在 Go Generated Code guide 🔗中阅读更多关于生成内容的详细信息,但大多数情况下,你可以将它们视为完全普通的 Go 类型。

以下是来自 list_people command’s unit tests 🔗 中的示例,展示如何创建 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{}
// ...
// 将新地址簿写回磁盘。
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 命令中解析文件,我们使用:

// 读取现有的地址簿。
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)
}

扩展协议缓冲区

发布使用你的协议缓冲区的代码后不久,你无疑会想要“改进”协议缓冲区的定义。如果你想让你的新缓冲区向后兼容,旧缓冲区向前兼容——你几乎肯定想要这样——那么你需要遵守一些规则。在新的协议缓冲区版本中:

  • 你不得更改任何现有字段的标签号。
  • 你可以删除字段。
  • 你可以添加新字段,但必须使用新的标签号(即从未在此协议缓冲区中使用过的标签号,甚至不要使用已删除的字段)。

(这些规则有 一些例外 🔗,但很少使用。)

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

然而,请记住,新字段将不会出现在旧消息中,因此你需要对默认值做一些合理的处理。使用类型特定的 默认值 🔗:对于字符串,默认值是空字符串。对于布尔值,默认值是 false。对于数值类型,默认值是零。

lomtom

标题:Protocol Buffer Basics: Go

转载:https://protobuf.dev/getting-started/gotutorial