原文在这里

本教程为 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的更多相关文章

  1. python通过protobuf实现rpc

    由于项目组现在用的rpc是基于google protobuf rpc协议实现的,所以花了点时间了解下protobuf rpc.rpc对于做分布式系统的人来说肯定不陌生,对于rpc不了解的童鞋可以自行g ...

  2. Protobuf使用规范分享

    一.Protobuf 的优点 Protobuf 有如 XML,不过它更小.更快.也更简单.它以高效的二进制方式存储,比 XML 小 3 到 10 倍,快 20 到 100 倍.你可以定义自己的数据结构 ...

  3. java netty socket库和自定义C#socket库利用protobuf进行通信完整实例

    之前的文章讲述了socket通信的一些基本知识,已经本人自定义的C#版本的socket.和java netty 库的二次封装,但是没有真正的发表测试用例. 本文只是为了讲解利用protobuf 进行C ...

  4. 在Wcf中应用ProtoBuf替代默认的序列化器

    Google的ProtoBuf序列化器性能的牛逼已经有目共睹了,可以把它应用到Socket通讯,队列,Wcf中,身为dotnet程序员一边期待着不久后Grpc对dotnet core的支持更期待着Wc ...

  5. protobuf的编译安装

    github地址:https://github.com/google/protobuf支持多种语言,有多个语言的版本,本文采用的是在centos7下编译源码进行安装. github上有详细的安装说明: ...

  6. 编译protobuf的jar文件

    1.准备工作 需要到github上下载相应的文件,地址https://github.com/google/protobuf/releases protobuf有很多不同语言的版本,因为我们需要的是ja ...

  7. protobuf学习(2)-相关学习资料

    protobuf官方git地址 protobuf官方英文文档   (你懂的需要FQ) protobuf中文翻译文档 protobuf概述          (官方翻译 推荐阅读) protobuf入门 ...

  8. google protobuf安装与使用

    google protobuf是一个灵活的.高效的用于序列化数据的协议.相比较XML和JSON格式,protobuf更小.更快.更便捷.google protobuf是跨语言的,并且自带了一个编译器( ...

  9. c# (ENUM)枚举组合类型的谷歌序列化Protobuf

    c# (ENUM)枚举组合类型的谷歌序列化Protobuf,必须在序列化/反序列化时加上下面: RuntimeTypeModel.Default[typeof(Alarm)].EnumPassthru ...

  10. dubbox 增加google-gprc/protobuf支持

    好久没写东西了,今年实在太忙,基本都在搞业务开发,晚上来补一篇,作为今年的收官博客.google-rpc 正式发布以来,受到了不少人的关注,这么知名的rpc框架,不集成到dubbox中有点说不过去. ...

随机推荐

  1. 从源码级剖析Java类加载原理

    相信大多数熟悉Java的研发工程师,都知道Java类加载原理:Java中的类是由类加载器采用双亲委派机制进行加载.其中,Java核心库中实现了三种类型的类加载器,它们分别是:引导类加载器Bootstr ...

  2. WPF入门教程系列二十八 ——DataGrid使用示例MVVM模式(6)

    WPF入门教程系列目录 WPF入门教程系列二--Application介绍 WPF入门教程系列三--Application介绍(续) WPF入门教程系列四--Dispatcher介绍 WPF入门教程系 ...

  3. Go-变量篇

    一.变量的声明方式(三种) 1.var a int = num 2.var a = num 3.a := num 二.字符类型使用细节 *Golang的字符使用UTF-8. 英文 -1 字节:汉字-3 ...

  4. 前端Vue自定义顶部搜索框 热门搜索 历史搜索 用于搜索跳转使用

    前端Vue自定义顶部搜索框 热门搜索 历史搜索 用于搜索跳转使用, 下载完整代码请访问uni-app插件市场地址:https://ext.dcloud.net.cn/plugin?id=13128 效 ...

  5. FPGA加速技术在游戏和娱乐系统中的应用:实现高效的游戏和娱乐系统

    目录 1. 引言 2. 技术原理及概念 3. 实现步骤与流程 4. 应用示例与代码实现讲解 <35. FPGA加速技术在游戏和娱乐系统中的应用:实现高效的游戏和娱乐系统>这篇文章是一篇针对 ...

  6. Terraform 系列-使用 for-each 对本地 json 进行迭代

    系列文章 Terraform 系列文章 Grafana 系列文章 概述 前文 Grafana 系列 - Grafana Terraform Provider 基础 介绍了使用 Grafana Terr ...

  7. 记一次DNS问题排查

    一.问题:域名flow.nzkong.com解析很慢: 排查过程 抓包分析:tcpdump -i eth0 -n -s 500 port domain 1 14:40:44.548553 IP 10. ...

  8. GGTalk 开源即时通讯系统源码剖析之:数据库设计

    自从<开源即时通讯GGTalk 8.0发布,增加Linux客户端,支持在统信UOS.银河麒麟上运行!>一文在博客园发布后,有园友联系我QQ,说能不能整理个更系统更详细地介绍GGTalk源码 ...

  9. 【Docker】离线安装

    离线安装Docker 1.下载docker 离线安装包 ​ 下载地址如下:Index of linux/static/stable/x86_64/ 2.将下载的包上传至服务器上 我这里下载的是20.1 ...

  10. 使用官方推荐的库来测react hook组件

    最近写单元测试的时候遇见了一些问题,当我使用使用jest测React. useRef,  React. useEffect时,总是测不到, 然后我去查阅了一下官方文档,它推荐了使用下面这个库 @tes ...