87

自定义协议和Golang实现

 4 years ago
source link: https://www.tuicool.com/articles/aaqAVvE
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

自定义协议和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.LittleEndian, []byte{0x01,0x02})
    binary.Write(&buf, binary.LittleEndian, 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] != 0x88 || data[1] != 0x94 {
        err = errors.New("protocol error")
        return
    }

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

    l := uint16(header)
    //做大小端转换
    l = (l >> 8) | (l << 8)
    //通过长度读取数据,advance为读取的长度,包括头和数据,data是读取的数据
    if int(l) <= len(data) {
        advance, token, err = int(l), data[:int(l)], nil
    }
    if atEOF {
        err = errors.New("EOF")
    }

    return
}

总结

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


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK