补充tcp协议和滑动窗口我们在向对端发送数据的时候,并不是随机发送数据,因为TCP建立连接之后,建立的是管道,其实和管道是有区别的。很多工作设备,如路由器、交换机等,都会缓存接收到的TCP数据包,以实现排序再发送,但这些设备并不是只为一个TCP连接传输数据包,大量的网络数据包可能存储空间耗尽,导致TCP连接吞吐量急剧下降。为了避免在这种情况下发送,TCP的设计必须是一个无私的协议,它必须检测到这个网络拥塞问题,否则我们想一想,一旦发生拥塞(判断是丢包还是重传),如果TCP只能做重传,那么重传数据包就会让网络上的包变多,网络的负担就会变重,导致延迟变大,丢包变多,这样就会进入恶性循环。如果网络上所有的TCP连接都这样,那么马上就会形成“网络风暴”,拖垮整个网络,也是一场灾难。那么TCP应该能够检测到这种情况。当发生拥堵时,必须做出自我牺牲,就像堵车一样。每辆车都应该让路,而不是抢路。这称为拥塞控制。这是怎么控制的?首先,我们要看看TCP是如何充分利用网络的。TCP实际上是在逐渐检测这个通道的最大传输容量。这种循序渐进的探索,就是我们要讲的慢启动算法。这种慢启动算法是:新建立的连接一开始不能发送大量的数据包,而是根据网络情况逐渐增加每次发送的数据包数量。具体工作步骤为:慢启动算法:发送端维护一个拥塞窗口。一开始,拥塞窗口(cwnd,congestionwindow)被设置为1,这个1代表一个MSS字节。如果每收到一个ACK,那么cwnd(2,4,8,16,32,64)就会呈指数增长。事实上,它不会继续呈指数增长。TCP会设置一个慢启动阈值(ssthresh,慢启动阈值,65535字节),当cwnd>=ssthresh时,进入拥塞避免阶段。当在拥塞避免阶段收到ACK时,cwnd=cwnd+1/cwnd;每当RTT通过时,cwnd=cwnd+1;这样就减缓了拥塞窗口的增长速度,避免了过快增长造成的网络拥塞。慢慢增加并调整到网络的最佳值。如果在此过程中出现拥塞,则会进入拥塞状态。拥塞状态如何判断拥塞状态?只要出现丢包,就认为处于拥塞状态。进入拥塞状态也有两种情况:1)等到RTO超时(重传超时),重传数据包。TCP认为这种情况太糟糕了,反应非常强烈:sshthresh=cwnd/2cwnd被重置为1进入慢启动过程,快速重传。2)当连续收到3个重复ACK时,重传数据包,不等待RTO。这种情况就是下面的快速重传。[问题]什么情况下会出现3个重复ACK?当TCP收到一个乱序的报文段时,它会立即发送一个重复的ACK,这个ACK不能延迟。如果连续收到3个或更多的重复ACK,TCP将判定该段丢失,需要重传,无需等待RTO。这称为快速重传。TCPTahoe的实现和RTO超时是一样的。TCPReno的实现是:sshthresh=cwndcwnd=cwnd/2进入快速恢复算法-FastRecovery上面我们可以看到RTO超时后,sshthresh会变成cwnd的一半,也就是说如果cwnd<=sshthresh出现数据包loss,那么TCP的sshthresh就会减半,然后当cwnd以指数增长的速度快速攀升到这个地方的时候,就会变成缓慢的线性增长。通过这种强烈的震荡,我们可以看到TCP是如何快速而细致地找到网站流量的平衡点的。快速恢复算法TCPReno该算法在RFC5681中定义。快速重传和快速恢复算法一般是同时使用的。fastrecovery算法认为你还有3个DuplicatedAcks,说明网络没那么差,所以没必要强到RTO超时。注意,前面说过,在进入FastRecovery之前,cwnd和sshthresh已经更新了:sshthresh=cwndcwnd=cwnd/2那么,真正的FastRecovery算法如下:cwnd=sshthresh+3*MSS(3表示有收到3个数据包)重传DuplicatedACKs指定的数据包如果再次收到重复的Ack,则cwnd=cwnd+1如果收到新的Ack,则,cwnd=sshthresh,表示恢复过程结束,然后进入拥塞避免算法。如果我们仔细思考上面的算法,就会知道上面的算法也有一个问题,那就是——它依赖于3个重复的Acks。注意3次重复的Acks并不代表只有1个数据包丢失,很有可能很多包都丢失了。但是这个算法只会重传一个,剩下的包只能等到RTO超时,所以进入噩梦模式——超时窗口会减半,多次超时会导致TCP的传输速度按比例下降,并且不会触发快速恢复算法。TCPNewReno于是,1995年,TCPNewReno(参见RFC6582)算法被提出:当发送方收到3个DuplicatedAcks时,进入FastRetransimit模式,发展为重传重复Acks指示的数据包。如果只有这一个数据包丢失,那么重传这个数据包后返回的Ack将ack返回发送方已经发送的全部数据。如果不是,则意味着有多个数据包丢失。我们称此ACK为部分ACK。Sender一旦发现PartialACK出现,就可以推断出有多个包丢失,于是继续重传滑动窗口中第一个没有被ack的包。直到不再收到PartialAck,FastRecovery的过程才真正结束。我们可以看到,这种“改变FastRecovery”是一种非常激进的玩法,它同时延长了FastRetransmit和FastRecovery的过程。
