自定义协议和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中自定义简单通信协议的方式。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。