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: "jdoe@example.com",
        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。 对于数字类型,默认值为零。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,509评论 6 504
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,806评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,875评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,441评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,488评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,365评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,190评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,062评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,500评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,706评论 3 335
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,834评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,559评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,167评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,779评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,912评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,958评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,779评论 2 354

推荐阅读更多精彩内容