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:對應花括號{}。