建立TCP连接的双方都维护着一个缓冲区, 当缓冲区满了的时候再接收数据会造成溢出, 就会丢弃数据.
因此, 为了消除这种现象, TCP提供了流量控制服务.
另外, 由于网络带宽等因素的限制, 发送方发出的数据并不能总是顺利地到达接收方, 当路由器缓冲区
满了的时候, 再有数据到达会被丢弃, 从而造成数据丢失, 针对这种现象, TCP同样提供了拥塞控制服务
流量控制
如上所述, TCP连接的建立会产生两个缓冲区 RcvByffer, 我们假设建立连接的双方为A和B
TCP通过让发送方A维护一个称为接收窗口 rwnd 的变量来提供流量控制
定义:
- LastByteRead : 主机B上缓冲区被上层读取的最后一个字节编号
- LastByteRcvd : 主机B缓冲区接收的最后一个字节编号
为了不使缓冲区溢出, 下面的式子必须成立:
LastByteRcvd - LastByteRead <= RcvByffer
则接收窗口表示为:
rwnd = RcvByffer - (LastByteRcvd - LastByteRead)
其实就是接收方缓冲区的空闲空间
因此, 对于发送方A来说, 其必须保证已经发送出去,但是未收到ACK的数据 <= rwnd
为了让发送方A时刻满足上述条件, 接收方B每次回复A时会将接收窗口大小告诉A.
但是, 有一个问题, 假如B告诉A接收窗口为0, A不再发送数据, 但是之后B的缓冲区有空闲了, 由于没有数据到达, B无法告知A缓冲区可以接收新的数据, A将一直等待.
为了解决上述情况, TCP规范规定, 当接收方B告知A缓冲区已满, A继续向B发送一个字节的数据, B将会收到这个数据,并在缓冲区有空时通過確認报文告知A.
拥塞控制
同流量控制类似, 发送方也通过维护一个额外变量拥塞窗口 cwnd来提供拥塞控制服务.
因此, 发送方已经发送出去,但是未收到ACK的数据 <= MIN{rwnd, cwnd}
再介绍拥塞控制之前先说说哪些情况对于TCP发送方来说意味着出现拥塞
- 当出现超时情况时, 认为发生了丢包, 需要进行拥塞控制
- 当收到连续的3个冗余ACK时, 可能发生了丢包, 也意味着拥塞
而当收到正常的ACK时, 发送方认为网络状况良好, 会提高发送速率
TCP拥塞控制算法包括3个部分:慢启动, 拥塞避免和快速恢复
慢启动
当一条TCP连接刚开始时, 发送方不知道当前的网络状况如何, 因此最好的办法是试探, 先发送一个 MSS (TCP报文数据字段最大值), 如果正常收到 ACK, 下次发送 两个 MSS, 如果还是正常, 再下次就发送 四个 MSS。。。。
所以慢启动算法可以描述如下:
发送方最初将 cwnd 设置为一个MSS 大小, 当收到正常的ACK时, 将 cwnd 增加一个 MSS, 一次性发送 2*MSS
. 然后再每收到一个正常ACK时 cwnd 增加一个 MSS, 当之前发送的两个 MSS 都收到 ACK后下次发送时就一次性发送 4*MSS
. 如此, 在慢启动阶段, cwnd 大小会呈 2^n
次增长。 当然, 这种指数增长状态不会一直持续下去。
慢开始 状态不会一直持续下去, 网络的承载能力总是有限的, 因此有个变量 ssthresh
即慢启动阈值 来限制慢启动状态的 cwnd
增长。
阈值就相当与分界线, 在分界线以下是一个状态即 慢启动, 在分界线以上则转为 拥塞避免 状态。阈值的设置有下面两种情况:
- 情况1: 当发生一个 TIME_OUT 丢包时, 发送方 将状态量
ssthresh
(慢启动阈值) 设为cwnd/2
, 然后置 cwnd 为1*MSS
. 继续慢启动开始的增长状态。 - 情况2: 当检测到3个冗余ACK时, 说明可能发生了丢包(注意, 丢包一定产生3个ACK, 但3个ACK不一定就是丢包). TCP 执行快速重传, 并进入快速恢复状态。
我们看到,不管是哪种情况, 只要发生了,就认为当前网络状况不太好,然后就将慢启动阈值 ssthresh
设置为当前拥塞窗口的一半。区别在于不同状况下新的拥塞窗口初始大小不一样。
拥塞避免
前面说到, 当 cwnd >= ssthresh
时从慢启动转向拥塞避免状态, 此时对cwnd的增长不再是指数的, 而是线性的.
在拥塞避免状态, 当收到正常ACK时, cwnd增加 一个 MSS*(MSS/cwnd) 大小 ,当出现下面两种情况时, 就会结束拥塞避免状态:
- 情况1:发生TIME_OUT , 处理方式和慢启动一样,
ssthresh = cwdn/2
,cwnd = 1*MSS
, 并转向慢启动 - 情况2:收到3个A冗余ACK,
ssthresh = cwdn/2
,cwnd = ssthresh+3*MSS
, 处于快速恢复状态。
快速恢复
快速恢复算法是后来对 TCP 拥塞控制进行改进而在慢启动 和 拥塞避免 基础上增加的算法。
快速恢复算法是用来应对包丢失的情况。 当接收方收到乱序的数据包,接收方向发送方发送一个 “请求再次发送丢失的包” 的消息,当请求消息达到三次则开始快速恢复算法。
快速恢复算法之所以不同与慢开始的依据是: 既然发送方能收到接收方的连续三个请求重发的消息, 说明网络拥塞状况并不严重,那就没必要把拥塞窗口一下子降低到一个 MSS 大小。而是把慢启动阈值设为当前拥塞窗口的一半, 并将拥塞窗口初始大小设为 阈值 + 3个MSS 大小。多出的 3个 MSS 理由是:发送方既然已经收到接收方发来的3个重发请求, 说明已经有三个分组被接收方成功接收了,因此窗口可以适当增加3个 MSS
快速恢复算法开始后,发送方向接收方发送之前丢失的包, 当这个包的 ACK 到达后就会重新设置 cwdn 然后转为 拥塞控制
状态。所以说快速恢复状态的持续时间很短。
具体描述为:
TCP发送方会将阈值和拥塞窗口的值分别设置为 ssthresh = cwdn/2
, cwnd = ssthresh+3*MSS
。
- 最终当丢失报文段的第一个ACK到达发送方时,TCP发送方**再次降低cwdn**进入拥塞避免 此时 `cwdn = ssthresh`.
- 而当出现TIME_OUT时, 则令 `ssthresh = cwdn/2`, `cwdn=1*MSS` 转向慢启动
总结:
- 无论在哪种状态, 当发生TIME_OUT时 ,令
ssthresh = cwnd/2
,cwdn = 1*MSS
转向慢启动. - 无论在哪种状态, 当发生3个冗余ACK时, 令
ssthresh = cwnd/2
,cwnd = ssthresh + 3*MSS
, 转向快速恢复 - 处于慢启动时, 当
cwnd >= ssthresh
会转向 拥塞避免; - 处于快速恢复时, 当丢失报文段的ACK第一次到达时,令
cwnd = ssthresh
转向拥塞避免