frp中的json模塊

預備知識

Go中的接口的數據結構能夠分爲兩部分:html

  1. 其中一部分指向或者存儲了原始數據的值
  2. 另外一部分指向或者存儲了原始數據的類型描述符(其中包含類型,以及對應於接口中的方法)

因此大致上咱們能夠粗略的認爲接口內部存儲了原始數據的值和類型。
更詳細的能夠看一下Go數據結構-接口git

正文

json模塊一共三個文件,分別是 msg.go pack.go process.go,總共300行左右的代碼量,雖然很少,但確實有許多較爲深的點的。github

三個文件一塊兒看,總共就一個接口一個結構體。json

接口是Message,一個空接口沒啥好看的。segmentfault

再來看結構體MsgCtl以及其生成函數:數據結構

type MsgCtl struct {
    typeMap     map[byte]reflect.Type
    typeByteMap map[reflect.Type]byte

    maxMsgLength int64
}

func NewMsgCtl() *MsgCtl {
    return &MsgCtl{
        typeMap:      make(map[byte]reflect.Type),
        typeByteMap:  make(map[reflect.Type]byte),
        maxMsgLength: defaultMaxMsgLength,
    }
}

感受彷佛也很簡單,兩個map一個整型。MsgCtl有不少方法,比較簡單的像:函數

// 註冊,注意這裏看出typeMap和typeByteMap是相互對應的。並且容量只有256個
func (msgCtl *MsgCtl) RegisterMsg(typeByte byte, msg interface{}) {
    msgCtl.typeMap[typeByte] = reflect.TypeOf(msg)
    msgCtl.typeByteMap[reflect.TypeOf(msg)] = typeByte
}

func (msgCtl *MsgCtl) SetMaxMsgLength(length int64) {
    msgCtl.maxMsgLength = length
}

分別是向map中填充數據以及設置惟一的整型字段的值。編碼

剩餘的幾個方法最重要的就是PackreadMsgunpack這三個方法,其他的都是添頭了。
先來看一下Pack方法:指針

func (msgCtl *MsgCtl) Pack(msg Message) ([]byte, error) {
    // 1
    typeByte, ok := msgCtl.typeByteMap[reflect.TypeOf(msg).Elem()]
    if !ok {
        return nil, ErrMsgType
    }
    
    // 2
    content, err := json.Marshal(msg)
    if err != nil {
        return nil, err
    }

    // 3
    buffer := bytes.NewBuffer(nil)
    buffer.WriteByte(typeByte)
    binary.Write(buffer, binary.BigEndian, int64(len(content)))
    buffer.Write(content)
    return buffer.Bytes(), nil
}
  1. 獲取msg的結構體類型以及其對應'標示字節(就是typeByteMap鍵值對中的值)',首先通常來講:msg參數中的類型通常是一個結構體實例的指針類型,因此reflect.TypeOf(msg).Elem()返回的是這個結構體類型
  2. 解析爲json
  3. 先將標示字節寫入,而後將json的長度按大端寫入,最後將json寫入

來看一下readMsg方法:code

func (msgCtl *MsgCtl) readMsg(c io.Reader) (typeByte byte, buffer []byte, err error) {
    // 1
    buffer = make([]byte, 1)
    _, err = c.Read(buffer)
    if err != nil {
        return
    }
    typeByte = buffer[0]
    if _, ok := msgCtl.typeMap[typeByte]; !ok {
        err = ErrMsgType
        return
    }
    
    // 2
    var length int64
    err = binary.Read(c, binary.BigEndian, &length)
    if err != nil {
        return
    }
    if length > msgCtl.maxMsgLength {
        err = ErrMaxMsgLength
        return
    } else if length < 0 {
        err = ErrMsgLength
        return
    }

    // 3
    buffer = make([]byte, length)
    n, err := io.ReadFull(c, buffer)
    if err != nil {
        return
    }

    if int64(n) != length {
        err = ErrMsgFormat
    }
    return
}

看完Pack方法後,再看這個就不難理解了。這個方法基本上就是三步走:

  1. 消息的第一個字節表示消息類型,讀取後檢測這個消息類型是不是合法的(是否被註冊過)
  2. 消息的第二個和第三個字節表示數據長度,讀出來後檢測該長度是否有效
  3. 知道了長度後,就把對應長度的數據讀出來放到buffer中

因此Pack後的數據通常須要readMsg來讀取。

接下來再看unpack方法:

func (msgCtl *MsgCtl) unpack(typeByte byte, buffer []byte, msgIn Message) (msg Message, err error) {
    if msgIn == nil {
        t, ok := msgCtl.typeMap[typeByte]
        if !ok {
            err = ErrMsgType
            return
        }

        msg = reflect.New(t).Interface().(Message)
    } else {
        msg = msgIn
    }

    err = json.Unmarshal(buffer, &msg)
    return
}

unpack通常是將readMsg讀取的數據加以處理獲得其對應的結構。這個方法有些東西,一開始看的我一臉懵逼,主要是對Go中的反射reflect不熟,後來看了看這個Go 語言反射三定律,我才瞭解了這些東西。首先msgIn確定是一個Message接口類型的對象,假如其是nil的話,那咱們根據typeByte找出對應的類型,而後就是複雜的這一句了:
msg = reflect.New(t).Interface().(Message)t是一個reflect.Type類型的接口實例,reflect.New(t)則會返回一個reflect.Value類型的結構體實例,但注意:這個Value的類型是t的原始類型的指針類型,值則是該類型的零值reflect.New(t).Interface()會將reflect.Value這個實例中真正對應的值以及其指針類型轉換爲空接口而後返回,緊接着後面又跟了.(Message)將空接口轉換爲Message空接口。繞了這麼一大圈,咱們知道:如今msg接口中兩部分中值是t原始類型的零值,類型是t原始類型的指針類型。

最後,將buffer中的數據解析出來賦給msg,並返回。

其他的方法基本上都是調用了這三個方法中的某個或者某幾個

func (msgCtl *MsgCtl) UnPack(typeByte byte, buffer []byte) (msg Message, err error) {
    return msgCtl.unpack(typeByte, buffer, nil)
}

func (msgCtl *MsgCtl) ReadMsg(c io.Reader) (msg Message, err error) {
    typeByte, buffer, err := msgCtl.readMsg(c)
    if err != nil {
        return
    }
    return msgCtl.UnPack(typeByte, buffer)
}

func (msgCtl *MsgCtl) WriteMsg(c io.Writer, msg interface{}) (err error) {
    buffer, err := msgCtl.Pack(msg)
    if err != nil {
        return
    }

    if _, err = c.Write(buffer); err != nil {
        return
    }
    return nil
}

在外層咱們基本上只用ReadMsgWriteMsg來讀取數據就能夠了。

用法

package main

import (
    "fmt"
    jsonMsg "github.com/fatedier/golib/msg/json"
)

const (
    TypeMsgOne = '1'
    TypeMsgTwo = '2'
)

var msgTypeMap = map[byte]interface{}{
    TypeMsgOne: MsgOne{},
    TypeMsgTwo: MsgTwo{},
}

var msgCtl *jsonMsg.MsgCtl


type MsgOne struct {}

type MsgTwo struct {}

type EchoWriter struct {}

func (EchoWriter)Write(p []byte) (n int, err error) {
    fmt.Println(p)
    fmt.Println(string(p))
    return len(p), nil
}

func init() {
    msgCtl = jsonMsg.NewMsgCtl()
    for typeByte, msg := range msgTypeMap {
        msgCtl.RegisterMsg(typeByte, msg)
    }
}

func main() {
    msgCtl.WriteMsg(EchoWriter{}, &MsgOne{})

}

運行後結果是

[49 0 0 0 0 0 0 0 2 123 125]
1{}

首先是字節49:表示字符串1;而後是佔了8個字節的0 0 0 0 0 0 0 2:表示長度2;最後是字節123和125:對應花括號{}。

總結

  1. 總體來看json模塊就是對結構體編解碼處理,本質上和go官方的json模塊無區別。詳細的說該json模塊提供了對特定的(被註冊的)結構體(通常是結構體,固然其餘的也能夠。)的存儲或者傳輸(能夠理解爲讀取寫入buffer)的處理
  2. 接上一點:其寫處理方式則是將該結構體類型對應的byte、該結構體json序列化後的長度、以及該結構體編碼後的字節序列按照順序寫入
  3. 接上一點:其讀處理方式則是將讀取到的字節序列,按照寫入的順序讀取並解析出來,返回給上層調用的代碼。
相關文章
相關標籤/搜索