Go with Protobuf

go,with,protobuf · 浏览次数 : 0

小编点评

**代码结构** * addressbook.pb.go:包含People字段的AddressBook结构体 * addressbook.pb.marshal:用于序列化addressbook数据 * addressbook.pb.unmarshal:用于反序列化addressbook数据 **数据类型** * People:包含Id、Name、Email和Phones字段的Person * AddressBook:包含People字段的AddressBook结构体 **方法** * Marshal():将addressbook数据序列化为protocol buffers数据 * Unmarshal():从protocol buffers数据反序列化为addressbook数据 **测试** * list_people():读取并解析地址book文件 * add_person():创建新People实例并添加到AddressBook中 **其他** * 新版的protocol buffer中: *不能改变已有字段的序号 *删除重复字段 *新增repeated字段,必须使用新的序号 *支持多种扩展 * 使用时需要带简单的排版

正文

原文在这里

本教程为 Go 程序员提供了使用Protocol buffer的基本介绍。

本教程使用proto3向 Go 程序员介绍如何使用 protobuf。通过创建一个简单的示例应用程序,它向你展示了如何:

  • .proto中定义消息格式
  • 使用protocol buffer编译器
  • 使用Go protocol buffer API读写消息

这并不是protocol buffer在Go中使用的完整指南。更多细节,详见Protocol Buffer Language GuideGo API ReferenceGo Generated Code GuideEncoding Reference

为什么使用Protocol Buffer

我们要使用的例子是一个非常简单的“通讯录”应用程序,它可以从文件中读写联系人的信息。通讯录中每个人都有一个姓名、ID、邮箱和练习电话。

你如何序列化并取回这样结构化的数据呢?下面有几条建议:

  • 原始内存中数据结构可以发送/保存为二进制。这是一种随时间推移而变得脆弱的方法,因为接收/读写的代码必须编译成相同的内存布局,endianness等。另外,文件已原始格式积累数据和在网络中到处传输副本,因此扩展这种格式十分困难。
  • 你可以编写已临时的方法来讲数据元素编码到单个字符串中 --- 例如用“12:3:-23:67”来编码4个int。这是一种简单而灵活的方法,尽管它确实需要编写一次性的编码和解析代码,并且解析会增加少量的运行时成本。这对于编码非常简单的数据最有效。
  • 序列化为XML。这种方法非常有吸引力,因为XML(某种程度上)是人类可读的,而且有许多语言的绑定库。如果你希望与其他应用程序/项目共享数据,这可能是一个不错的选择。然而,XML是出了名的空间密集型,对它进行编码/解码会给应用程序带来巨大的性能损失。而且,在XML DOM树中导航要比在类中导航简单字段复杂得多。

Protocol buffers是解决这个问题的灵活、高效、自动化的解决方案。使用Protocol buffers,你编写一个描述要存储的数据结构的.proto文件。然后,Protocol buffer编译器会创建一个类,该类实现了Protocol buffer数据的自动编码和解析,使用高效的二进制格式。生成的类为构成Protocol buffer的字段提供了获取器和设置器,并处理了读取和写入Protocol buffer的细节。重要的是,Protocol buffer格式支持随着时间的推移扩展格式的想法,以使代码仍然能够读取使用旧格式编码的数据。

从哪能找到示例代码呢?

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

你可以从这里下载。

定义Protocol文件

通讯录程序从定义.proto文件开始。.proto文件中的定义很简单:为要序列化的每个数据结构添加一个message,然后为消息中的每个字段指定名称和类型。在我们的示例中,定义消息的.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。消息只是一个包含一组类型化字段的聚合。许多标准简单数据类型都可用作字段类型,包括boolint32floatdoublestring。你也可以通过使用其他消息类型作为字段类型来为消息添加更多结构。

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

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

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

在上面例子中,Person消息包含PhoneNumber消息,同时Person消息包含在AddressBook消息中。你甚至可以定义消息类型嵌套在其它消息中 --- 就像上面PhoneNumber定义在Person中。你也可以定义enum类型,如果你想让你的字段只是用预定义列表中的一个值 --- 这里你想声明的电话类型可以是MOBILEHOMEWORK其中之一。

“= 1”,“= 2”标记每个字段在二进制编码中的唯一的“tag”。序号1-15编码的字节数比较高的数字少一位,因此,作为一种优化,你可以决定对常用或重复的元素使用这些标记,而对不常用的可选元素使用标记16或更高。重复字段中的每个元素都需要重新编码标记号,因此重复字段是此优化的特别好的候选项。

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

如果字段是repeated的,那么该字段可以重复任意次数(包括零次)。重复值的顺序将由protocol buffer处理。可以将重复字段视为动态大小的数组。

你可以在Protocol Buffer语言指南中找到撰写.proto文件的完整指南,包括所有可能的字段类型。但不要寻找类继承类似的功能 - 因为protocol buffer不支持这一点。

编译Protocol Buffers

现在你已经有.proto文件了,接下来你需要生成读写AddressBook(包括PersonPhoneNumber)消息的类。现在,你需要运行protocol buffer编译器protoc

  • 如果你还没安装编译器,可从这里下载并根据README编译安装。
  • 使用如下命令按照Go protocol buffers插件:
    $ go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
    
    protoc-gen-go编译器插件将安装在$GOBIN中,默认为$GOPATH/bin。protocol buffer编译器protoc必须能够在你的$PATH中找到它。
  • 现在运行编译器,指明源目录(应用程序源文件目录,不指定的话默认使用当前目录),目标路径(你要存放生成的代码的目录,通常与$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结构体
  • 包含NameIdEmailPhones字段的People
  • 包含NumberType字段的Person_PhoneNumber
  • 自定义枚举类型的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},
    },
}

创建Message

使用protocol buffers的目的是将数据序列化,以便在其他地方进行解析。在 Go 中,你可以使用proto库的Marshal函数来序列化你的protocol buffers数据。protocol buffers消息的结构体指针实现了proto.Message接口。调用proto.Marshal返回编码后的protocol buffers数据。例如,我们在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)
}

读取Message

要解析已编码的消息,可以使用proto库的Unmarshal函数。调用此函数将数据解析为protocol buffers,并将结果放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定义。如果你想新的buffer可以被后向兼容,并且旧的buffer可以被前向兼容,--- 你确实想这样做 --- 那你需要遵守下面的规则。在新版的protocol buffer中:

  • 必须不能改变已有字段的序号。
  • 可以删除repeated字段。
  • 可以新增repeated字段,但必须使用新的序号(序号在protocol buffer中没被用过,也没被删除)。

还有一些其它的扩展要遵守,但很少会用到它们。

遵循这些规则,旧代码将可以轻松地读取新的消息,并且会忽略任何新字段。对于旧代码来说,已删除的单字段将只是它们的默认值,而已删除的重复字段将为空。新代码也可以透明地读取旧消息。

但请记住,旧消息中不会包含新字段,因此你需要合理地处理默认值。使用类型特定的默认值:对于字符串,默认值是空字符串。对于布尔值,默认值是false。对于数值类型,默认值是零。


孟斯特

声明:本作品采用署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0)进行许可,使用时请注明出处。
Author: mengbin
blog: mengbin
Github: mengbin92
cnblogs: 恋水无意


与Go with Protobuf相似的内容:

Go with Protobuf

原文在这里。 本教程为 Go 程序员提供了使用Protocol buffer的基本介绍。 本教程使用proto3向 Go 程序员介绍如何使用 protobuf。通过创建一个简单的示例应用程序,它向你展示了如何: 在.proto中定义消息格式 使用protocol buffer编译器 使用Go pro

Go微服务开发指南

在这篇深入探讨Go语言在微服务架构中的应用的文章中,我们介绍了选择Go构建微服务的优势、详细分析了主要的Go微服务框架,并探讨了服务发现与注册和API网关的实现及应用。 关注TechLead,复旦博士,分享云服务领域全维度开发技术。拥有10+年互联网服务架构、AI产品研发经验、团队管理经验,复旦机器

Go 使用原始套接字捕获网卡流量

Go 使用原始套接字捕获网卡流量 Go 捕获网卡流量使用最多的库为 github.com/google/gopacket,需要依赖 libpcap 导致必须开启 CGO 才能够进行编译。 为了减少对环境的依赖可以使用原始套接字捕获网卡流量,然后使用 gopacket 的协议解析功能,这样就省去了解析

Go 如何对多个网络命令空间中的端口进行监听

Go 如何对多个网络命令空间中的端口进行监听 需求为 对多个命名空间内的端口进行监听和代理。 刚开始对 netns 的理解不够深刻,以为必须存在一个新的线程然后调用 setns(2) 切换过去,如果有新的 netns 那么需要再新建一个线程切换过去使用,这样带来的问题就是线程数量和 netns 的数

【建议收藏】Go语言关键知识点总结

容器 数组和切片 在Go语言中,数组和切片是两个基本的数据结构,用于存储和操作一组元素。它们有一些相似之处,但也有许多不同之处。下面我们详细介绍数组和切片的特点、用法以及它们之间的区别。 数组 数组是固定长度的序列,存储相同类型的元素。数组的长度在定义时就固定下来,不能改变。 package mai

如何基于R包做GO分析?实现秒出图

GO分析 基因本体论(Gene Ontology, GO)是一个用于描述基因和基因产品属性的标准术语体系。它提供了一个有组织的方式来表示基因在生物体内的各种角色。基因本体论通常从三个层面对基因进行描述:细胞成分(Cellular Component,CC)、生物学过程(Biological Proc

Go变量作用域精讲及代码实战

关注作者,复旦AI博士,分享AI领域与云服务领域全维度开发技术。拥有10+年互联网服务架构、AI产品研发经验、团队管理经验,同济本复旦硕博,复旦机器人智能实验室成员,国家级大学生赛事评审专家,发表多篇SCI核心期刊学术论文,阿里云认证的资深架构师,项目管理专业人士,上亿营收AI产品研发负责人。 精讲

2024-06-05:用go语言,给定三个正整数 n、x 和 y, 描述一个城市中由 n 个房屋和 n 条街道连接的情况。 城市中存在一条额外的街道连接房屋 x 和房屋 y。 需要计算对于每个街道数(

2024-06-05:用go语言,给定三个正整数 n、x 和 y, 描述一个城市中由 n 个房屋和 n 条街道连接的情况。 城市中存在一条额外的街道连接房屋 x 和房屋 y。 需要计算对于每个街道数(从 1 到 n), 有多少房屋对满足从一个房屋到另一个房屋经过的街道数正好为该街道数。 在结果数组中

go高并发之路——go语言如何解决并发问题

一、选择GO的原因 作为一个后端开发,日常工作中接触最多的两门语言就是PHP和GO了。无可否认,PHP确实是最好的语言(手动狗头哈哈),写起来真的很舒爽,没有任何心智负担,字符串和整型压根就不用区分,开发速度真的是比GO快很多。现在工作中也还是有一些老项目在使用PHP,但21年之后的新项目基本上就都

2024-04-27:用go语言,在一个下标从 1 开始的 8 x 8 棋盘上,有三个棋子,分别是白色车、白色象和黑色皇后。 给定这三个棋子的位置,请计算出要捕获黑色皇后所需的最少移动次数。 需要注意

2024-04-27:用go语言,在一个下标从 1 开始的 8 x 8 棋盘上,有三个棋子,分别是白色车、白色象和黑色皇后。 给定这三个棋子的位置,请计算出要捕获黑色皇后所需的最少移动次数。 需要注意的是,白色车可以垂直或水平移动,而白色象可以沿对角线移动,它们不能跳过其他棋子。 如果白色车或白色象