b TCP粘包/拆包简单介绍

一、TCP粘包/拆包 是什么

tcp的数据发送都是靠流,流由一个接一个的数据包组成。发送过程中tcp会把数据拆成很多个包,也有可能将小的数据合成一个大包。

1、TCP粘包/拆包问题说明

包读取有大致4种情况

b TCP粘包/拆包简单介绍
假设客户端分别发送了两个数据包D1和D2给服务端,由于服务端一次读取到的字节数是不确定的,所以存在以下问题

  • 服务端分两次读取到了连个独立的数据包,分别是D1和D2,没有粘包和拆包
  • 服务端一次接收到了两个数据包,D1和D2粘在一起就是粘包
  • 服务端分两次读取到了两个数据包,第一次读取到了完整的D1包和D2包的部分包,第二次读到了D2包的剩余内容,被称为TCP拆包
  • 服务端分两次读取到了两个数据包,第一次读取到了D1包的部分内容D1_1,第二次读取到了D1包的剩余内容D1_和D2包的整包。
    如果服务端TCP的接受滑窗非常小,而数据包D1和D2比较大,就可能产生第五种可能那个,即服务端分多次才能将D1和D2包完全接收,期间发生多次拆包

2、**粘包发生的原因

问题产生的原因有三个

  • 应用程序write的字节小于大于socket发送缓冲区大小
  • 进行MSS大小的TCP分段
  • 以太网帧payload大于MTU进行IP分片
    b TCP粘包/拆包简单介绍

3、粘包问题的解决策略

由于底层TCP无法理解上层的业务数据,所以在底层是无法保证数据包不被拆分和充足,只能同设计上层的应用的协议栈来解决,根据业界的主流协议的解决方法,归纳如下

  • 消息定长,每个报文固定长度为200空格,不够空格来凑
  • 包尾增加回车换行符进行分割,例如FTP协议
  • 将消息分为消息头和消息体,消息头包含消息长度,通常为第一个字段int32来表示消息的总长度
  • 更复杂的应用层协议

二、不考虑TCP粘包/拆包的问题案例

案例一

b TCP粘包/拆包简单介绍
看这个for循环,server端write写了100次

b TCP粘包/拆包简单介绍
client每读一次就记个数
结果是发100次,client就收到2次,说明包被拼在一起

三、Netty的解决之道

Netty默认提供了多种便面用于处理半包问题,原生JDK没有。

LineBasedFrameDecoder和StringDecoder

  • LineBasedFrameDecoderStringDecoder成功解决了TCP粘包导致的读半包问题。使用者将handler添加ChannelPipeline即可
    LineBasedFrameDecoder的工作原理是它依次遍历ByteBuf中的可读字节,遇到\n\r\n就以此为结束位置。换行符作为结束标志,支持设置单行最大长度,如果读取到最大长度还没有发现换行符,就会抛异常,并忽略到之前的异常码流
  • StringDecoder将接受到的对象转换为字节符,然后继续调用后续handler。LineBasedFrameDecoder+StringDecoder组合就是按行切换的文本解码器,被设计用来解决TCP的粘包拆包

如果输入的文本没有换行符怎么办,后续有分隔符解码器的介绍

代码如何支持

服务端和客户端都是一个责任链模式,发送消息前,加两个handler
b TCP粘包/拆包简单介绍