慢启动定义
慢启动, 是传输控制协议使用的一种拥塞控制机制慢启动也叫做指数增长期慢启动是指每次 TCP 接收窗口收到确认时都会增长增加的大小就是已确认段的数目这种情况一直保持到要么没有收到一些段, 要么窗口大小到达预先定义的阈值如果发生丢失事件, TCP 就认为这是网络阻塞, 就会采取措施减轻网络拥挤一旦发生丢失事件或者到达阈值, TCP 就会进入线性增长阶段这时, 每经过一个 RTT 窗口增长一个段
慢启动解析
发送方一开始便向网络发送多个报文段, 直至达到接收方通告的窗口大小为止当发送方和接收方处于同一个局域网时, 这种方式是可以的但是如果在发送方和接收方之间存在多个路由器和速率较慢的链路时, 就有可能出现一些问题一些中间路由器必须缓存分组, 并有可能耗尽存储器的空间现在, TCP 需要支持一种被称为慢启动 (slowstart) 的算法该算法通过观察到新分组进入网络的速率应该与另一端返回确认的速率相同而进行工作慢启动为发送方的 TCP 增加了另一个窗口: 拥塞窗口 (congestionwindow), 记为 cwnd 当与另一个网络的主机建立 TC P 连接时, 拥塞窗口被初始化为 1 个报文段(即另一端通告的报文段大小) 每收到一个 ACK, 拥塞窗口就增加一个报文段 (c w n d 以字节为单位, 但是慢启动以报文段大小为单位进行增加) 发送方取拥塞窗口与通告窗口中的最小值作为发送上限拥塞窗口是发送方使用的流量控制, 而通告窗口则是接收方使用的流量控制发送方开始时发送一个报文段, 然后等待 A C K 当收到该 AC K 时, 拥塞窗口从 1 增加为 2, 即可以发送两个报文段当收到这两个报文段的 A C K 时, 拥塞窗口就增加为 4 这是一种指数增加的关系
为了防止网络的拥塞现象, TCP 提出了一系列的拥塞控制机制最初由 V. Jacobson 在 1988 年的论文中提出的 TCP 的拥塞控制由慢启动 (Slow start) 和拥塞避免 (Congestion avoidance) 组成, 后来 TCP Reno 版本中又针对性的加入了快速重传 (Fast retransmit) 快速恢复 (Fast Recovery) 算法, 再后来在 TCP NewReno 中又对快速恢复算法进行了改进, 近些年又出现了选择性应答 ( selective acknowledgement,SACK) 算法, 还有其他方面的大大小小的改进, 成为网络研究的一个热点
TCP 的拥塞控制主要原理依赖于一个拥塞窗口 (cwnd) 来控制, 在之前我们还讨论过 TCP 还有一个对端通告的接收窗口 (rwnd) 用于流量控制窗口值的大小就代表能够发送出去的但还没有收到 ACK 的最大数据报文段, 显然窗口越大那么数据发送的速度也就越快, 但是也有越可能使得网络出现拥塞, 如果窗口值为 1, 那么就简化为一个停等协议, 每发送一个数据, 都要等到对方的确认才能发送第二个数据包, 显然数据传输效率低下 TCP 的拥塞控制算法就是要在这两者之间权衡, 选取最好的 cwnd 值, 从而使得网络吞吐量最大化且不产生拥塞
由于需要考虑拥塞控制和流量控制两个方面的内容, 因此 TCP 的真正的发送窗口 = min(rwnd, cwnd)但是 rwnd 是由对端确定的, 网络环境对其没有影响, 所以在考虑拥塞的时候我们一般不考虑 rwnd 的值, 我们暂时只讨论如何确定 cwnd 值的大小关于 cwnd 的单位, 在 TCP 中是以字节来做单位的, 我们假设 TCP 每次传输都是按照 MSS 大小来发送数据的, 因此你可以认为 cwnd 按照数据包个数来做单位也可以理解, 所以有时我们说 cwnd 增加 1 也就是相当于字节数增加 1 个 MSS 大小
慢启动: 最初的 TCP 在连接建立成功后会向网络中发送大量的数据包, 这样很容易导致网络中路由器缓存空间耗尽, 从而发生拥塞因此新建立的连接不能够一开始就大量发送数据包, 而只能根据网络情况逐步增加每次发送的数据量, 以避免上述现象的发生具体来说, 当新建连接时, cwnd 初始化为 1 个最大报文段 (MSS) 大小, 发送端开始按照拥塞窗口大小发送数据, 每当有一个报文段被确认, cwnd 就增加 1 个 MSS 大小这样 cwnd 的值就随着网络往返时间 (Round Trip Time,RTT) 呈指数级增长, 事实上, 慢启动的速度一点也不慢, 只是它的起点比较低一点而已我们可以简单计算下:
开始 ---> cwnd = 1
经过 1 个 RTT 后 ---> cwnd = 2*1 = 2
经过 2 个 RTT 后 ---> cwnd = 2*2= 4
经过 3 个 RTT 后 ---> cwnd = 4*2 = 8
如果带宽为 W, 那么经过 RTT*log2W 时间就可以占满带宽
拥塞避免: 从慢启动可以看到, cwnd 可以很快的增长上来, 从而最大程度利用网络带宽资源, 但是 cwnd 不能一直这样无限增长下去, 一定需要某个限制 TCP 使用了一个叫慢启动门限 (ssthresh) 的变量, 当 cwnd 超过该值后, 慢启动过程结束, 进入拥塞避免阶段对于大多数 TCP 实现来说, ssthresh 的值是 65536(同样以字节计算)拥塞避免的主要思想是加法增大, 也就是 cwnd 的值不再指数级往上升, 开始加法增加此时当窗口中所有的报文段都被确认时, cwnd 的大小加 1,cwnd 的值就随着 RTT 开始线性增加, 这样就可以避免增长过快导致网络拥塞, 慢慢的增加调整到网络的最佳值
上面讨论的两个机制都是没有检测到拥塞的情况下的行为, 那么当发现拥塞了 cwnd 又该怎样去调整呢?
首先来看 TCP 是如何确定网络进入了拥塞状态的, TCP 认为网络拥塞的主要依据是它重传了一个报文段上面提到过, TCP 对每一个报文段都有一个定时器, 称为重传定时器(RTO), 当 RTO 超时且还没有得到数据确认, 那么 TCP 就会对该报文段进行重传, 当发生超时时, 那么出现拥塞的可能性就很大, 某个报文段可能在网络中某处丢失, 并且后续的报文段也没有了消息, 在这种情况下, TCP 反应比较强烈:
1. 把 ssthresh 降低为 cwnd 值的一半
2. 把 cwnd 重新设置为 1
3. 重新进入慢启动过程
从整体上来讲, TCP 拥塞控制窗口变化的原则是 AIMD 原则, 即加法增大乘法减小可以看出 TCP 的该原则可以较好地保证流之间的公平性, 因为一旦出现丢包, 那么立即减半退避, 可以给其他新建的流留有足够的空间, 从而保证整个的公平性
其实 TCP 还有一种情况会进行重传: 那就是收到 3 个相同的 ACKTCP 在收到乱序到达包时就会立即发送 ACK,TCP 利用 3 个相同的 ACK 来判定数据包的丢失, 此时进行快速重传, 快速重传做的事情有:
1. 把 ssthresh 设置为 cwnd 的一半
2. 把 cwnd 再设置为 ssthresh 的值(具体实现有些为 ssthresh+3)
3. 重新进入拥塞避免阶段
后来的快速恢复算法是在上述的快速重传算法后添加的, 当收到 3 个重复 ACK 时, TCP 最后进入的不是拥塞避免阶段, 而是快速恢复阶段快速重传和快速恢复算法一般同时使用快速恢复的思想是数据包守恒原则, 即同一个时刻在网络中的数据包数量是恒定的, 只有当老数据包离开了网络后, 才能向网络中发送一个新的数据包, 如果发送方收到一个重复的 ACK, 那么根据 TCP 的 ACK 机制就表明有一个数据包离开了网络, 于是 cwnd 加 1 如果能够严格按照该原则那么网络中很少会发生拥塞, 事实上拥塞控制的目的也就在修正违反该原则的地方
具体来说快速恢复的主要步骤是:
1. 当收到 3 个重复 ACK 时, 把 ssthresh 设置为 cwnd 的一半, 把 cwnd 设置为 ssthresh 的值加 3, 然后重传丢失的报文段, 加 3 的原因是因为收到 3 个重复的 ACK, 表明有 3 个老的数据包离开了网络
2. 再收到重复的 ACK 时, 拥塞窗口增加 1
3. 当收到新的数据包的 ACK 时, 把 cwnd 设置为第一步中的 ssthresh 的值原因是因为该 ACK 确认了新的数据, 说明从重复 ACK 时的数据都已收到, 该恢复过程已经结束, 可以回到恢复之前的状态了, 也即再次进入拥塞避免状态
快速重传算法首次出现在 4.3BSD 的 Tahoe 版本, 快速恢复首次出现在 4.3BSD 的 Reno 版本, 也称之为 Reno 版的 TCP 拥塞控制算法
可以看出 Reno 的快速重传算法是针对一个包的重传情况的, 然而在实际中, 一个重传超时可能导致许多的数据包的重传, 因此当多个数据包从一个数据窗口中丢失时并且触发快速重传和快速恢复算法时, 问题就产生了因此 NewReno 出现了, 它在 Reno 快速恢复的基础上稍加了修改, 可以恢复一个窗口内多个包丢失的情况具体来讲就是: Reno 在收到一个新的数据的 ACK 时就退出了快速恢复状态了, 而 NewReno 需要收到该窗口内所有数据包的确认后才会退出快速恢复状态, 从而更一步提高吞吐量
SACK 就是改变 TCP 的确认机制, 最初的 TCP 只确认当前已连续收到的数据, SACK 则把乱序等信息会全部告诉对方, 从而减少数据发送方重传的盲目性比如说序号 1,2,3,5,7 的数据收到了, 那么普通的 ACK 只会确认序列号 4, 而 SACK 会把当前的 5,7 已经收到的信息在 SACK 选项里面告知对端, 从而提高性能, 当使用 SACK 的时候, NewReno 算法可以不使用, 因为 SACK 本身携带的信息就可以使得发送方有足够的信息来知道需要重传哪些包, 而不需要重传哪些包
来源: http://www.bubuko.com/infodetail-2494124.html