随笔 - 112  文章 - 19 评论 - 49 trackbacks - 0

预备知识

Go中的接口的数据结构可以分为两部分:

  1. 其中一部分指向或者存储了原始数据的值
  2. 另一部分指向或者存储了原始数据的类型描述符(其中包含类型,以及对应于接口中的TT快三方法 )

所以大体上TT快三TT快三我 们 可以粗略的认为接口内部存储了原始数据的值和类型。
更详细的可以看一下Go数据结构-接口

正文

json模块一共三个文件,分别是 msg.go pack.go process.go,总共300行左右的代码量,虽然不多,但确实有许多较为深的点的。

三个文件一起看,总共就一个接口一个结构体。

接口是Message,一个空接口没啥好看的。

再来看结构体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有很多TT快三方法 ,比较简单的像:

// 注册,注意这里看出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中填充数据以及设置唯一的整型字段的值。

剩余的几个TT快三方法 最重要的就是PackreadMsgunpack这三个TT快三方法 ,其余的都是添头了。
先来看一下PackTT快三方法 :

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写入

来看一下readMsgTT快三方法 :

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
}

看完PackTT快三方法 后,再看这个就不难理解了。这个TT快三方法 基本上就是三步走:

  1. 消息的第一个字节表示消息类型,读取后检测这个消息类型是否是合法的(是否被注册过)
  2. 消息的第二个和第三个字节表示数据长度,读出来后检测该长度是否有效
  3. 知道了长度后,就把对应长度的数据读出来放到buffer中

所以Pack后的数据一般需要readMsg来读取。

接下来再看unpackTT快三方法 :

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读取的数据加以处理得到其对应的结构。这个TT快三方法 有些东西,一开始看的TT快三我 一脸懵逼,主要是对Go中的反射reflect不熟,后来看了看这个Go 语言反射三定律,TT快三我 才了解了这些东西。首先msgIn肯定是一个Message接口类型的对象,假如其是nil的话,那TT快三TT快三我 们 根据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空接口。绕了这么一大圈,TT快三TT快三我 们 知道:现在msg接口中两部分中值是t原始类型的零值,类型是t原始类型的指针类型。

最后,将buffer中的数据解析出来赋给msg,并返回。

其余的TT快三方法 基本上都是调用了这三个TT快三方法 中的某个或者某几个

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
}

在外层TT快三TT快三我 们 基本上只用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. 接上一点:其读处理方式则是将读取到的字节序列,按照写入的顺序读取并解析出来,返回给上层调用的代码。
posted on 2019-04-15 14:41 MnCu 阅读(...) 评论(...) 编辑 收藏