牛逼!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月前
|
编解码 缓存 移动开发
TCP粘包/拆包与Netty解决方案
TCP粘包/拆包与Netty解决方案
45 0
|
7月前
|
移动开发 网络协议 算法
由浅入深Netty粘包与半包解决方案
由浅入深Netty粘包与半包解决方案
50 0
|
2月前
|
网络协议
Netty实现TCP通信
Netty实现TCP通信
43 0
|
14天前
|
网络协议 Java 物联网
Spring Boot与Netty打造TCP服务端(解决粘包问题)
Spring Boot与Netty打造TCP服务端(解决粘包问题)
26 1
|
3月前
|
编解码
Netty Review - 优化Netty通信:如何应对粘包和拆包挑战_自定义长度分包编解码码器
Netty Review - 优化Netty通信:如何应对粘包和拆包挑战_自定义长度分包编解码码器
46 0
|
3月前
|
网络协议
Netty Review - 优化Netty通信:如何应对粘包和拆包挑战
Netty Review - 优化Netty通信:如何应对粘包和拆包挑战
50 0
|
10月前
|
网络协议
netty编程实战02-创建一个带有连接重试的tcp客户端程序
netty编程实战02-创建一个带有连接重试的tcp客户端程序
161 0
|
8月前
|
Nacos
Netty自定义消息协议的实现逻辑处理粘包拆包、心跳机制
Netty自定义消息协议的实现逻辑处理粘包拆包、心跳机制
106 0
|
8月前
|
存储 编解码 网络协议
Netty各组件基本用法、入站和出站详情、群聊系统的实现、粘包和拆包
Netty各组件基本用法、入站和出站详情、群聊系统的实现、粘包和拆包
70 0
|
9月前
|
网络协议 算法
Netty入门到超神系列-TCP粘包拆包处理
TCP是面向连接的,服务端和客户端通过socket进行数据传输,发送端为了更有效的发送数据,通常会使用Nagle算法把多个数据块合并成一个大的数据块,这样做虽然提高了效率,但是接收端就很难识别完整的数据包了(TCP无消息保护边界),可能会出现粘包拆包的问题。
65 0