通过上面我们可以知道,TCP的重传是由超时触发的,这会引发一个重传选择问题,假设TCP发送端连续发了1、2、3、4、5、6、7、8、9、10共10包,其中4、6、8这3个包全丢失了,由于TCP的ACK是确认最后连续收到序号,这样发送端只能收到3号包的ACK,这样在TIME_OUT的时候,发送端就面临下面两个重传选择:
仅重传4号包
优点:按需重传,能够最大程度节省带宽。
缺点:重传会比较慢,因为重传4号包后,需要等下一个超时才会重传6号包
重传3号后面所有的包,也就是重传4~10号包
优点:重传较快,数据能够较快交付给接收端。
缺点:重传了很多不必要重传的包,浪费带宽,在出现丢包的时候,一般是网络拥塞,大量的重传又可能进一步加剧拥塞。
上面的问题是由于单纯以时间驱动来进行重传的,都必须等待一个超时时间,不能快速对当前网络状况做出响应,如果加入以数据驱动呢?
TCP引入了一种叫Fast Retransmit(快速重传 )的算法,就是在连续收到3次相同确认号的ACK,那么就进行重传。这个算法基于这么一个假设,连续收到3个相同的ACK,那么说明当前的网络状况变好了,可以重传丢失的包了。
快速重传解决了timeout的问题,但是没解决重传一个还是重传多个的问题。出现难以决定是否重传多个包问题的根源在于,发送端不知道那些非连续序号的包已经到达接收端了,但是接收端是知道的,如果接收端告诉一下发送端不就可以解决这个问题吗?于是,RFC2018提出了 SACK(Selective Acknowledgment)——选择确认机制,SACK是TCP的扩展选项
一个SACK的例子如下图,红框说明:接收端收到了0-5500,8000-8500,7000-7500,6000-6500的数据了,这样发送端就可以选择重传丢失的5500-6000,6500-7000,7500-8000的包。
SACK依靠接收端的接收情况反馈,解决了重传风暴问题,这样够了吗?接收端能不能反馈更多的信息呢?显然是可以的,于是,RFC2883对对SACK进行了扩展,提出了D-SACK,也就是利用第一块SACK数据中描述重复接收的不连续数据块的序列号参数,其他SACK数据则描述其他正常接收到的不连续数据。这样发送方利用第一块SACK,可以发现数据段被网络复制、错误重传、ACK丢失引起的重传、重传超时等异常的网络状况,使得发送端能更好调整自己的重传策略。
D-SACK,有几个优点:
发送端可以判断出,是发包丢失了,还是接收端的ACK丢失了。(发送方,重传了一个包,发现并没有D-SACK那个包,那么就是发送的数据包丢了;否则就是接收端的ACK丢了,或者是发送的包延迟到达了)
发送端可以判断自己的RTO是不是有点小了,导致过早重传(如果收到比较多的D-SACK就该怀疑是RTO小了)。
发送端可以判断自己的数据包是不是被复制了。(如果明明没有重传该数据包,但是收到该数据包的D-SACK)
发送端可以判断目前网络上是不是出现了有些包被delay了,也就是出现先发的包却后到了。
TCP的流量控制ACK携带两个信息。
期待要收到下一个数据包的编号
接收方的接收窗口的剩余容量
TCP的标准窗口最大为2^16-1=65535个字节
TCP的选项字段中还包含了一个TCP窗口扩大因子,option-kind为3,option-length为3个字节,option-data取值范围0-14
窗口扩大因子用来扩大TCP窗口,可把原来16bit的窗口,扩大为31bit。这个窗口是接收端告诉发送端自己还有多少缓冲区可以接收数据。于是发送端就可以根据这个接收端的处理能力来发送数据,而不会导致接收端处理不过来。也就是:
发送端是根据接收端通知的窗口大小来调整自己的发送速率的,以达到端到端的流量控制——Sliding Window(滑动窗口)。
TCP的窗口机制TCP协议里窗口机制有2种:一种是固定的窗口大小;一种是滑动的窗口。
这个窗口大小就是我们一次传输几个数据。对所有数据帧按顺序赋予编号,发送方在发送过程中始终保持着一个发送窗口,只有落在发送窗口内的帧才允许被发送;同时接收方也维持着一个接收窗口,只有落在接收窗口内的帧才允许接收。这样通过调整发送方窗口和接收方窗口的大小可以实现流量控制。
下面一张图来分析一下固定窗口大小有什么问题