自定义协议和Golang实现

写在这里一是做一下总结在忘的时候方便查看,二是如果文章有错误请各位大佬喷我哈哈,欢迎指正。如果能帮到别人也挺好的。

协议

所谓协议就是指定一系列规则,这些规则使想要交流的双方或多方可以正常通信交流。如我们说的汉语就是协议,如果不按照汉语规则说话,交流的人就听不懂对方说什么,汉语和英语就是不同的协议,用汉语和只会英语的人交流,人家也听不懂你说啥。在计算机中,入参出参是协议,最简单的服务端rest接口是协议,系统实现一层层的网络协议使计算机可以互相通信。

自定义协议

自定义协议就是在现有协议满足不了我们需求时,在现有协议之上构建的满足我们自己程序的通信需求的协议。目前的操作系统,会把我们应用需要的底层网络协议实现好,这对我们来说是透明的,但是在我们写rest时,是不是需要定义接口呢,这接口就是我们自定义的一种协议,通过定义请求参数,响应参数,让客户端和我们写好的服务端程序进行交互,这就是自定义的协议。

定义自定义协议

在TCP基础上自定义协议,主要就是通信双方规定一个头,头后面是传递的数据。头中规定一些参数,比如传递的消息前10个字节是头,头最前面2个字节是个固定魔数,让你知道我是根据这个协议来通信的,如果不支持这个协议的话,就可以直接关掉这个连接了。跟着魔数的4个字节是整个包中数据的长度,这个长度不包括头,需要长度的原因是TCP协议面向的是流,会把应用原本一个一个发送的包一股脑的扔给对方,如果不用长度字段,我们就很有可能拿到了多个包的数据。最后4个字节是个校验码,校验收到的包的数据是否有错误,主要是防止通信时被篡改。
此时我们就完成了我们自定义协议的定义。只需要发送端在将数据写入TCP前,在数据前面加入这个头,接收端接收到数据时,按照这个头的定义拿取数据,就可以完成双方的通信了。

GO中定义协议

自定义了一个Conn结构体,结构体中包含连接,元数据,scan是用来从真正的socket中获取数据,通过socket连接new一个scan,然后通过scan的split方法注册一个分割数据的函数,这里包装在了RegisterSplitFunc()函数中,scan会通过我们注册函数中的分割方式来分割数据,通过Scan()方法分割一次数据,获取返回数据使用scan.Bytes()

type Conn struct {
    c    net.Conn
    meta metadata.Metadata
    scan *bufio.Scanner
}

func (c *Conn) Read() (b []byte, err error) {
    if c.c == nil {
        return nil, errors.New("conn is nil")
    }

    if c.scan == nil {
        //包装过的函数,做了一个scan.Split(splitFunc)的操作
        c.RegisterSplitFunc()
    }

    if c.scan.Scan() {
        b = c.scan.Bytes()
    } else {
        c.c.Close()
        err = errors.New("conn scan error")
    }
    return
}
//write方法中的头由模数0x0102, 长度len(data)+4组成
//头后面跟着数据
func (c *Conn) Write(data []byte) (err error) {
    if len(data) > math.MaxUint16 {
        return errors.New("data too big")
    }

    buf := bytes.Buffer{}
    binary.Write(&buf, binary.BigEndian, []byte{0x01,0x02})
    binary.Write(&buf, binary.BigEndian, uint16(len(data)+4))
    binary.Write(&buf, binary.BigEndian, data)
    _, err = c.c.Write(buf.Bytes())
    return
}

分割函数

func MySplitFunc(data []byte, atEOF bool) (advance int, token []byte, err error) {
    //判断长度,魔数
    if len(data) < 4 || data[0] != 0x01 || data[1] != 0x02 {
        err = errors.New("protocol error")
        return
    }

    var l uint16
    //获取header
    err = binary.Read(bytes.NewReader(data[2:4]), binary.BigEndian, &l)
    if err != nil {
        logs.Error("binary.Read error(%v)", err)
        return
    }

    //通过长度读取数据,advance为读取的长度,包括头和数据,data是读取的数据
    if int(l) <= len(data) {
        advance, token, err = int(l), data[:int(l)], nil
    }
    if atEOF {
        err = errors.New("EOF")
    }

    return
}

总结

以上为在golang中自定义简单通信协议的方式。