TCP协议下的粘包与拆包,如何解决一、粘包、拆包1.1 粘包原因1.1.1 滑动窗口1.1.2 Nagle算法1.1.3 应用层原因1.2 拆包原因1.2.1 滑动窗口1.2.2 MSS限制1.2.3 应用层原因1.2 Netty提供的解决方案二、自定义协议解决粘包、拆包2.1 自定义协议要素
因为TCP/IP在起初,所有的请求是串行化的,之后做成了滑动窗口的概念。那么在接收方,如果接收不及时且窗口大小足够大,就可能出现粘包的情况。
因为每次数据发送的时候,都需要加上消息头等特殊数据,TCP与IP协议分别会加20Byte数据,因此哪怕只是发送1Byte数据,最终接收方还是会接收到41Byte;在这样的背景下,Nagle算法可能会将多个数据包合并在一起发送。
接收方ByteBuf设置太大(Netty默认为1024Byte),因此如果客户端发送的报文都非常小,抛开上述 1.1.1.1 中的原因不谈,光在Netty这一处也非常容易出现粘包现象
假设接收方的窗口只剩128Bytes,发送方的报文大小是256Bytes,这时放不下了,只能先发送前128Bytes,等待ack后,窗口有剩余空间了才会发送剩余部分,这就导致了拆包
当发送数据超过MSS限制后,会将数据切分发送,而这个MSS是根据不同类型网卡的限制来看,譬如某笔记本网卡可以发送1500Byte,除去一次数据包中的TCP/IP固定40Byte外,真正的数据也只能发送1460Byte。
Netty中ByteBuf设置的大小小于数据包大小。
未完待续... ...
如果客户端连续不断的向服务端发送数据包时,服务端接收的数据会出现两个数据包粘在一起的情况,这就是TCP协议中经常会遇到的粘包以及拆包的问题。
传输层的UDP协议是否会发生粘包或者拆包问题?
不会。UDP是基于报文发送的,在UDP首部采用了16bit来指示UDP数据报文的长度,因此在应用层能很好的将不同的数据报文区分开,从而避免粘包和拆包的问题。
传输层的TCP协议是否会发生粘包或者拆包问题?
会。原因有以下两点:
1、TCP是基于字节流的,虽然应用层和传输层之间的数据交互是大小不等的数据块,但是TCP把这些数据块仅仅看成一连串无结构的字节流,没有边界;
2、在TCP的首部没有表示数据长度的字段,基于上面两点,在使用TCP传输数据时,才有粘包或者拆包现象发生的可能。
现在假设客户端向服务端连续发送了两个数据包,用packet1和packet2来表示,那么服务端收到的数据可以分为三种,现列举如下:
第一种情况,接收端正常收到两个数据包,即没有发生拆包和粘包的现象,此种情况不在本文的讨论范围内。
第二种情况,接收端只收到一个数据包,由于TCP是不会出现丢包的,所以这一个数据包中包含了发送端发送的两个数据包的信息,这种现象即为粘包。这种情况由于接收端不知道这两个数据包的界限,所以对于接收端来说很难处理。
第三种情况,这种情况有两种表现形式,如下图。接收端收到了两个数据包,但是这两个数据包要么是不完整的,要么就是多出来一块,这种情况即发生了拆包和粘包。这两种情况如果不加特殊处理,对于接收端同样是不好处理的。
发生TCP粘包或拆包有很多原因,现列出常见的几点:
1、要发送的数据大于TCP发送缓冲区剩余空间大小,将会发生拆包。
2、待发送数据大于MSS(最大报文长度),TCP在传输前将进行拆包。
3、要发送的数据小于TCP发送缓冲区的大小,TCP将多次写入缓冲区的数据一次发送出去,将会发生粘包。
4、接收数据端的应用层没有及时读取接收缓冲区中的数据,将发生粘包。
通过以上分析,我们清楚了粘包或拆包发生的原因,那么如何解决这个问题呢?解决问题的关键在于如何给每个数据包添加边界信息,常用的方法有如下几个:
1、发送端给每个数据包添加包首部,首部中应该至少包含数据包的长度,这样接收端在接收到数据后,通过读取包首部的长度字段,便知道每一个数据包的实际长度了。
2、发送端将每个数据包封装为固定长度(不够的可以通过补0填充),这样接收端每次从接收缓冲区中读取固定长度的数据就自然而然的把每个数据包拆分开来。
3、可以在数据包之间设置边界,如添加特殊符号,这样,接收端通过这个边界就可以将不同的数据包拆分开。
Tomcat是通过http的协议来进行传输数据的,http协议在请求头内有一个Content-Length字段来告诉服务端请求体的长度,并且请求头和请求体通过空一行来分开的
Netty支持多种处理粘包和拆包的方式,可以根据具体的场景进行选择。
参考:
https://www.cnblogs.com/yaochunhui/p/14175396.html
假设客户端向服务端连续发送了两个数据包,用 packet1 和 packet2 来表示,那么服务端收到的数据可以分为三种,现列举如下
当需要传输的数据大于MSS或者MTU时,数据会被拆分成多个包进行传输。由于MSS是根据MTU计算出来的,因此当发送的数据满足MSS时,必然满足MTU。
MTU是以太网(数据链路层)传输数据方面的限制
由于MTU限制了一次最多可以发送1500个字节,而TCP协议在发送DATA时,还会加上额外的TCP Header和Ip Header,因此刨去这两个部分,就是TCP协议一次可以发送的实际应用数据的最大大小,也就是MSS。
MSS长度=MTU长度-IP Header-TCP Header
TCP Header的长度是20字节,IPv4中IP Header长度是20字节,IPV6中IP Header长度是40字节,因此:在IPV4中,以太网MSS可以达到1460byte;在IPV6中,以太网MSS可以达到1440byte。
需要注意的是MSS表示的一次可以发送的DATA的最大长度,而不是DATA的真实长度。发送方发送数据时,当数据量大于MSS时, *** 作系统会将数据进行拆分,使得每一部分都小于MSS,这就是拆包,然后每一部分都加上TCP Header,构成多个完整的TCP报文进行发送,当然经过网络层和数据链路层的时候,还会分别加上相应的内容。
TCP流量控制主要使用滑动窗口协议,滑动窗口是接收端使用的窗口大小,用来告诉发送端接收端的缓存大小,以此可以控制发送端发送数据的大小,从而达到流量控制的目的。这个窗口大小就是我们一次传输几个数据。对所有数据帧按顺序赋予编号,发送方在发送过程中始终保持着一个发送窗口,只有落在发送窗口内的帧才允许被发送;同时接收方也维持着一个接收窗口,只有落在接收窗口内的帧才允许接收。这样通过调整发送方窗口和接收方窗口的大小可以实现流量控制。
滑动窗口大小这个字段是接收端告诉发送端自己还有多少缓冲区可以接收数据。于是发送端就可以根据这个接收端的处理能力来发送数据,而不会导致接收端处理不过来。
来看一下滑动窗口是如何造成粘包、拆包的?
Nagle算法就是为了尽可能发送大块数据,避免网络中充斥着许多小数据块。
Nagle算法的基本定义是任意时刻,最多只能有一个未被确认的小段。 所谓“小段”,指的是小于MSS尺寸的数据块,所谓“未被确认”,是指一个数据块发送出去后,没有收到对方发送的ACK确认该数据已收到。
Nagle算法的实现规则:
UDP每一段都是一条消息,应用程序必须以消息为单位提取数据,不能一次提取任意字节的数据
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)