上篇文章,我们讲了TCP协议的连接管理(”三次握手“和”四次挥手“的过程)。
4、滑动窗口
这个滑动窗口是TCP中非常有特点的机制。我们知道,TCP是通过前面讲的三个机制:确认应答,超时重传,连接管理,实现了可靠传输。
但是,为了实现可靠传输,同时也付出代价,单位时间传输的数据量变少了(传输效率降低了)。
在确认应答机制下,每次发送方收到一个ack才会发送下一个数据,就会导致有大量时间都消耗在等待ack上了,此处等待消耗的时间成本是非常多的。我们希望的是,在保证可靠传输的前提下,尽可能让效率高些,让消耗的时间成本少些。
滑动窗口这个机制就是为了解决上述问题的,滑动窗口可以在保证可靠传输的基础上,提高效率。(这里的提高效率,其实是降低了时间的损耗,而不是增加了传输速度)。
虽然通过滑动窗口这个机制,一定程度上提高了效率,但还是不可能高于UDP这种不需要可靠性的协议。
滑动窗口,其实就是批量传输:发一个数据,然后不等ack,再发下一条,继续往下发,连续发了一定的数据之后,统一等一波ack。——把多次请求等待ack的时间,使用同一份时间进行等待了,减少了总的等待时间。
为啥会叫滑动窗口呢?—— 批量传输数据的过程,就像下图窗口滑动的过程,返回一个ack,就窗口就往后移动(发送下一组数据)。
在这幅图上面,黄色区域中的四份数据已经批量传输出去了,传输出这四份数据之后,就等待ack ,暂时先不传输数据了。我们就把黄色区域(不等待ack,能够批量传输的数据量)称为“窗口大小”。
注意:这里并不是等所有ack回来再开始发下一组数据(5001~6001)的,而是等回来一个ack就开始往后发一组数据。
上述发送/返回ack过程都会很快,窗口快速地向后面移动,也就形成一个“滑动”的效果了。因此就把上述过程称为滑动窗口了。
出现丢包的处理
滑动窗口是我们为了提升传输效率的一个机制,但TCP安身立命之本还是可靠性。提升效率的大前提一定是传输是可靠的,如果“滑动窗口”传输的过程中丢包会这么处理呢?
情况一:数据报已经到达,但是ACK丢包了
如上图,主机A的数据都是完好的传到了主机B,但是主机B返回的一些ack出现丢包了,这种情况并不会对我们数据的传输造成影响,我们无需进行任何处理。
注意:这里返回ack 确认序号的含义,比如说返回一个5001,代表5001之前的数据,我(主机B)都已经收到了,就相当于告诉主机A 4001 之前的数据我也已经收到了。
情况二:数据报丢了
数据丢了的情况,此时就需要重传了
如上图1001~2000的数据报丢失了,解决这份数据报丢失的关键要点:在主机A继续向发送数据的时候,主机B的应答报文中返回的确认序号是1001,相当于在向对方索要1001~2000的数据。当发送方向接收方重传了1001~2000的数据报之后(此时已经传到3001~4000的数据了),确认序号就变为了当前所传数据的下一序号(4001)。
在上述重传的过程中,整体的效率是非常高的,这里做到了“针对性重传”。只需要把那个丢了的数据重传,已经收到的数据就不必重传了,我们就把这种重传称为“快速重传”。
注意:
我们前面讲的几个机制:确认应答、超时重传、滑动窗口、快速重传,他们是并不冲突的。
滑动窗口中当然也有确认应答,只不过把等待策略稍作调整,转成批量的了。批量的前提是:你短时间内发了很多数据。如果你发的数据很少,此时滑动窗口滑不起来,就变成了确认应答。
如果当前传输过程是按照滑动窗口(短时间内传输了大量数据)就按照快速重传保证可靠性,此时判定丢包的标准就是看有连续多个ack索要同一个数据。
如果当前传输过程是不是按照滑动窗口(没有传输很多数据),此时仍然按照之前的超时重传保证数据的可靠性,此时判定丢包的标准就是达到超时时间还没有ack到达。
5、流量控制
我们前面讲,通过滑动窗口,可以提高传输速率,窗口大小越大,就有更多的数据复用同一块时间等待ack,效率就越高。
窗口大小是不可能无限大的,因为TCP的安生立命之本是可靠传输,任何提升效率的行为,都不能影响可靠性(如果接收方的接收缓冲区饱和了,你继续再给他发就会丢包了,此时重传也可能没用,反而会浪费硬件资源)。
这就像我们小学数学的蓄水池问题: 与其等接收方的接收缓冲区满了,发送方再停止发送,还不如让发送方提前感知到接收方接收缓冲区的情况,提前减慢发送速度,让发送方发送数据和接收方处理数据的速度,可以做到步调一致。这也就是流量控制。
在TCP报头中,有一个16位窗口大小,就是用来做这个的:
通过这个字段来给发送方反馈接收方的接收能力(接收方接收缓冲区的剩余大小),发送方就会知道接下来的窗口大小要设置成多少合适。
补充:
我们这里TCP报头中,窗口大小是16位,但窗口大小并非是64KB(2的16次方是65535,64KB = 64*1024 = 65535),实际上,TCP报头选项中,还包含了一个参数,叫窗口扩展因字,实际上真是要设置的窗口大小是:16位窗口大小*2^窗口扩展因子。
如上图,当接收方的接收缓冲区为0的时候,此时接收方就应该暂停发送,发送方什么时候恢复发送呢?发送方会周期性地发送一个"窗口探测包",这个窗口探测包,并不会携带任何载荷,这样地包对于业务并不产生影响,只是为了触发接收方的ack,一旦查询出的结果是非0时,就什么缓冲区又有空位了,发送方就又可以继续发送了。