go处理tcp粘包问题

背景

在一次压测的过程中,一直发现客户端丢包,但是服务器端打日志检查了相关的回包是一定下发了,因为是tcp的连接,是可靠连接,不会出现丢包的现象,想了很久一直没发现什么问题,后面通过打印包长度确定了出现了tcp粘包的情况。读取函数如下:

reply := make([]byte, 10240)
lenData, err := conn.Read(reply)

读取的数据去解包的时候,会发现一直缺少一些包,当延迟越小的时候,出现的概率越大,因为延迟小,意味着回包越快,出现粘包的概率就越大。后面经过查询资料,发现了bufio.Scanner完美解决了这个问题,不需要我们手工去写切割包的代码,这里有一个前提:一个完整包里面需要有字段去标志你这个完整包的大小,这样才能切割粘包。好了,说了这个多看看代码。

解决方案

reply := make([]byte, 10240)
lenData, err := conn.Read(reply)
result := bytes.NewBuffer(nil)
result.Write(reply[0:lenData])
scanner := bufio.NewScanner(result)
split := func(data []byte, atEOF bool) (advance int, token []byte, err error) {
    if !atEOF  {
              // 这里写你的具体切割逻辑
        var plen int
        binary.Read(bytes.NewReader(data[0:10]), binary.BigEndian, &plen)
        plen = int(binary.LittleEndian.Uint32(data[0:10]))
        return plen, data[:plen], nil
    }
    return
}
scanner.Split(split)
for scanner.Scan() {
        // 这里的scanner.Bytes()就是切割出来的每一个数据,对应这个split函数里面的第二个返回值
    fmt.Println("recv:", string(scanner.Bytes()))
}

这样就完美解决了粘包的问题

原理

官方给的解释是:

SplitFunc is the signature of the split function used to tokenize the input. The arguments are an initial substring of the remaining unprocessed data and a flag, atEOF, that reports whether the Reader has no more data to give. The return values are the number of bytes to advance the input and the next token to return to the user, if any, plus an error, if any.
Scanning stops if the function returns an error, in which case some of the input may be discarded.
Otherwise, the Scanner advances the input. If the token is not nil, the Scanner returns it to the user. If the token is nil, the Scanner reads more data and continues scanning; if there is no more data--if atEOF was true--the Scanner returns. If the data does not yet hold a complete token, for instance if it has no newline while scanning lines, a SplitFunc can return (0, nil, nil) to signal the Scanner to read more data into the slice and try again with a longer slice starting at the same point in the input.
The function is never called with an empty data slice unless atEOF is true. If atEOF is true, however, data may be non-empty and, as always, holds unprocessed text.
package "buffio"

函数原型:

type SplitFunc func(data []byte, atEOF bool) (advance int, token []byte, err error)

Scanner是有缓存的,意思是Scanner底层维护了一个Slice用来保存已经从Reader中读取的数据,Scanner会调用我们设置SplitFunc,将缓冲区内容(data)和是否已经输入完了(atEOF)以参数的形式传递给SplitFunc,而SplitFunc的职责就是根据上述的两个参数返回下一次Scan需要前进几个字节(advance),分割出来的数据(token),以及错误(err)。参考https://studygolang.com/articles/15474
这个bufio是个挺有意思的库,里面还有大量的api,有时间可以详细了解一下,bufio官方文档

待完成

这些只解决了粘包的现象,有关于半包的情况并没有找到好的库去解决,半包情况还需要看一下

你可能感兴趣的:(go处理tcp粘包问题)