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_encode
与 json_decode
所做的那样。
关于 Gob 编解码规则我们这里做一个简单的介绍,对 Gob 而言,发送方和接受方的数据结构并不需要完全一致,以官方示例为例:
上述 struct { A, B int }
结构编码的数据可以被后面 9 种结构类型接收解码,具体来说,接收数据结构只要满足与发送数据结构签名一致(与顺序无关,不能类型之间不能相互编解码,整型还要细分为有符号和无符号)、或者是发送数据类型的子集(但不能为空)或超集,即可正常接收并解码。
具体到不同的数据类型,规则如下:
struct
、array
、slice
是可以被编码的,但是 function
和 channel
是不能被编码的;uint
来编码的,0
是 false
,1
是 true
;float64
类型的值来编码的,浮点型和整型也是不能相互编解码的;string
和 []byte
)是以无符号字节个数 + 每个字节编码的形式编解码的;slice
和 array
)是按照无符号元素个数 + 每个数组元素编码的形式进行编解码的;map
)是按照无符号元素个数 + 键值对这样的形式进行编解码的;struct
)是按照序列化的属性名 + 属性值来进行编解码的,其中属性值是其自己对应类型的 Gob 编码,如果有一个属性值为 0
或空,则这个属性直接被忽略,每个属性的序号是由编码时的顺序决定的,从 0
开始顺序递增。struct
在序列化前会以 -1
代表序列化的开始,以 0
代表序列化结束,即 struct
的序列化是按照 “-1 (0 属性1的名字 属性1的值) (1 属性2的名字 属性2的值) 0 ”来进行编码的。
最后,需要注意的是 struct
类型中的属性名都应该以大写字母开头,以便可以在包外被访问。
注:更多 Gob 编解码规则细节可以参考 encoding/gob 包文档:https://golang.google.cn/pkg/encoding/gob/ 和官方教程博客:https://blog.golang.org/gobs-of-data。
下面我们来看一个简单的 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 编解码,执行上述代码,打印结果如下:
与 JSON 或 XML 这种基于文本描述的数据交换格式不同,Gob 是二进制编码的数据流,因此性能和传输效率更高,并且 Gob 流是可以自解释的,从而具备了完整的表达能力。
但是,作为针对 Go 语言的数据结构编解码专用序列化工具,意味着 Gob 无法跨语言使用,只能仅局限于基于 Go 语言开发的 RPC 客户端与服务端进程间通信,然而,大多数时候,我们用 Go 语言编写的 RPC 服务端,可能更希望它是通用的,与语言无关的,无论是 PHP、Python、Java 或其他编程语言实现的 RPC 客户端,均可与之通信。面对这种情况,我们需要对 net/rpc
包底层的编解码工具进行自定义,改用跨语言的 JSON 或者 Protobuf 进行数据格式序列化,关于编解码工具的自定义,我们放到下一篇教程给大家详细介绍。