tcp 粘包/拆包问题分析 netty 解决方案

TCP 粘包拆包说明

TCP 是个“流”协议(流 -> 没有界限的一串数据)。

TCP 底层并不了解上层数据的具体含义,它会根据TCP缓冲区的实际情况进行包的划分,所以在业务上认为,一个完整的包可能会被tcp拆分成多个包发送,也有可能把多个小的包封装成一个大的数据包发送。

示例分析

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

  1. 图第一行:服务端分别读取到两个独立的数据包,没有粘包和拆包
  2. 图第二行:服务端一次接收到两个数据包,D1 和 D2 粘合在一起,被称为TCP 粘包
  3. 图第三行:服务端第一次读取到 D1 的完整内容和 D2 的部分内容,第二次读取到了 D2 的剩余部分,这被称为 TCP 拆包
  4. 图第四行:服务端第一次读取到了 D1 的部分内容, 第二次读取到了 D1 的剩余部分和 D2 的整包
  5. 如果服务端tcp 接收滑窗非常小,而数据包 D1 和 D2 比较大,则服务端需要多次才能将D1 和 D2 包接收完全,期间发生多次拆包。
发生原因

问题产生的原因有三,如下:
1. 应用程序 write 写入的字节大小大于套接字缓冲区大小
2. 待发送数据大于MSS(指TCP报文能够携带的最大数据长度),TCP在传输前将进行拆包
3. 以太网帧的 payload 大于 MTU(最大传输单元)而进行 IP 分片

解决策略

由于 tcp 无法理解上层业务数据,所以这个问题只能通过上层的设计来解决,解决方案归纳如下:

  1. 消息定长。规定每个报文为固定长度,如果不够,空位补空格。
  2. 在包尾增加回车换行符进行分割,例如 FTP 协议。
  3. 将消息分为头部和消息主体,头部包含表示消息长度的字段

LineBasedFrameDecoder 解决 TCP 粘包

原理分析

对应于上面解决策略其二,在包尾添加回车换行符,然后在解码时以回车换行符作为包结束标志。

// 包尾添加换行符
msg= Unpooled.copiedBuffer(("QUERY TIME ORDER"+System.getProperty("line.separator")).getBytes());
// 添加解码器 LineBasedFrameDecoder
ch.pipeline().addLast(new LineBasedFrameDecoder(1024));

LineBasedFrameDecoder 的工作原理是它依次遍历ByteBuf中的可读字节,判断看是否有“\n” 或“\r\n”,如果有,就以此位置作为结束位置。

DelimiterBasedFrameDecoder 分隔符解码器

原理分析

以自定义分隔符作为包结束标志。

// 1. 定义分隔符$_
// 2. 添加分隔符解码器 DelimiterBasedFrameDecoder
ByteBuf delemiter= Unpooled.copiedBuffer("$_".getBytes());
ch.pipeline().addLast(new DelimiterBasedFrameDecoder(1024,delemiter));

// 3.包尾添加$_
msg= Unpooled.copiedBuffer(("QUERY TIME ORDER$_").getBytes());
ctx.writeAndFlush(msg);

FixedLengthFrameDecoder 定长解码器

原理说明

FixedLengthFrameDecoder 是固定长度解码器,它能够按照指定的长度对消息进行自动解码,开发者不需要考虑TCP的粘包拆包问题。

// 添加定长为20的解码器
ch.pipeline().addLast(new FixedLengthFrameDecoder(20));

无论一次接收到多少数据包,它都会按照设置的定长进行解码。

例如:设置定长为5,接收到数据包abcdefg, 解码器按照定长5进行解码,对abcde进行处理,剩余的fg缓存起来,等待下个包到来再进行拼包。

猜你喜欢

转载自blog.csdn.net/qq_24871519/article/details/82429154