传输层协议TCP—滑动窗口(6)
作者:互联网
1 传输控制
TCP(Transmission Control Protocol 传输控制协议)是一种面向连接的、可靠的、基于字节流的传输层通信协议。在前面的章节中,我们介绍了 TCP 连接的相关概念,也介绍了 TCP 为什么说是一种面向字节流的协议,也基本介绍了为什么说 TCP 是可靠的协议。但是 TCP 中的“C”——控制(Control),体现在哪里呢?在数据传输的过程中,TCP 又是如何控制的呢?要研究 TCP 中的控制,首先要看 TCP 的上下文,如下图5-82所示:
就例如上图来说,TCP 的控制,就是面向发送者(进水速度)、接收者(出水速度)、发送方缓存(发送侧蓄水池)、接收方缓存(接收侧蓄水池)、网络(带宽、QoS(时延、丢包、抖动))的一个控制协议,它既要保证传输的可靠,也要面对各方面的复杂情况。
1.1 滑动窗口
1.1.1 滑动窗口基本概念
TCP 引入滑动窗口的最直接的原因是“接收方的缓存是有限的”。在 “5.2.7 TCP 连接的收发空间”,我们介绍过,TCP 发送方的最大允许发送字节数,也就是发送方的发送窗口(SND.WND),是由其接收报文中的 Window 字段(SEG.WND)所决定的,即 SND.WND = SEG.WND。SND.WND 是 TCP 发送空间其中1个概念,如下图所示:
SND.WND 称为滑动窗口。顾名思义,是可以滑动的。也就说滑动窗口是动态变化的,如图5-85所示:
这个矩形在 X轴上的左坐标为 WL(window left)、右坐标为 WR(window right)。结合图5-84,我们知道:WL = SND.NXT、WR - WL = SND.WND。图5-85也指出了 WL 只能向右滑,WR 可以左右滑。实际上 WL 何时向右滑,滑多少,取决于 TCP 所发送的报文;WR 何时滑动(或左或右),滑多少,取决于 TCP 所接收的报文。如下图5-86所示:
图5-86中,(1)T0时刻:WL = 7,WR = 12,WR - WL = SND.WND = 5。(2)在 T1 时刻,TCP 发送方,发送了1个报文,这个报文的数据长度是2个字节,于是我们看到了 WL 向右滑动:WL = 9。但是此时 WR 是不滑动的,因为 TCP 发送方还没有收到 TCP 接收方的指示。也正因为此,我们看到 T1 时刻,其滑动窗口的大小 SND.WND = 3,实际上是变小了。(3)在 T2 时刻,TCP发送方,接收了1个报文(对方的 ACK 报文),在这个报文中,其指示了接收窗口的大小等于6(SEG.WND = 6),于是我们看到:SND.WND = 6,同时也意味着 WR 向右滑动 WR = WL + SND.WND = 9 + 6 = 15。
图5-86中,T2 时刻的 WR 是大于 T1 时刻的 WR,即 WR2 > WR1,但是在某些场景下也会出现 WR2 < WR1 的情形,如图5-87所示:
上图5-87中,在 T2 时刻,TCP发送方,接收了1个报文(对方的 ACK 报文),在这个报文中,其指示了接收窗口的大小等于2(SEG.WND = 2),于是我们看到:SND.WND = 2,同时也意味着 WR 向右滑动 WR = WL + SND.WND = 9 + 2 = 11。而 WR1 = 12,WR2 = 11,即:WR2 < WR1。这种情形,TCP 称为窗口收缩(shrinking the window)。RFC 793 对待窗口收缩的态度是:强烈不建议,但是也不拒绝。
1.1.2 窗口大小与发送效率
RFC 793 中所定义的 Window 字段有16个 bits,也就说滑动窗口最大可以为65535(不考虑 RFC 1323 的 TCP Window Scale Option)。那么,滑动窗口的大小,与传输效率有什么关系呢?可以用下面这一张图来说明:
图5-88-上,表达的是一个比较大的滑动窗口(比如1000),此时它发送1个100字节长度的数据,只需1对报文(一来一回)即可,所花费的时间记为 t。图5-88-下,表达的是滑动窗口大小只为1的情形,此时它发送1个100字节长度的数据,则需要100对报文(一来一回)。我们忽略网络拥塞等因素,可以简单理解为它所花费的时间是100t。
太小的滑动窗口(即使比1大一点),其引发的问题主要有:
- (1)带来很多不必要的传输延时
- (2)网络带宽利用率极低,而且还很有可能造成网络拥塞
也正是鉴于此,TCP 其中有有很关键的一点就是致力于滑动窗口大小(size)的研究。
1.2 糊涂窗口综合症
对于这种来来回回发送的小包,有一个专有称谓:糊涂窗口综合症(Silly Window Syndrome,SWS)。所谓小包,指的是 TCP 报文中的有效载荷(即数据部分)很小,甚至数据长度只有1个字节。这种情形所引发的问题还是比较大的。
引发糊涂窗口综合症,有3种情形:或者是发送端、或者是接收端、或者是两者兼而有之。对于发送端来说,它产生数据的速度可能很慢(比如 telnet 程序),此时就会引发糊涂窗口综合症。对于接收端来说,原因也是如此——它处理数据的速度很慢——也会引发糊涂窗口综合症,如下图所述:
如上图,接收方一次处理1个字节,仅仅是一个举例,事实上可以不是1个字节,只要是比较小的字节数即可,就会引发糊涂窗口综合症。如果不做一些特殊的处理,在 TCP 的世界里,糊涂窗口综合症几乎是必然要发生的,
1.2.1 接收方治疗方案
接收方治疗方案基本可以归类为两种。
- 第1种是 Clark 方案。Clark 方案可以理解为:立即确认0窗口。当接收缓存比较小时,如果收到对方发送过来的报文,接收方会立即确认,但是 ACK 报文中的 Window 字段会等于0。等到自己的缓存比较大(比如大小达到 MSS(maximum segment size),或者达到最大缓存空间的的一半时),再主动 ACK 接收方,告知自己Window。
- 第2种方案是延迟确认(Delay ACK)。当接收缓存比较小时,如果收到对方发送过来的报文,接收方会稍微等待一段时间再确认——其基本思路是:在等待的这段时间内,自己会处理更多的报文,从而腾挪出更多的接收缓存,此时再发送 ACK 报文,就可以通告一个比较大的窗口。
当然,延迟确认的“延迟”时间不能过长,否则会让发送方以为发送超时,进而引发重新发送,这就弄巧成拙了。一般来说这个延迟时间是200 ms 或者500 ms(具体多少,是一个经验值),超过这个时间,接收方必须要给对方回应1个 ACK 报文。
1.2.2 发送方治疗方案
也有两种。第一种叫Nagle 算法,第二种是CORK 算法。
Nagle 算法
Nagle 算法可以用一句话总结:大包随便发(大于等于 MSS),小包发1个要等到确才能发下1个。Nagle 算法的本质仍然是期望能“休息”或者等待一段时间,以使自己能将多个小包“攒”成1个大包发送。这个等待时间就是接收方 ACK 的时间。可以看到,如果对方 ACK 速度比较快,Nagle 算法仍然无法避免小包发送。
需要说明的是,对于 FIN 报文,Nagle 算法是不等待的,立即发送,因为它并不想延迟 FIN 报文的发送。这句话的潜台词是:Nagle 算法对于其他报文是有可能造成延时的。另外,那些时延敏感的程序,如果遇到“发送方 Nagle 算法 + 接收方延迟确认”,那是雪上加霜:发送方在等接收方的确认,接收方又在等自己慢慢长大。所以,TCP 提供了一个选项“TCP_NODELAY”来禁用 Nagle 算法。
标签:窗口,WND,报文,TCP,发送,传输层,滑动,接收 来源: https://blog.csdn.net/weixin_45537413/article/details/113946807