Netty指南(4)--- TCP粘包/拆包

前言

本节我们会介绍TCP的粘包和拆包,什么是TCP粘包/拆包,怎么解决粘包问题,以及代码演示。

TCP粘包/拆包介绍

TCP是个”流”协议,没有界限,因为TCP底层并不了解上层业务数据的具体含义,它会根据TCP缓冲区的实际情况进行包的划分。
一个完整的数据包可能会被TCP拆分成多个包进行发送,也可能把多个小的包封装成一个大的数据包发送,这就是TCP粘包/拆包的由来。

上图以两个完整数据包(D1、D2)为例,演示了可能发生的4种情况,分别如下:

  1. 服务器分两次读到了两个独立数据包,分别是D1和D2,没有发生粘包和拆包。
  2. 服务器一次接受到了两个数据包,D1和D2粘在了一起,被称为TCP粘包。
  3. 服务器分两次读到了两个数据包,第一次读到了完整的D1+部分D2,第二次读到了D2的剩余部分,被称为TCP拆包。
  4. 服务器分两次读到了两个数据包,第一次读到了D1的一部分,第二次读到了D1剩余部分+D2完整包,这种即发生了粘包现象也发生了拆包现象。

TCP粘包/拆包问题的原因

导致发生TCP粘包/拆包现象的原因有三个,分别如下:

  1. 应用程序write写入的字节数量大于套接口发送缓冲区的大小。
  2. 进行MSS(maximum segment size,最大分节大小,为TCP数据包每次传输的最大数据分段大小,一般由发送端向对端TCP通知对端在每个分节中能发送的最大TCP数据。)大小的TCP分段。
  3. 以太网帧的payload大于MTU(maximum transmission unit,最大传输单元,由硬件规定,如以太网的MTU为1500字节,其中包括 IPHeader(20Byte)TCPHeader(20Byte))进行IP分片。

TCP粘包/拆包的解决方案

由于底层TCP无法理解上层业务数据,所以底层无法保证数据不被拆分和重组,这个问题只能通过上层的应用协议栈设计来解决,有如下几种解决方案:

  1. 将消息长度固定,例如将消息长度len = 100,累计读到100字节后就认为读到了一个完整的消息。
  2. 将回车换行符(System.getProperty(“line.separator”))作为消息结束符,例如FTP协议,这种方式在问本协议中应用比较广泛。
  3. 将特殊的分割符作为消息的结束标志,回车换行符就是一种特殊的结束分隔符。
  4. 将消息分为消息头和消息体,消息头中包含消息总长度或消息体长度的字段。

未处理TCP粘包导致的功能异常案例

我们之前提供的所有示例代码中都没有包含,TCP粘包/拆包的处理,在功能测试上一般不会出现问题,但是一旦客户端增多服务器的压力增加,或者发送较大数据包时就会出现粘包/拆包问题。
如果程序没有处理粘包/拆包,往往会出现解码错误的错误,导致程序不能正常工作。
我为了制造TCP粘包/拆包的问题,将客户端向服务器发送消息的代码块增加了循环,发送100次消息给服务器,并增加了计数。
点击查看Netty未处理TCP粘包导致的功能异常案例完整示例代码
启动上面代码的客户端和服务器查看结果,你会发现服务器只收到了一个消息,并回复了一个应答,由于客户端发送的消息发生粘包,不满足时间服务器查询条件,所以返回了 “QUERY FAIL”。

处理了TCP粘包的正常功能案例

我们再看处理了TCP粘包/拆包的程序,由于我们的实例程序比较简单,只操作字符串,因此代码中我在客户端发给服务器的消息和服务器返回给客户端的消息后面增加了换行分隔符(System.getProperty(“line.separator”)),并且使用了Netty内置的解码器 LineBasedFrameDecoder 来解决TCP粘包问题。
为了解决TCP粘包/拆包导致的读写问题,Netty默认提供了多种编解码器用于处理粘包问题,你也可以自己实现编解码器,关于Netty编解码器我们下节会介绍,这里不展开详述。
点击查看Netty处理了TCP粘包的正常功能案例完整示例代码
启动上面代码的客户端和服务器查看结果,你会发现服务器收到了100条客户端的请求消息消息,客户端同样收到了100条服务器的响应消息消息,并给出了正确的响应内容,这说明我们已经正确的处理了TCP的粘包问题。

Choice wechat
关注公众号,获取文章更新通知。
-------------本文结束感谢您的阅读-------------
坚持原创技术分享,您的支持将鼓励我继续创作!