为什么还需要快速重传机制?
超时重传是按时间来驱动的,如果是网络状况真的不好的情况,超时重传没问题,但是如果网络状况好的时候,只是恰巧丢包了,那等这么长时间就没必要。
于是又引入了数据驱动的重传叫快速重传,什么意思呢?就是发送方如果连续三次收到对方相同的确认号,那么马上重传数据。
因为连续收到三次相同 ACK 证明当前网络状况是 ok 的,那么确认是丢包了,于是立马重发,没必要等这么久。
看起来好像挺完美的,但是你有没有想过我发送1、2、3、4这4个包,就 2 对方没收到,1、3、4都收到了,然后不管是超时重传还是快速重传反正对方就回 ACK 2。
这时候要重传 2、3、4 呢还是就 2 呢?
SACK 的引入是为了解决什么问题?
SACK 即 Selective Acknowledgment,它的引入就是为了解决发送方不知道该重传哪些数据的问题。
我们来看一下下面的图就知道了。
SACK 就是接收方会回传它已经接受到的数据,这样发送方就知道哪一些数据对方已经收到了,所以就可以选择性的发送丢失的数据。
如图,通过 ACK 告知我接下来要 5500 开始的数据,并一直更新 SACK,6000-6500 我收到了,6000-7000的数据我收到了,6000-7500的数据我收到了,发送方很明确的知道,5500-5999 的那一波数据应该是丢了,于是重传。
而且如果数据是多段不连续的, SACK 也可以发送,比如 SACK 0-500,1000-1500,2000-2500。就表明这几段已经收到了。
D-SACK 又是什么东西?
D-SACK 其实是 SACK 的扩展,它利用 SACK 的第一段来描述重复接受的不连续的数据序号,如果第一段描述的范围被 ACK 覆盖,说明重复了,比如我都 ACK 到6000了你还给我回 SACK 5000-5500 呢?
说白了就是从第一段的反馈来和已经接受到的 ACK 比一比,参数是 tcp_dsack,Linux 2.4 之后默认开启。
那知道重复了有什么用呢?
1、知道重复了说明对方收到刚才那个包了,所以是回来的 ACK 包丢了。 2、是不是包乱序的,先发的包后到? 3、是不是自己太着急了,RTO 太小了? 4、是不是被数据复制了,抢先一步呢?
滑动窗口干嘛用?
我们已经知道了 TCP 有序号,并且还有重传,但是这还不够,因为我们不是愣头青,还需要根据情况来控制一下发送速率,因为网络是复杂多变的,有时候就会阻塞住,而有时候又很通畅。
所以发送方需要知道接收方的情况,好控制一下发送的速率,不至于蒙着头一个劲儿的发然后接受方都接受不过来。
因此 TCP 就有个叫滑动窗口的东西来做流量控制,也就是接收方告诉发送方我还能接受多少数据,然后发送方就可以根据这个信息来进行数据的发送。
以下是发送方维护的窗口,就是黑色圈起来的。
图中的 #1 是已收到 ACK 的数据,#2 是已经发出去但是还没收到 ACK 的数据,#3 就是在窗口内可以发送但是还没发送的数据。#4 就是还不能发送的数据。
然后此时收到了 36 的 ACK,并且发出了 46-51 的字节,于是窗口向右滑动了。
TCP/IP Guide 上还有一张完整的图,画的十分清晰,大家看一下。
如果接收方回复的窗口一直是 0 怎么办?
上文已经说了发送方式根据接收方回应的 window 来控制能发多少数据,如果接收方一直回应 0,那发送方就杵着?
你想一下,发送方发的数据都得到 ACK 了,但是呢回应的窗口都是 0 ,这发送方此时不敢发了啊,那也不能一直等着啊,这 Window 啥时候不变 0 啊?
于是 TCP 有一个 Zero Window Probe 技术,发送方得知窗口是 0 之后,会去探测探测这个接收方到底行不行,也就是发送 ZWP 包给接收方。
具体看实现了,可以发送多次,然后还有间隔时间,多次之后都不行可以直接 RST。
假设接收方每次回应窗口都很小怎么办?
你想象一下,如果每次接收方都说我还能收 1 个字节,发送方该不该发?
TCP + IP 头部就 40 个字节了,这传输不划算啊,如果傻傻的一直发这就叫 Silly Window。
那咋办,一想就是发送端等着,等养肥了再发,要么接收端自己自觉点,数据小于一个阈值就告诉发送端窗口此时是 0 算了,也等养肥了再告诉发送端。
发送端等着的方案就是纳格算法,这个算法相信看一下代码就知道了。
简单的说就是当前能发送的数据和窗口大于等于 MSS 就立即发送,否则再判断一下之前发送的包 ACK 回来没,回来再发,不然就攒数据。
接收端自觉点的方案是 David D Clark’s 方案,如果窗口数据小于某个阈值就告诉发送方窗口 0 别发,等缓过来数据大于等于 MSS 或者接受 buffer 腾出一半空间了再设置正常的 window 值给发送方。
对了提到纳格算法不得不再提一下延迟确认,纳格算法在等待接收方的确认,而开启延迟确认则会延迟发送确认,会等之后的包收到了再一起确认或者等待一段时候真的没了再回复确认。
这就相互等待了,然后延迟就很大了,两个不可同时开启。
已经有滑动窗口了为什么还要拥塞控制?
前面我已经提到了,加了拥塞控制是因为 TCP 不仅仅就管两端之间的情况,还需要知晓一下整体的网络情形,毕竟只有大家都守规矩了道路才会通畅。
前面我们提到了重传,如果不管网络整体的情况,肯定就是对方没给 ACK ,那我就无脑重传。
如果此时网络状况很差,所有的连接都这样无脑重传,是不是网络情况就更差了,更加拥堵了?
然后越拥堵越重传,一直冲冲冲!然后就 GG 了。
所以需要个拥塞控制,来避免这种情况的发送。
拥塞控制怎么搞?
主要有以下几个步骤来搞:
1、慢启动,探探路。 2、拥塞避免,感觉差不多了减速看看 3、拥塞发生快速重传/恢复
慢启动,就是新司机上路慢慢来,初始化 cwnd(Congestion Window)为 1,然后每收到一个 ACK 就 cwnd++ 并且每过一个 RTT ,cwnd = 2*cwnd 。
线性中带着指数,指数中又夹杂着线性增。
然后到了一个阈值,也就是 ssthresh(slow start threshold)的时候就进入了拥塞避免阶段。
这个阶段是每收到一个 ACK 就 cwnd = cwnd + 1/cwnd并且每一个 RTT 就 cwnd++。
可以看到都是线性增。
然后就是一直增,直到开始丢包的情况发生,前面已经分析到重传有两种,一种是超时重传,一种是快速重传。
如果发生超时重传的时候,那说明情况有点糟糕,于是直接把 ssthresh 置为当前 cwnd 的一半,然后 cwnd 直接变为 1,进入慢启动阶段。
如果是快速重传,那么这里有两种实现,一种是 TCP Tahoe ,和超时重传一样的处理。
一种是 TCP Reno,这个实现是把 cwnd = cwnd/2 ,然后把 ssthresh 设置为当前的 cwnd 。
然后进入快速恢复阶段,将 cwnd = cwnd + 3(因为快速重传有三次),重传 DACK 指定的包,如果再收到一个DACK则 cwnd++,如果收到是正常的 ACK 那么就将 cwnd 设为 ssthresh 大小,进入拥塞避免阶段。
可以看到快速恢复就重传了指定的一个包,那有可能是很多包都丢了,然后其他的包只能等待超时重传,超时重传就会导致 cwnd 减半,多次触发就指数级下降。
所以又搞了个 New Reno,多加了个 New,它是在没有SACK 的情况下改进快速恢复,它会观察重传 DACK 指定的包的响应 ACK 是否是已经发送的最大 ACK,比如你发了1、2、3、4,对方没收到 2,但是 3、4都收到了,于是你重传 2 之后 ACK 肯定是 5,说明就丢了这一个包。
不然就是还有其他包丢了,如果就丢了一个包就是之前的过程一样,如果还有其他包丢了就继续重传,直到 ACK 是全部的之后再退出快速恢复阶段。
简单的说就是一直探测到全部包都收到了再结束这个环节。
还有个 FACK,它是基于 SACK 用来作为重传过程中的拥塞控制,相对于上面的 New Reno 我们就知道它有 SACK 所以不需要一个一个试过去,具体我不展开了。
还有哪些拥塞控制算法?
从维基上看有这么多。
本来我还想哔哔几句了,哔哔了之后又删了,感觉说了和没说一样,想深入但是实力不允许,有点惆怅啊。
各位看官自个儿查查吧,或者等我日后修炼有成再来哔哔。
总结
说了这么多来总结一下吧。
TCP 是面向连接的,提供可靠、有序的传输并且还提供流控和拥塞控制,单独提取出 TCP 层而不是在 IP层实现是因为 IP 层有更多的设备需要使用,加了复杂的逻辑不划算。
三次握手主要是为了定义初始序列号为了之后的传输打下基础,四次挥手是因为 TCP 是全双工协议,因此双方都得说拜拜。
SYN 超时了就阶梯性重试,如果有 SYN攻击,可以加大半队列数,或减少重试次数,或直接拒绝。
TIME_WAIT 是怕对方没收到最后一个 ACK,然后又发了 FIN 过来,并且也是等待处理网络上残留的数据,怕影响新连接。
TIME_WAIT 不建议设小,或者破坏 TIME_WAIT 机制,如果真想那么可以开启快速回收,或者重用,不过注意受益的对象。
超时重传是为了保证对端一定能收到包,快速重传是为了避免在偶尔丢包的时候需要等待超时这么长时间,SACK 是为了让发送方知道重传哪些。
D-SACK 是为了让发送方知道这次重传的原因是对方真的没收到还是自己太心急了 RTO 整小了,不至于两眼一抹黑。
滑动窗口是为了平衡发送方的发送速率和接收方的接受数率,不至于瞎发,当然还需要注意 Silly Window 的情况,同时还要注意纳格算法和延迟确认不能一起搭配。
而滑动窗口还不够,还得有个拥塞控制,因为出行你我他,安全靠大家,TCP 还得跳出来看看关心下当前大局势。
最后
至此就差不多了,不过还是有很多很多细节的,TCP 协议太复杂了,这可能是我文章里面图画的最少的一篇了,你看复杂到我图都画不来了哈哈哈。
今天我就说了个皮毛,如有纰漏请赶紧后台联系鞭挞我。