乐趣区

自定义协议和Golang实现

自定义协议和 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 中自定义简单通信协议的方式。

退出移动版