zoukankan      html  css  js  c++  java
  • frp中的json模块

    预备知识

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

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

    所以大体上我们可以粗略的认为接口内部存储了原始数据的值和类型。
    更详细的可以看一下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有很多方法,比较简单的像:

    // 注册,注意这里看出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方法:

    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. 接上一点:其读处理方式则是将读取到的字节序列,按照写入的顺序读取并解析出来,返回给上层调用的代码。
  • 相关阅读:
    使用JDBC连接MySql时出现:The server time zone value '�й���׼ʱ��' is unrecognized or represents more than one time zone. You must configure either the server or JDBC driver (via the serverTimezone configuration
    Mysql Lost connection to MySQL server at ‘reading initial communication packet', system error: 0
    mysql-基本命令
    C# 监听值的变化
    DataGrid样式
    C# 获取当前日期时间
    C# 中生成随机数
    递归和迭代
    PHP 时间转几分几秒
    PHP 根据整数ID,生成唯一字符串
  • 原文地址:https://www.cnblogs.com/MnCu8261/p/10710546.html
Copyright © 2011-2022 走看看