TCP长链接通信解决方案记录

bufio

-带有缓存区的io

指定一个默认大小的缓冲区的io,进行读取,先从缓冲区读取,缓冲区中没数据之后再更新缓冲区。

默认缓冲区大小为4M,最小为16字节

其提供了更丰富的操作如

img

如Peek,读一行,读固定字节,读rune,读slice等等,丰富了操作。

我们的系统需要与对方系统同步数据,所以建立TCP长链接进行数据同步比数据轮询来得更有价值些。

TCP基于数据流协议,直接读取的字符流,相较于HTTP,还需要增加额外的HTTP header等不重要的数据进行传输。

那么这一过程有什么问题呢?

之前的思想是建立好连接之后,直接从io.read中进行读取固定字节大小的slice。

例如从连接中每次读取2M数据。

但是因为TCP中的数据只要不断开,就是无穷无尽的数据。

这就引入了,从无边界的数据,读取数据的问题。既然每次都读2M肯定是行不通的。

因为每次发送的数据不是2M,那么数据就必须要有分隔符。比如加入 \n \0等等。

通过网上的资料来查,这也不是一个好的办法。最终采用了,先传数据大小的header头信息 + body大小,进行计算,再读出指定大小的数据包大小的数据即可。

原本以为这是件很简单事:

创建连接 -》 读数据-》 计算body大小-》读body大小的数据-》解析数据(如存入缓存、队列来异步解析)-》再读header…以次类推。

问题来源:

  1. 连接的维护:TCP连接不是创建好了之后,就永远不会断开。

    比如有另外一条连接创建了,对方的服务端会主动断开之前的TCP连接。我们线上有有一次发生这个问题,由于某种事情,导致了部署两套实例。导致后面的数据都同步不过来了。

  2. 粘包等问题:

    1. 粘包是TCP的常见的问题。由于通信双方每次网卡发送的数据不一致,可能回导致发送的数据不被立即写出或等待一会儿一并写出。

    2. go中的io.read方法官方文档上已经明确指出。当我们想读取pack := make([]byte, 2048)读取2M的数据时。可能会返回

      0 <= n <= 2048的读数据大小。也明确指出:当可读区小于想读取的大小数据时,直接返回读到的数据去代替继续等待。所以

      这个地方栽跟头了。

      img

    3. 那么最终解决的问题是通过bufioio.readFull进行解决得。

    4. bufio会内部会维护一个缓冲区默认为4M,和w和r的变量,作为读到的和使用的指针下标。

      1. 先从缓冲区中读
      2. 缓冲区读取不到的话,就利用被封装了的io.read进行读取。
    5. 结合io.read问题,可能读取不全。内部利用了io.read去循环读取读取。

    TODO 是否可以对内核进行传递参数,如SOCKET_REUSE等