详解啥是 TCP 粘包和拆包现象并演示 Netty 是如何解决的
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了详解啥是 TCP 粘包和拆包现象并演示 Netty 是如何解决的相关的知识,希望对你有一定的参考价值。
参考技术A 本文介绍什么是 TCP 粘包和拆包现象,并通过 Netty 编写详细的案例来重现 TCP 粘包问题,最后再通过一个 Netty 的 demo 来解决这个问题。具体内容如下TCP 编程底层都有粘包和拆包机制,因为我们在C/S这种传输模型下,以TCP协议传输的时候,在网络中的byte其实就像是河水,TCP就像一个搬运工,将这流水从一端转送到另一端,这时又分两种情况:
下面通过 Netty 重现 TCP 粘包和拆包现象。
其中关键的代码如下
从上面的案例可以发现当出现 TCP 粘包和拆包现象后会出现下面的问题:
由于 TCP 粘包和拆包现象会导致不能正确区分数据的头尾,那么解决的办法也挺简单的,通过 特殊字符串 来分隔消息体或者使用 定长消息 就能够正确区分数据的头尾。
目前的主流解决方式有以下几种:
Netty 中也提供了基于分隔符实现的半包解码器和定长的半包解码器:
使用 DelimiterBasedFrameDecoder 可以确保收到的数据会自动通过 自定义的分隔符 进行分隔。发送的时候消息的后面只需要增加上 自定义的分隔符 即可。
粘包和拆包
写在前面
粘包、拆包是 Socket 编程中最常遇见的一个问题,本文只对粘包、拆包现象及发生的原因做简要分析,具体如何解决粘包和拆包的问题,在后续文章中会详细介绍。
什么是粘包、拆包
TCP 是个"流"协议,所谓流,就是没有界限
的一串数据(无论你上层是如何封装的数据,到通信层都会转换成“流”的形式,比如 Netty 的 ByteBuf),它会根据 TCP 缓冲区的实际情况进行包的划分,所以实际场景可能是:
对于发送端而言:
-
当 TCP 发送缓冲区剩余空间不足时,一个完整的包可能会被拆分为多个包进行发送,即可能发生拆包情况。
-
当 TCP 发送缓冲区剩余空间足够时,多个小的包也有可能被封装成一个大的包进行发送,即可能发生粘包情况。
粘包、拆包产生的原因
上面我们详细了解了 TCP 粘包与拆包,那么为什么会发生粘包和拆包呢,大致上有三个方面的原因:
-
即上文描述的那种情况。
-
Nagle
算法,TCP 默认开启 Nagle 算法,Nagle 算法主要做两件事情:只有上一个分组得到确认,才发送下一个分组,收集多个小分组,在一个确认到来时一起发送,Nagle 算法可能造成发送方粘包。 -
进行
MSS
大小的 TCP,MSS 是最大报文段长度的缩写,是 TCP 报文段中的数据字段
最大长度,MSS = TCP 报文段长度 - TCP 首部长度。 -
以太网的 Payload 大于
MTU
,进行 IP 分片,MTU 是最大传输单元的缩写,以太网的 MTU 为 1500 字节。
拆包和粘包是相对的,一端粘了包,另外一端就需要将粘过的包拆开
如何处理 TCP 粘包和 TCP 拆包问题?
无论是 TCP 拆包还是 TCP 粘包本质问题都在于无法区分包的边界
,一般有三种区分包边界的方式:
-
消息数据固定长度,实际应用中基本不可能做到,即时做到了,也是很浪费存储和网络资源。
-
使用分割符来区分包的界限
-
数据包的头部中增加数据包长度字段
UDP 存在粘包和拆包的问题吗?
TCP 之所以存在拆包和粘包问题,本质就是 TCP 是面向字节流的协议,字节流协议即无边界协议;而像 UDP 是面向报文的,当客户端连续发送多个包,并不会发生粘包现象,每一个包都是独立的,发送的时候也是以一个一个包为单位。
那么问题来了,不会发生粘包,如果应用程序 write 一个大的包,那么到底层进行发送的时候会不会发生拆包呢?
答案是:不会。UDP 协议发送时,用 sendto 函数最大能发送数据的长度为:65535- IP 头(20) - UDP 头(8) = 65507 字节
。用 sendto 函数发送数据时,如果发送数据长度大于该值,则函数直接返回错误,不会发生拆包,而 TCP 流协议是会发生拆包的。
sendto 扩展
sendto
是一个计算机函数,指向一指定目的地发送数据,sendto 适用于发送未建立连接的 UDP 数据包 (参数为SOCK_DGRAM)。sendto 发送数据必需注意数据长度不应超过通讯子网的 IP 包最大长度。IP 包最大长度在 WSAStartup() 调用返回的 WSAData 的 iMaxUdpDg 元素中。如果数据太长无法自动通过下层协议,则返回 WSAEMSGSIZE
错误,数据不会被发送。
WSAEMSGSIZE
:套接口为 SOCK_DGRAM 类型,且数据报大于 WINDOWS 套接口实现所支持的最大值。
int PASCAL FAR sendto(SOCKET s, const char FAR* buf, int len, int flags, const struct sockaddr FAR* to, int tolen);
s:一个标识套接口的描述字
buf:含待发送数据的缓冲区
len:buf 缓冲区中数据的长度
flags:调用方式标志位
to:(可选)指针,指向目的套接口的地址
tolen:to 所指地址的长度
总结
到这里关于 TCP 粘包和拆包是什么,产生的原因是什么,以及 UDP 是否也会发生粘包和拆包的问题做了简要分析。这只是关于 TCP 粘包和拆包问题的第一篇文章,后面会详细分析常用的解决方案,以及市面上常用通信框架的解决方案是什么。
参考
以上是关于详解啥是 TCP 粘包和拆包现象并演示 Netty 是如何解决的的主要内容,如果未能解决你的问题,请参考以下文章