Protocol Buffer Marshal & Unmarshal

47 阅读4分钟

Protocol Buffers and Go

在本教程中,我们将了解如何在 GO 语言中使用协议缓冲区。

What is Protocol Buffer

协议缓冲区是以结构化格式存储数据的数据格式。协议缓冲区格式的数据可以用多种语言进行序列化和反序列化。你可以把它想象成 JSON、XML,但它有很多优点。

我们先来看一个最简单的协议缓冲文件示例。

person.proto

syntax = "proto3";

option go_package = "./";

message Person {
    string name = 1;
}

关于上述文件有几点需要注意。

  • 这个文件只是我们的协议缓冲区结构的蓝图。目前还没有关联的数据。这与JSON/XML不同,在JSON/XML中,文件也表示实际数据。
  • 在上面的文件中有一个人名消息,字段名类型为 string。“Proto3”意味着所编写的消息与 Protocol Buffer 版本 3 不兼容。
  • 从上面的示例中,您可以注意到与 JSON 的一个不同之处,即它具有类型信息。这种类型的信息将有助于用不同语言自动生成代码。让我们看看 Golang 自动生成的例子

Auto Generation of GO Code

我们可以使用上面的 person.proto 文件生成相应的 Golang 代码。但要做到这一点,我们需要安装一些必须的东西:

  • 首先安装协议缓冲区的 C++ 实现。每个平台都有自己的安装方式
  • Install Golang
  • Install protoc-gen-go(proto 用于自动生成 golang 代码而提供的库)
go get -u http://github.com/golang/protobuf/protoc-gen-go

安装完成后,cd 到包含 person.proto 文件的目录。运行这个命令:

protoc -I ./ --go_out=./ ./person.proto

它将在同一目录中生成一个名为 person.pb.go 的数据访问 go 文件。

现在最大的问题是这个 person.pb.go 文件是什么,它是由 protoc 使用 person.proto 自动生成的。首先要注意的几点:

  • Person 结构如下,看看如何利用 person.proto 文件的类型信息来知道 Name 是什么类型的字段Name是字符串
type Person struct {
	state         protoimpl.MessageState
	sizeCache     protoimpl.SizeCache
	unknownFields protoimpl.UnknownFields

	Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
}
  • 此外,proto包提供了对消息进行操作的函数,包括对二进制格式的转换和转换。

proto.Message接口定义了一个ProtoReflect方法。这个方法返回一个protoreflect.Message,它提供了一个基于反射的消息视图。

optimize_for选项不影响Go代码生成器的输出。

func (*Person) ProtoMessage() {}

func (x *Person) GetName() string {
	if x != nil {
		return x.Name
	}
	return ""
}

因此,这个自动生成的文件基本上为 Person 结构生成数据访问器,并提供允许将 Person 结构类型序列化到字节数据或从实际字节数据序列化/反序列化的方法。

我们编写一个 main.go 程序来实际创建 Person 结构的具体对象。

main.go

package main

import (
	"fmt"
	"io/ioutil"
	"log"

	proto3 "test/proto3"

	proto "github.com/golang/protobuf/proto"
)

func main() {
	person := &proto3.Person{Name: "XXX"}
	fmt.Printf("Person's name is %s\n", person.GetName())

	//Now lets write this person object to file
	out, err := proto.Marshal(person)
	if err != nil {
		log.Fatalf("Serialization error: %s", err.Error())
	}
	if err := ioutil.WriteFile("person.bin", out, 0644); err != nil {
		log.Fatalf("Write File Error: %s ", err.Error())
	}
	fmt.Println("Write Success")

	//Read from file
	in, err := ioutil.ReadFile("person.bin")
	if err != nil {
		log.Fatalf("Read File Error: %s ", err.Error())
	}
	person2 := &proto3.Person{}
	err2 := proto.Unmarshal(in, person2)
	if err2 != nil {
		log.Fatalf("DeSerialization error: %s", err.Error())
	}
	fmt.Println("Read Success")
	fmt.Printf("Person2's name is %s\n", person2.GetName())
}
  • 运行结果

image.png

  • 目录结构

image.png

其中 go.mod 定义为 test。

请注意,在上面的程序中我们

  • 我们将具体的 person 结构写入文件“person.bin”中。它是一个二进制文件,人类不可读。
  • 我们也从文件中读取,它能够成功地读取和打印“ Person2’s name is XXX”

令人惊讶的是,“person.bin” 文件只有 5 个字节,相比之下,如果您使用相同的数据创建一个 JSON 文件,其大小将超过 15 个字节。此外,与 JSON 文件的序列化和反序列化相比,从字节到具体对象(反之亦然)的 Marshal 和 UnMarshal 也非常快。

Advantage

  • 比相应的 JSON 和 XML 更清晰、更明确,因为它们还存储了类型信息。
  • 存储的数据相对较小,几乎小 2 - 3 倍。
  • 它要快得多。例如,使用协议缓冲区,序列化和反序列化速度更快
  • 自动代码生成 – 您编写一个协议缓冲区文件,然后自动生成相应的 GO 文件
  • GRPC 中使用了协议缓冲区,它是 REST 协议的下一代替代品

Conclusion

除了我们在文章中讨论的内容外,协议缓冲区还有很多其他功能。本文简要介绍了什么是协议缓冲区,以及与 JSON/XML 格式相比,协议缓冲区有哪些优势。