原文在这里

本教程为 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. 获取邮箱(QQ、126、163)的客户端授权码

    获取QQ邮箱的客户端授权码 1.首先登录QQ邮箱(https://mail.qq.com),然后,点击"设置"按钮. 2.点击"账户"按钮,进入"账户 ...

  2. 浏览器手动设置Cookie

    浏览器手动设置Cookie js代码: document.cookie="{KEY}={Value}":  可多次执执行.

  3. 基于FFMPEG+SDL的简单的视频播放器分析

    基于FFMPEG+SDL的简单的视频播放器分析 前言 最近看了雷霄骅前辈的博客<最简单的基于FFMPEG+SDL的视频播放器 ver2 (采用SDL2.0)>,参照他的代码,在window ...

  4. 2023-07-10:Kafka如何做到消息不丢失?

    2023-07-10:Kafka如何做到消息不丢失? 答案2023-07-10: Kafka采用多种机制来确保消息的不丢失,其中包括副本机制.ISR(In-Sync Replicas)机制以及ACK机 ...

  5. CMU15-445 Project4 Concurrency Control心得

    一.概述 过瘾!过瘾!过瘾!P4 真过瘾!写 P3 的博客时我说过"感觉自己在数据库方面真正成长了",但写完 P4 之后最大的感受就是,我终于理解了 andy 在第一课说过的&qu ...

  6. 聊聊又拍云存储 S3 协议的使用

    近期,有细心的同学发现,在又拍云控制台中的云存储产品中增加了针对 S3 协议标准的兼容支持,授权用户通过 S3 协议标准对存储空间的数据进行读写操作.此配置操作之前是由人工协助的方式提供给用户使用的, ...

  7. Django基本数据库操作

    Django基本数据库操作 @ 目录 Django基本数据库操作 ‍内容一:基本数据库配置 ‍内容二:ORM基本操作 ‍内容一:基本数据库配置 Django是一个流行的Python Web框架,它可以 ...

  8. ElementUi使用el-tooltip实现超出部分显示省略号

    效果前: 效果后: 创建myTooltio.vue组件 <template> <div class="tooltip-container"> <el- ...

  9. Oracle快速拷贝数据

    游标拷贝数据 根据条件进行数据拷贝 -- 游标方式拷贝数据 DECLARE CURSOR cur IS SELECT * FROM JACKPOT WHERE TO_CHAR(JACKPOT.CREA ...

  10. js面向对象编程,你需要知道这些

    javascript中对象由key和value组成,key是标识符,value可以为任意类型 创建对象的方式 1.通过构造函数 var obj = new Object() obj.name = 'a ...