# Netty粘包与拆包

  • TCP是一个“流”协议。所谓流,就是没有界限的一长串二进制数据。
  • 拆包和粘包:TCP作为传输层协议,并不了解上层业务数据的具体含义,它会根据TCP缓冲区的实际情况进行数据包的划分,所以在业务上认为是一个完整包的,可能会被TCP拆分成多个包进行发送,也有可能把多个小的包封装成一个大的数据包发送,这就是所谓的TCP拆包和粘包问题。
  • 半包:Netty在轮询读事件的时候,每次从Channel中读取的数据,不一定是一个完整的数据包,这种情况就叫作半包。
  • 粘包:Client往Server发送数据包时,如果发送频繁很有可能会将多个数据包的数据都发送到通道中,Server在读取的时候可能会读取到超过一个完整数据包的长度,这种情况叫作粘包。

# TCP 粘包/拆包问题说明

假设客户端分别发送了两个数据包 Dl 和 D2 给服务端,由于服务端一次读取到的字节数是不确定的,故可能存在以下 4 种情况。

  1. 服务端分两次读取到了两个独立的数据包,分别是 D1 和 D2,没有粘包和拆包;
  2. 服务端一次接收到了两个数据包, D1 和 D2 粘合在一起,被称为 TCP 粘包;
  3. 服务端分两次读取到了两个数据包,第一次读取到了完整的D1包和D2包的部分内容,第二次读取到了D2包的剩余内容,这被称为 TCP 拆包;
  4. 服务端分两次读取到了两个数据包,第一次读取到了D1包的部分内容D1_1,第二次读取到了D1包的剩余内容D1_2和D2包的整包。

# TCP 粘包/拆包发生的原因

问题产生的原因有三个,分别如下。

  1. 应用行序write 写入的字节大小大于套接日发送缓冲区大小;
  2. 进行 MSS 大小的 TCP 分段;
  3. 以太网帧的payload 大于MTU进行IP分片

# 粘包问题的解决策略

  1. 消息定长,报文长度固定,例如每个报文的长度固定为200字节,如果不够空位补空格。
  2. 报尾添加特殊分隔符,例如每条报文结束都添加回车换行符(如FTP)或者指定特殊字符作为报文分隔符,接收方通过特殊分隔符区分报文。
  3. 将消息分为消息头和消息体,消息头包含表示信息的总长度(或者消息体长度)的属性。
  4. 更复杂的自定义应用层协议。 Netty对半包或者粘包的处理:每个Handler都是和Channel唯一绑定的,一个Handler只对应一个Channel,所以Channel中的数据读取的时候经过解析,如果不是一个完整的数据包,则解析失败,将这个数据包进行保存,等下次解析时再和这个数据包进行组装解析,直到解析到完整的数据包,才会将数据包向下传递。

# 利用LineBasedFrameDecoder解决TCP粘包问题

LineBaseFrameDecoder的工作原理是它依次遍历ByteBuf中的可读下节,判断否有“\n”或者"\r\n", 如果有,就以此位置为结束位置,从可读索引到结束位置区间的字节就组成了一行。它是以换行符为结束标志的解码器,支持携带结束符或者不携带结束符两种解码方式,同时支持配置单行的最大长度。如果连续读取到最大长度后仍然没有发现换行符,就会抛出异常,同时忽略掉之前读到的异常码流。

StingDecoder的功能日常间单,就是将接收到的对象转换成字符串,然后继续调用后面的Handler。LineBasedFrameDecoder + String Decoder组合就是按行切换的文本解码器,它被设计用来支持 TCP 的粘包和拆包。

同时也支持其他的解码器,以满足不同诉求。

Last Updated: 5/18/2021, 5:02:25 PM