牛逼!TCP 粘拆包问题及 Netty 中的解决方案

简介: 本文选自 Doocs 开源社区旗下“源码猎人”项目,作者 AmyliaY。

本文选自 Doocs 开源社区旗下“源码猎人”项目,作者 AmyliaY。


项目将会持续更新,欢迎 Star 关注。


项目地址:https://github.com/doocs/source-code-hunter


熟悉 TCP 编程的都知道,无论是服务端还是客户端,当我们读取或者发送消息的时候,都需要考虑 TCP 底层的粘包/拆包机制


TCP 粘包/拆包问题,在功能测试时往往不会怎么出现,而一旦并发压力上来,或者发送大报文之后,就很容易出现粘包/拆包问题。如果代码没有考虑,往往就会出现解码错位或者错误,导致程序不能正常工作。


本篇文章,我们先简单了解 TCP 粘包/拆包 的基础知识,然后来看看 Netty 是如何解决这个问题的


TCP 粘包/拆包


TCP 粘包/拆包问题说明


TCP 是个 “流” 协议。所谓流,就是没有界限的一串数据。TCP 底层并不了解上层(如 HTTP 协议)业务数据的具体含义,它会根据 TCP 缓冲区的实际情况进行包的划分,所以在业务上认为,一个完整的包可能会被 TCP 拆分成多个包进行发送,也有可能把多个小的包封装成一个大的数据包发送,这就是所谓的 TCP 粘包和拆包问题。我们可以通过下面的示例图,对 TCP 粘包和拆包问题进行说明。


27.png


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


1.服务端分两次读取到了两个独立的数据包,分别是 D1 和 D2,没有粘包和拆包;

2.服务端一次接收到了两个数据包,DI 和 D2 粘合在一起,被称为 TCP 粘包;

3.服务端分两次读取到了两个数据包,第一次读取到了完整的 DI 包 和 D2 包的部分内容,第二次读取到了 D2 包的剩余内容,这被称为 TCP 拆包;

4.服务端分两次读取到了两个数据包,第一次读取到了 D1 包的部分内容,第二次读取到了 D1 包的剩余内容和 D2 包的整包。


如果此时服务端 TCP 接收滑窗非常小,而 数据包 DI 和 D2 比较大,很有可能会发生第 5 种可能,即服务端分多次才能将 D1 和 D2 包接收完全,期间发生多次拆包。


TCP 粘包/拆包发生的原因


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


1.应用程序 write 写入的字节大小 超出了 套接口发送缓冲区大小;

2.进行 MSS 大小的 TCP 分段;

3.以太网帧的 payload 大于 MTU 进行 IP 分片。


粘拆包问题的解决策略


由于底层的 TCP 无法理解上层的业务数据,所以在底层是无法保证数据包不被拆分和重组的,这个问题只能通过上层的应用协议栈设计来解决,根据业界的主流协议的解决方案,可以归纳如下。


1.固定消息长度,例如,每个报文的大小为 固定长度 200 字节,如果不够,空位补空格;

2.在包尾使用 “回车换行符” 等特殊字符,作为消息结束的标志,例如 FTP 协议,这种方式在文本协议中应用比较广泛;

3.将消息分为消息头和消息体,在消息头中定义一个 长度字段 Len 来标识消息的总长度;4.更复杂的应用层协议


介绍完了 TCP 粘包/拆包 的基础,下面我们来看看 Netty 是如何使用一系列 “半包解码器” 来解决 TCP 粘包/拆包问题的。


Netty 解码器解决 TCP 粘拆包问题


根据上面的粘拆包问题解决策略,Netty 提供了相应的解码器实现。有了这些解码器,用户不需要自己对读取的报文进行人工解码,也不需要考虑 TCP 的粘包和拆包。


LineBasedFrameDecoder 和 StringDecoder 的原理分析


为了解决 TCP 粘包 / 拆包 导致的半包读写问题,Netty 默认提供了多种编解码器用于处理半包,只要能熟练掌握这些类库的使用,TCP 粘拆包问题从此会变得非常容易,你甚至不需要关心它们,这也是其他 NIO 框架 和 JDK 原生的 NIO API 所无法匹敌的。对于使用者来说,只要将支持半包解码的 Handler 添加到 ChannelPipeline 对象 中即可,不需要写额外的代码,使用起来非常简单。


// 示例代码,其中 socketChannel 是一个 SocketChannel对象socketChannel.pipeline().addLast( new LineBasedFrameDecoder(1024) );socketChannel.pipeline().addLast( new StringDecoder() );


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


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


其它解码器


除了 LineBasedFrameDecoder 以外,还有两个常用的解码器 DelimiterBasedFrameDecoderFixedLengthFrameDecoder,前者能自动对 “以分隔符做结束标志的消息” 进行解码,后者可以自动完成对定长消息的解码。使用方法也和前面的示例代码相同,结合字符串解码器 StringDecoder,轻松完成对很多消息的自动解码。


目录
相关文章
|
4月前
|
网络协议 Java Maven
基于Netty实现TCP通信
基于Netty实现TCP通信
76 0
|
4月前
|
编解码 缓存 移动开发
TCP粘包/拆包与Netty解决方案
TCP粘包/拆包与Netty解决方案
85 0
|
1月前
|
编解码 网络协议 开发者
Netty运行原理问题之NettyTCP的粘包和拆包的问题如何解决
Netty运行原理问题之NettyTCP的粘包和拆包的问题如何解决
|
1月前
|
移动开发 网络协议 算法
(十)Netty进阶篇:漫谈网络粘包、半包问题、解码器与长连接、心跳机制实战
在前面关于《Netty入门篇》的文章中,咱们已经初步对Netty这个著名的网络框架有了认知,本章的目的则是承接上文,再对Netty中的一些进阶知识进行阐述,毕竟前面的内容中,仅阐述了一些Netty的核心组件,想要真正掌握Netty框架,对于它我们应该具备更为全面的认知。
|
4月前
|
网络协议
Netty实现TCP通信
Netty实现TCP通信
80 0
|
3月前
|
Java
Netty传输object并解决粘包拆包问题
Netty传输object并解决粘包拆包问题
30 0
|
3月前
|
Java
Netty中粘包拆包问题解决探讨
Netty中粘包拆包问题解决探讨
18 0
|
4月前
|
网络协议 Java 物联网
Spring Boot与Netty打造TCP服务端(解决粘包问题)
Spring Boot与Netty打造TCP服务端(解决粘包问题)
647 1
|
4月前
|
JSON 移动开发 网络协议
数据拆散与黏连:深入剖析Netty中的半包与粘包问题
数据拆散与黏连:深入剖析Netty中的半包与粘包问题
103 0
|
4月前
|
消息中间件 Oracle Dubbo
Netty 源码共读(一)如何阅读JDK下sun包的源码
Netty 源码共读(一)如何阅读JDK下sun包的源码
108 1