Go RPC(一)- Gobs of Data

Go的Gob包是一个专职于Go语言的高效,易用的编码系统,用于传输数据的包

1. 概述

要通过网络传输数据结构或将其存储在文件中,必须对其进行编码然后再次解码。当然有很多可用的编码:JSON,XML,Google的协议缓冲区(protocol buffers)等等。

现在还有另一个,由Go的gob包提供。为何需要gob包?

因为对于特定于Go的环境,例如在使用Go编写的两个服务器之间进行通信,则有机会构建更多内容更容易使用,可能更有效。

2. gob的主要目标

  • 易于使用:gobs永远不会与其他语言一起使用;因为Go有反射,所以不需要单独的接口定义语言或“协议编译器”;
  • 传输效率:以XML和JSON为例的文本表示太慢,无法置于高效通信网络的中心。二进制编码是必要的;
  • Gob流必须是自我描述的:即使在您忘记它代表的数据之后很久,也始终能够解码存储在文件中的gob流;

3. Pbf的功能缺陷

协议缓冲区对gob的设计有重大影响,但有三个特意被故意避免。(不考虑协议缓冲区不是自描述的属性:如果您不知道用于编码协议缓冲区的数据定义,则可能无法解析它。)

  • 首先,协议缓冲区仅适用于我们在Go中称为结构的数据类型。(Pbf您不能在顶层编码整数或数组,只能将其放入到对应的包含字段的结构。)
  • 其次,协议缓冲器定义可以指定每当编码或解码类型T的值时都需要存在字段T.x和T.y。(它们也是一个维护问题。随着时间的推移,人们可能希望修改数据定义以删除必填字段,但这可能会导致数据的现有客户端崩溃。)
  • 再次,是默认值问题。(Pbf的默认值增加了设计和实现的复杂性,Gob基于Go的默认值规则,它具有该类型的“零值” - 并且它不需要传输

所以gob最终看起来像一种通用的,简化的协议缓冲区。

4. gob值 - 忽略零值,类型可变

gob值类似于go中的常量,对于类似int8和int16没有做区分,同样针对有符合和无符号也未做区分,比如值7,字节传输过程中的值有符号和无符号被相同的传递,接收值针对值进行解码操作,因此编码的值可能是int8类型,接收值类型变为int64。这个值是一个整数,只要它适合,一切都有效(不适合转换的会报错),这种与变量大小的分离为编码提供了一些灵活性:我们可以随着软件的发展扩展整数变量的类型,但仍然能够解码旧数据。

这种灵活性也适用于指针。比如int8, *int8, **int8, ****int8

// 发送者编码(没有必要发送零值)
type T struct{ X, Y, Z int } // Only exported fields are encoded and decoded.
var t = T{X: 7, Y: 0, Z: 8}

// 接收者解码(仅存在的字段收到影响,Z字段被忽略)
type U struct{ X, Y *int8 } // Note: pointers to int8s
var u U

从整数我们可以构建所有其他类型:字节,字符串,数组,切片,映射,甚至浮点数。浮点值由它们的IEEE 754浮点位模式表示,存储为整数,只要您知道它们的类型就可以正常工作,我们总是这样做。顺便说一句,该整数以字节反转顺序发送,因为浮点数的常见值(如小整数)在低端有很多零,我们可以避免传输。

Go使得gobs的一个很好的特性是它们允许你通过让你的类型满足GobEncoderGobDecoder接口来定义自己的编码,类似于JSON包的MarshalerUnmarshaler,以及来自包fmt的Stringer接口。此工具可以在传输数据时表示特殊功能,强制执行约束或隐藏机密。

5. gob类型底层实现 - 首次传递包含类型描述,后续基于类型编码ID

第一次发送给定类型时,gob包在数据流中包含该类型的描述。实际上,所发生的是编码器用于以标准gob编码格式编码描述类型的内部结构并为其提供唯一编号。 (基本类型,以及类型描述结构的布局,由软件预定义用于自举。)在描述类型之后,可以通过其类型编号引用它。

因此,当我们发送第一个类型T时,gob编码器发送T的描述并用类型编号标记它,比如127.所有值(包括第一个)都以该数字为前缀,因此T值流看起来像:

// go结构体类型
type Node struct {
    Value       int
    Left, Right *Node
}

// 编码后,拥有ID127标识
("define type id" 127, definition of type T)(127, T value)(127, T value), ...

6. 编译机器 - 利用Go的反射构造

6.1. 编码

第一次编码给定类型的值时,gob包会构建一个特定于该数据类型的解释机器。

gob包使用类型的反射来构造该机器,但是一旦机器被构建,它就不依赖于反射。

该机器使用包不安全和一些技巧将数据高速转换为编码字节。它可以使用反射并避免不安全,但会明显变慢。 (Go的协议缓冲支持采用了类似的高速方法,其设计受到gob实现的影响。)

相同类型的后续值使用已编译的机器,因此可以立即对它们进行编码。

6.2. 解码

解码类似但更难。解码值时,gob包保存一个字节切片,表示要解码的给定编码器定义类型的值,以及要对其进行解码的Go值

gob包为这一对构建了一台机器:在线上发送的gob类型与提供解码的Go类型交叉。然而,一旦构建了解码机,它又是一个无反射的引擎,它使用不安全的方法来获得最大速度。

7. gob使用 - 高效,易用的编码系统,用于传输数据。

利用gob传输数据,需要做的就是为gob包提供值和变量,它可以完成所有的工作。

package main

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

// 发送数据类型
type P struct {
    X, Y, Z  int
    Name     string
    BirthDay time.Time
    Phone    string
    deleted  bool   //小写开头不会被导出
}

// 接收数据类型
type Q struct {
    X, Y *int32
    Name string
    BirthDay time.Time
    Phone    string
    deleted  bool
}

func main() {
    // Initialize the encoder and decoder.  Normally enc and dec would be
    // bound to network connections and the encoder and decoder would
    // run in different processes.

    // 网络连接IO
    var network bytes.Buffer // Stand-in for a network connection

    // 发送者编码:Encode (send) the value.
    p := P{3, 4, 5, "Pythagoras", time.Now(), "18503061234", true}
    enc := gob.NewEncoder(&network) // Will write to network.
    err := enc.Encode(p)
    if err != nil {
        log.Fatal("encode error:", err)
    }

    // 接收者解码:Decode (receive) the value.
    var q Q
    dec := gob.NewDecoder(&network) // Will read from network.
    err = dec.Decode(&q)
    if err != nil {
        log.Fatal("decode error:", err)
    }
    fmt.Printf("%q: {%d,%d, %s, %s, %v}\n", q.Name, *q.X, *q.Y, q.BirthDay, q.Phone, q.deleted)
}

8. 应用 - go的rpc包

pc软件包构建在gobs之上,可将此编码/解码自动化转换为传输,以便通过网络进行方法调用。

9. 参考