RPC 编程(二):默认的编解码工具 Gob 使用介绍

Gob 简介

Gob 是 Go 语言的一个序列化数据结构的编码解码工具,在 Go 标准库中内置了 encoding/gob 包以供使用。一个数据结构使用 Gob 进行序列化之后,能够用于网络传输,因此它的典型适用场景就是 RPC 编程,我们在上篇教程也提到了 net/rpc 包默认使用 encoding/gob 进行编解码,以 rpc.Client 为例,其初始化代码如下:

func NewClient(conn io.ReadWriteCloser) *Client {
	encBuf := bufio.NewWriter(conn)
	client := &gobClientCodec{conn, gob.NewDecoder(conn), gob.NewEncoder(encBuf), encBuf}
	return NewClientWithCodec(client)
}

发送端会在发送消息之前使用 gob.Encoder 对数据进行编码,接收端在收到消息后会通过 gob.Decoder 对数据进行解码,就像 PHP 中 json_encodejson_decode 所做的那样。

Gob 编解码规则

关于 Gob 编解码规则我们这里做一个简单的介绍,对 Gob 而言,发送方和接受方的数据结构并不需要完全一致,以官方示例为例:

Gob 编解码规则

上述 struct { A, B int } 结构编码的数据可以被后面 9 种结构类型接收解码,具体来说,接收数据结构只要满足与发送数据结构签名一致(与顺序无关,不能类型之间不能相互编解码,整型还要细分为有符号和无符号)、或者是发送数据类型的子集(但不能为空)或超集,即可正常接收并解码。

具体到不同的数据类型,规则如下:

  • structarrayslice 是可以被编码的,但是 functionchannel 是不能被编码的;
  • 整型分为有符号和无符号,无符号和有符号整型是不能互相编解码的;
  • 布尔类型是被当作 uint 来编码的,0false1true
  • 浮点型的值都是被当作 float64 类型的值来编码的,浮点型和整型也是不能相互编解码的;
  • 字符串类型(包含 string[]byte)是以无符号字节个数 + 每个字节编码的形式编解码的;
  • 数组类型(包含 slicearray)是按照无符号元素个数 + 每个数组元素编码的形式进行编解码的;
  • 字典类型(map)是按照无符号元素个数 + 键值对这样的形式进行编解码的;
  • 结构体类型(struct)是按照序列化的属性名 + 属性值来进行编解码的,其中属性值是其自己对应类型的 Gob 编码,如果有一个属性值为 0 或空,则这个属性直接被忽略,每个属性的序号是由编码时的顺序决定的,从 0 开始顺序递增。struct 在序列化前会以 -1 代表序列化的开始,以 0 代表序列化结束,即 struct 的序列化是按照 “-1 (0 属性1的名字 属性1的值) (1 属性2的名字 属性2的值) 0 ”来进行编码的。

最后,需要注意的是 struct 类型中的属性名都应该以大写字母开头,以便可以在包外被访问。

注:更多 Gob 编解码规则细节可以参考 encoding/gob 包文档和 官方教程博客

Gob 编解码示例

下面我们以看一个简单的 Gob 编解码实现示例:

package main

import (
	"bytes"
	"encoding/gob"
	"fmt"
	"log"
)

type P struct {
	X, Y, Z int
	Name    string
	Tags    []string
	Attr    map[string]string
}

type Q struct {
	X, Y *int32
	Name string
	Tags    []string
	Attr    map[string]string
}

func main() {
	var network bytes.Buffer
	enc := gob.NewEncoder(&network)  // 初始化编码器 gob.Encoder
	dec := gob.NewDecoder(&network)  // 初始化解码器 gob.Decoder
	// 数据编码(发送数据时)
	err := enc.Encode(P{3, 4, 5, "学院君", []string{"PHP", "Laravel", "Go"}, map[string]string{"webiste": "https://xueyuanjun.com"}})
	if err != nil {
		log.Fatal("encode error:", err)
	}
	// 数据解码(收到数据时)
	var q Q
	err = dec.Decode(&q)
	if err != nil {
		log.Fatal("decode error:", err)
	}
	fmt.Printf("%q: {%d,%d}, Tags: %v, Attr: %v\n", q.Name, *q.X, *q.Y, q.Tags, q.Attr)
}

其中涵盖了整型、字符串、切片、字典以及结构体类型的 Gob 编解码,执行上述代码,打印结果如下:

Gob 编解码示例

Gob 的优点与不足

与 JSON 或 XML 这种基于文本描述的数据交换格式不同,Gob 是二进制编码的数据流,因此性能和传输效率更高,并且 Gob 流是可以自解释的,从而具备了完整的表达能力。

但是,作为针对 Go 语言的数据结构编解码专用序列化工具,意味着 Gob 无法跨语言使用,只能仅局限于基于 Go 语言开发的 RPC 客户端与服务端进程间通信,然而,大多数时候,我们用 Go 语言编写的 RPC 服务端,可能更希望它是通用的,与语言无关的,无论是 PHP、Python、Java 或其他编程语言实现的 RPC 客户端,均可与之通信。面对这种情况,我们需要对 net/rpc 包底层的编解码工具进行自定义,改用跨语言的 JSON 或者 Protobuf 进行数据格式序列化,关于编解码工具的自定义,我们放到下一篇教程给大家详细介绍。

上一篇: RPC 编程(一):客户端与服务端 RPC 调用的简单实现

下一篇: 没有下一篇了