系列文章:
01-计算机网络的体系结构
02-IP网络层
03-TCP和UDP的基本认识
04-TCP三握四挥
05-TCP的重传机制
06-TCP的流量控制,拥塞控制
07-Http的基础知识
08-HTTPS与加密
09-其它
参考文章:
小林coding: 你还在为 TCP 重传、滑动窗口、流量控制、拥塞控制发愁吗?看完图解就不愁了
一.滑动窗口协议(连续ARQ协议)
为什么会出现滑动窗口?
在确认应答策略(停止等待协议)中,对每一个发送的数据段,都要给一个ACK确认应答,收到ACK后再发送下一个数据段,这样做有一个比较大的缺点,就是性能比较差
,尤其是数据往返的时间长的时候。
为解决这个问题,TCP 引入了窗口这个概念。(它实际上是操作系统开辟的一个缓存空间,发送方主机在等到确认应答返回之前,必须在缓冲区中保留已发送的数据。如果按期收到确认应答,此时数据就可以从缓存区清除。)即使在往返时间较长的情况下,它也不会降低网络通信的效率。
窗口大小就是指无需等待确认应答,而可以继续发送数据的最大值。它是由接收端来定的,接收端通过TCP里面的window字段告诉发送端自己还有多少缓冲区可以接收数据。于是发送端就可以根据这个接收端的处理能力来发送数据,而不会导致接收端处理不过来。
发送方的滑动窗口
TCP 滑动窗口方案使用三个指针来跟踪在四个传输类别中的每一个类别中的字节。其中两个指针是绝对指针(指特定的序列号),一个是相对指针(需要做偏移)。
SND.WND、SND.UN、SND.NXT
SND.WND
:表示发送窗口的大小(大小是由接收方指定的);SND.UNA
:是一个绝对指针,它指向的是已发送但未收到确认的第一个字节的序列号,也就是 #2 的第一个字节。SND.NXT
:也是一个绝对指针,它指向未发送但可发送范围的第一个字节的序列号,也就是 #3 的第一个字节。- 指向 #4 的第一个字节是个相对指针,它需要
SND.UNA
指针加上SND.WND
大小的偏移量,就可以指向 #4 的第一个字节了。
那么可用窗口大小的计算就可以是:
可用窗口大 = SND.WND —(SND.NXT —SND.UNA)
接收方的滑动窗口
接下来我们看看接收方的窗口,接收窗口相对简单一些,根据处理的情况划分成三个部分:
接收窗口
其中三个接收部分,使用两个指针进行划分:
RCV.WND
:表示接收窗口的大小,它会通告给发送方。RCV.NXT
:是一个指针,它指向期望从发送方发送来的下一个数据字节的序列号,也就是 #3 的第一个字节。- 指向 #4 的第一个字节是个相对指针,它需要
RCV.NXT
指针加上RCV.WND
大小的偏移量,就可以指向 #4 的第一个字节了。
接收窗口和发送窗口的大小是相等的吗?
并不是完全相等,接收窗口的大小是约等于发送窗口的大小的。
因为滑动窗口并不是一成不变的。比如,当接收方的应用进程读取数据的速度非常快的话,这样的话接收窗口可以很快的就空缺出来。那么新的接收窗口大小,是通过 TCP 报文中的 Windows 字段来告诉发送方。那么这个传输过程是存在时延的,所以接收窗口和发送窗口是约等于的关系。
窗口关闭
如果接收方有大量负载,窗口大小为 0 时,就会阻止发送方给接收方传递数据,直到窗口变为非 0 为止,这就是窗口关闭。
窗口关闭有一个潜在的危险就是,接收方向发送方通告窗口大小时,是通过 ACK
报文来通告的。当发生窗口关闭时,接收方处理完数据后,会向发送方通告一个窗口非 0 的 ACK 报文,如果这个通告窗口的 ACK 报文在网络中丢失了。那么发送方一直在等待接收方的非 0 窗口通知,接收方一直在等待发送方的数据,此时就会造成死锁现象。
为了解决这个问题,TCP 为每个连接设有一个持续定时器,只要 TCP 连接一方收到对方的零窗口通知,就启动持续计时器。如果持续计时器超时,就会发送窗口探测 ( Window probe ) 报文,而对方在确认这个探测报文时,给出自己现在的接收窗口大小。如果接收窗口仍然为 0,那么收到这个报文的一方就会重新启动持续计时器;窗口探查探测的次数一般为 3 此次,每次大约 30-60 秒(不同的实现可能会不一样)。如果 3 次过后接收窗口还是 0 的话,有的 TCP 实现就会发 RST
报文来中断连接。
总结:
由于TCP的应答策略需要每一个发送的数据段,都要给一个ACK响应。只有收到响应后,才能发送下一个数据段。这样做使得数据传输的性能较差,网络的吞吐量太低。所以引入了滑动窗口协议。它可以在一定的范围内,无需等待确认应答,而继续发送数据。
首先接收方会根据自己的缓存区大小确定窗口大小,然后放在TCP头部的window字段中传递给发送方。
发送方拿到之后,就根据这个窗口大小和拥塞控制的窗口大小来确定自己的窗口大小。只要不超过窗口大小,就可以一直给接受方发送数据而无需确认应答。并且这些发送的数据会存放在发送方的缓存空间中,只有按期收到确认应答,数据才可以从缓存区清除,并且把发送方的滑动窗口向后移动,继续发送未发送但在接收方处理范围之内的数据。如果触发了重传机制,比如超时重传,或者快速重传,就会使用这里的数据对需要重传的进行重传。
接收方拿到数据后先放到了自己的缓存区里,然后根据自己的处理情况对缓存区中的数据进行处理,并把新的缓存区大小也就是窗口大小和ACK包一块返回给接收方。重复上述过程。
二.流量控制
如果发送者发送数据过快,接收者来不及接收,那么就会有分组丢失。为了避免分组丢失,控制发送者的发送速度,使得接收者来得及接收,这就是流量控制。流量控制根本目的是防止分组丢失,它是构成TCP可靠性的一方面。
由滑动窗口协议(连续ARQ协议)实现。滑动窗口协议既保证了分组无差错、有序接收,也实现了流量控制。主要的方式就是接收方返回的 ACK 中会包含自己的接收窗口的大小,并且利用大小来控制发送方的数据发送。
三. 累计确认,累计应答
由于TCP的滑动窗口协议,一次可以向接收端发送多个数据包,比如连续发送了 400499,500599,600~699三个TCP段,接收端都接收到了,并返回对应的ACK给发送端。如果ACK 600 在发送时丢失了,但是接收到了ACK 700,此时就可以通过接收到的ACk 700 来确认500~599的数据已经被接收到了。实际上,只要接收到了ACK 700,就可以认为他前面的所有数据都接收到了,这就是累计确认。
四. 拥塞控制
为什么要有拥塞控制呀,不是有流量控制了吗?
流量控制是避免「发送方」的数据填满「接收方」的缓存,但是并不知道网络的中发生了什么。
在网络出现拥堵时,如果继续发送大量数据包,可能会导致数据包时延、丢失等,这时 TCP 就会重传数据,但是一重传就会导致网络的负担更重,于是会导致更大的延迟以及更多的丢包,这个情况就会进入恶性循环被不断地放大….
于是,就有了拥塞控制,控制的目的就是避免「发送方」的数据填满整个网络。
什么是拥塞窗口?和发送窗口有什么关系呢?
拥塞窗口 cwnd是发送方维护的一个 的状态变量,它会根据网络的拥塞程度动态变化的。
发送窗口 swnd
和接收窗口 rwnd
是约等于的关系,那么由于入了拥塞窗口的概念后,此时发送窗口的值是拥塞窗口和接收窗口中的最小值。
那么怎么知道当前网络是否出现了拥塞呢?
其实只要「发送方」没有在规定时间内接收到 ACK 应答报文,也就是发生了超时重传,就会认为网络出现了拥塞。
拥塞控制有哪些控制算法?
拥塞控制主要是四个算法:
-
慢启动
-
拥塞避免
-
拥塞发生
-
快速恢复
慢启动
TCP 在刚建立连接完成后,首先是有个慢启动的过程,这个慢启动的意思就是一点一点的提高发送数据包的数量。
比如连接建立完成后,一开始初始化拥塞窗口大小为1,表示可以传一个 MSS
大小的数据。当收到这个ACK确认应答后,cwnd变为2发送,然后收到确认消息后,cwnd变为4。。。核心就是每次扩大2倍。发包的个数是指数性的增长。
但是这个拥塞窗口大小并不是无限制一直增长的,有一个叫慢启动阈值 ssthresh
(slow start threshold)状态变量。
- 当
cwnd < ssthresh
时,使用慢启动算法。 - 当
cwnd >= ssthresh
时,就会使用「拥塞避免算法」。
但是TCP在通信开始时,并没有设置慢启动阈值,而是在触发重传机制后,才把慢启动阈值设置为当时拥塞窗口大小的一半。
拥塞避免
当拥塞窗口 cwnd
「超过」慢启动门限 ssthresh
就会进入拥塞避免算法。它的规则是:每当收到一个 ACK 时,cwnd 增加1,变成了线性增长。
拥塞发生
当网络出现拥塞,也就是会发生数据包重传,重传机制主要有两种:
- 超时重传
- 快速重传
这两种使用的拥塞发送算法是不同的。
发生超时重传的拥塞发生算法
当发生了「超时重传」,则就会使用拥塞发生算法。
这个时候,sshresh 和 cwnd 的值会发生变化:ssthresh
设为 出现拥塞时的发送窗口大小的一半
(乘法减小),cwnd重置为
1。接着就重新开始慢启动。
发生快速重传的拥塞发生算法
TCP 认为这种情况不严重,因为大部分没丢,只丢了一小部分。 ssthresh
变为 出现拥塞时的拥塞窗口大小的一半
(乘法减小),cwnd的大小同样设置为原来的一半。然后进入快速恢复算法。
快速恢复
快速重传和快速恢复算法一般同时使用,快速恢复算法是认为,你还能收到 3 个重复 ACK 说明网络也不那么糟糕,所以没有必要像 RTO
超时那么强烈。
在进入快速恢复算法之前,cwnd
和 ssthresh
已经更新了。
然后,进入快速恢复算法如下:
- 拥塞窗口
cwnd = ssthresh + 3
( 3 的意思是确认有 3 个数据包被收到了) - 重传丢失的数据包
- 如果再收到重复的 ACK,那么 cwnd 增加 1
- 如果收到新数据的 ACK 后,设置 cwnd 为 ssthresh,接着就进入了拥塞避免算法