粘包半包与Netty的解决方案
粘包半包与Netty的解决方案
粘包半包问题
之前在NIO那一节,介绍过关于粘包半包问题,此处来更加详细的介绍一下。
TCP粘包与半包
假设客户端发送data1、data2两个数据包给服务器。
粘包:指服务器一次性接收到了data1、data2两个数据包
半包(拆包):指服务端两次接收到了两个包,并且第一次接收到了data1与半个data2,第二次接收到另一半data2
出现的原因
TCP协议是一个流式协议,所谓流式协议,就是没有具体界限的数据,(作为对比UDP协议则是一个数据包一个数据包的发送数据)
因此有可能出现粘包半包问题,具体出现的原因有以下三种情况:
- 应用程序
write
写入的字节大小大于套接口发送缓冲区大小
数据发送的流程大致为:
用户发送数据包A->调用write
系统调用->用户态转到内核态->数据从用户内存复制到内核的缓冲区->缓冲区满->数据从缓冲区发送到网卡->经过物理链路发送
注意这个过程中,如果发送的一个数据ABC
,缓冲区只能容下AB
,那么此时就会拆为两个包
- MSS大小的TCP分段
这里需要回忆一下TCP的网络知识,TCP协议规定了MSS
MSS 最大报文段长度:仅包括数据部分,不包括头部
在Internet标准下:IP协议MTU为576,那么TCP的MSS为536 byte (576 - 20ip的报头 - 20TCP的报头)
如果使用以太网的话:IP协议MTU为1500,那么TCP的MSS为1460(同样减去两个协议的报头)
注意:不同协议的MSS值不同,由具体的协议确定
如果要发送1500字节的数据,那么TCP协议会拆为两个包:一个1460字节(数据部分,不包含头)、一个40字节(注意:根据Naggle算法,这40字节很可能会跟着另外一个包发送,并不会单独发送)
额外需要注意的是:
MSS的大小,是在三次握手中确定的,MSS的值会在SYN报文中发送,由握手双方确定
(在第二次握手后就可以确定TCP中最大传输报文(MSS)大小)
这里稍微岔开一点,介绍一下IP切片与TCP切片:
以太网帧的payload
大于MTU进行IP分片(UDP协议才会IP分片)
如果数据链路层使用以太网的话(好像也没有使用其他的),那么数据如果超过这个大小也是会分片的。
提问1:TCP不是都分了段了吗?怎么还有IP分片?
答:这里指UDP协议!!UDP是没有MSS的!!
提问2:那意思是,UDP也会粘包?
答:不会的!!UDP虽然包有可能在IP层被拆了,但是,对端在接收到之后根据ip报文中的
identification、flags、offset
实现报文的重组,丢了怎么办?丢了就丢了,反正我叫UDP!注意:如果TCP分了段,那么以太网帧就不会分片了!
- Naggle算法:会把小的数据包掺加到一起发送
解决措施
- 固定消息长度:消息规定一个固定长度,剩余空间补空格
- 优点:简单,方便
- 缺点:占用带宽
- 格式化数据:利用特殊字符隔断数据,例如在FTP协议中,使用回车换行符分隔
- TLV 格式:即 Type 类型、Length 长度、Value 数据,在类型和长度已知的情况下就可以方便的知道消息大小,分配合适的 buffer
- 缺点:
buffer
需要提前分配,如果分配过大,影响 server 的吞吐量 - HTTP1.1 是 TLV 格式(先传输类型)
- HTTP2.0 是 LTV 格式(先传输长度)
- 缺点:
- 关闭Naggle算法:设置
TCP_NODELAY
就能关闭 Nagle 算法(严格意义上也算一种办法)
Netty的解法
- 固定长度解码器:Netty 提供了
FixedLengthFrameDecoder
,它允许你指定固定长度的消息,这样在解码时就可以按照固定长度来切分数据帧,从而避免粘包和半包问题。这种方法适用于消息长度固定的场景。 - 行分隔符解码器:Netty 提供了
LineBasedFrameDecoder
,它根据换行符(例如\n
或\r\n
)将数据分割成不同的消息。这对于文本协议非常有用,因为通常文本消息以换行符分隔。 - 分隔符解码器:Netty 提供了
DelimiterBasedFrameDecoder
,它允许你指定一个自定义的分隔符,将数据按照分隔符来分割消息。这种方法适用于自定义协议中需要特定分隔符的情况。 - 长度字段解码器:Netty 提供了
LengthFieldBasedFrameDecoder
,它通过读取消息中的长度字段来分割消息。这种方法适用于自定义协议中包含长度字段的情况。 - 自定义协议解码器:你可以根据自己的协议特点来编写自定义解码器,以确保消息能够被正确切分和解析。通过继承
ByteToMessageDecoder
类,你可以自定义解码逻辑,根据消息头部信息来切分消息。 - IdleStateHandler:虽然不直接解决粘包和半包问题,但
IdleStateHandler
可以帮助你检测连接的空闲状态,从而可以根据需要采取措施,例如关闭连接或发送心跳包。