2017-06-02

关于TCP粘包和拆包的终极解答

Views: 35244 | 1 Comment

程序员行业有一些奇怪的错误的观点(误解),这些误解非常之流行,而且持有这些错误观点的人经常言之凿凿,打死也不相信自己有错,实在让人啼笑皆非。究其原因,还是因为这些错误观点所对应的正确观点不符合人的正常思维习惯,是扭曲人的直观感受的。

有两个错误观点非常之经典,一而再,再而三的出现,就跟韭菜一样,割不完,还越长越多。一是经典的"服务器最多65536个连接"误解,打开链接看介绍。另一个就是这里要讲的TCP"粘包"和"拆包"问题。

基于前面的思路,我们先介绍人的正常思维习惯,然后再介绍扭曲的正确观点,这样你就会印象深刻了。

首先,人的正常思维习惯是,数据传输应该是基于报文的,人们不论是对话还是写信,肯定是一句一句的话或者一封一封的信,这就是所谓的报文。而 TCP 是什么东西呢?TCP 是一种流式协议,简单说,你使用它的时候,根本就没有所谓的报文,无论是聊天说的话还是发的图片,对于 TCP 来说,通通都是没有边界的,TCP 根本不认识这些东西,不知道你按换行时是一句话,你发的图片是一张图片,所有的东西都是数据流没有任何边界。这显然不符合人的正常思维,是扭曲的。

而无论是人的思维,还是实际的东西,一定是要有边界的。这就有矛盾了。

所以,对于程序员,在使用 TCP 之前,你必须进行报文协议设计。要么你使用别人设计好的报文协议,要么你自己设计。如果你自己不设计,也不使用别人设计好的,那么你肯定错了,一定会遇到所谓的粘包和拆包。

关于报文协议设计,这篇文章有介绍 http://www.ideawu.net/blog/archives/429.html

一般的程序员当然不愿意自己设计,一是没能力,二是不需要重新发明。那么设计完之后呢,必然是实现。因为自身水平的问题,还是会遇到 TCP 粘包和拆包问题。所以,我的建议是,不要自己去实现,应该去使用别人已经写好的代码。

接下来讲一讲如果自己直接使用 TCP 的接口,为什么会遇到粘包和拆包问题呢?TCP 的接口简单说只有两个:

send(data, length);
recv(buff);

不懂的人会基于人的正常思维想当然地认为 send() 就是发送报文,recv() 就是接收报文。但是,前面已经提到了,TCP 是没有所谓的报文边界的,所以你错了。为什么错?因为 send() 和 recv() 不是一一对应的。也就是说,send() 的调用次数和 recv() 的调用次数是独立的,有时是相等的(你在自己机器上和内网测试时基本都是相等的),有时是不相等的(互联网上非常容易出现)。

现在明白了吧,TCP 的 send() 和 recv() 不是一一对应的,理所当然会粘应用层的包,拆应用层的包。明白了之后怎么解决?简单,用别人封装好的代码。或者,你自己慢慢琢磨去吧!

正确地从TCP socket中读取报文的代码必须长这个样子,如果不长这个样子,就是错的!

char recv_buf[];
Buffer buffer;
// 网络循环:必须在一个循环中读取网络,因为网络数据是源源不断的。
while(1){
    // 从TCP流中读取不定长度的一段流数据,不能保证读到的数据是你期望的长度
    tcp.read(recv_buf);
    // 将这段流数据和之前收到的流数据拼接到一起
    buffer.append(recv_buf);
    // 解析循环:必须在一个循环中解析报文,避免所谓的粘包
    while(1){
        // 尝试解析报文
        msg = parse(buffer);
        if(!msg){
            // 报文还没有准备好,糟糕,我们遇到拆包了!跳出解析循环,继续读网络。
            break;
        }
        // 将解析过的报文对应的流数据清除
        buffer.remove(msg.length);
        // 业务处理
        process(msg);
    }
}

这段代码包含两个循环:网络循环和解析循环。

注意了!如果你的代码不长这个样子,绝对是错的!一定是错的!不要狡辩!正确的做法只有一种,错误的做法成千上万。

如果你想真正的解决所谓的TCP粘包和拆包问题,你写的代码必须和我的一样。如果不一样,就是错的,说明你根本就没弄懂。

Related posts:

  1. Master-Workers 模式处理高负载
  2. 通过 HTTP POST 发送二进制数据
  3. 数据传输中的停止等待机制的实现
  4. 使用 Channel 进行可靠传输
  5. Windows Python select标准输入输出
Posted by ideawu at 2017-06-02 15:02:56

One Response to "关于TCP粘包和拆包的终极解答"

Leave a Comment