Go中的接口的数据结构能够分为两部分:html
因此大致上咱们能够粗略的认为接口内部存储了原始数据的值和类型。
更详细的能够看一下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中填充数据以及设置惟一的整型字段的值。编码
剩余的几个方法最重要的就是Pack
和readMsg
和unpack
这三个方法,其他的都是添头了。
先来看一下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 }
reflect.TypeOf(msg).Elem()
返回的是这个结构体类型来看一下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
方法后,再看这个就不难理解了。这个方法基本上就是三步走:
因此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 }
在外层咱们基本上只用ReadMsg
和WriteMsg
来读取数据就能够了。
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:对应花括号{}。