b TCP粘包/拆包简单介绍
TCP粘包/拆包 是什么
一、TCP粘包/拆包 是什么
tcp的数据发送都是靠流,流由一个接一个的数据包组成。发送过程中tcp会把数据拆成很多个包,也有可能将小的数据合成一个大包。
1、TCP粘包/拆包问题说明
包读取有大致4种情况
假设客户端分别发送了两个数据包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分片
3、粘包问题的解决策略
由于底层TCP无法理解上层的业务数据,所以在底层是无法保证数据包不被拆分和充足,只能同设计上层的应用的协议栈来解决,根据业界的主流协议的解决方法,归纳如下
- 消息定长,每个报文固定长度为200空格,不够空格来凑
- 包尾增加回车换行符进行分割,例如FTP协议
- 将消息分为消息头和消息体,消息头包含消息长度,通常为第一个字段int32来表示消息的总长度
- 更复杂的应用层协议
二、不考虑TCP粘包/拆包的问题案例
案例一
看这个for循环,server端write写了100次
client每读一次就记个数
结果是发100次,client就收到2次,说明包被拼在一起
三、Netty的解决之道
Netty默认提供了多种便面用于处理半包问题,原生JDK没有。
LineBasedFrameDecoder和StringDecoder
-
LineBasedFrameDecoder
和StringDecoder
成功解决了TCP粘包导致的读半包问题。使用者将handler添加ChannelPipeline即可LineBasedFrameDecoder
的工作原理是它依次遍历ByteBuf中的可读字节,遇到\n
或\r\n
就以此为结束位置。换行符作为结束标志,支持设置单行最大长度,如果读取到最大长度还没有发现换行符,就会抛异常,并忽略到之前的异常码流
-
StringDecoder
将接受到的对象转换为字节符,然后继续调用后续handler。LineBasedFrameDecoder
+StringDecoder
组合就是按行切换的文本解码器,被设计用来解决TCP的粘包拆包
如果输入的文本没有换行符怎么办,后续有分隔符解码器的介绍
代码如何支持
服务端和客户端都是一个责任链模式,发送消息前,加两个handler