传输层协议TCP—滑动窗口(9)

1、Zero Window(0窗口)

        Zero Window(0窗口)指的是 TCP 发送方的滑动窗口大小为0,其本质上是因为 TCP 接收方的接收缓存已经满了,没有空间再接收数据。当处于 Zero Window 时,TCP 发送方是不能向对方发送数据的——即使发送也会被对方给怼回来,如图5-107所示:

        

       如上图所示,当A收到B发送过来的ACK报文(滑动窗口为0)后,正常来说,滑动窗口为0,A 不应该再发送数据给 B。假设 A 不遵守这个规则,给 B 发送了数据,此时假设 B 的接收缓存还是满的,没有空间接收数据,那么 B 就会给 A 回以1个 ACK 报文,这个报文表达了两个含义:

  • (1)AKN = 201,跟其所接收的报文的 SEQ 相同,表示其根本就没有接收数据
  • (2)Window = 0,表明了自己的接收缓存为0,不能接收数据

      不过接收缓存为0,只是表明 TCP 不能接收数据,不代表它不能接收报文——只要这个报文的数据长度为0,比如:ACK 报文、FIN 报文、URG = 1 但是数据长度为0 的报文。URG = 1 但是长度为0的报文,通过“5.3.1.4 Urgent(紧急数据)”的介绍,我们也知道,TCP 想发这样的报文就发送吧,表面上很“紧急”的样子,实际上也没什么特别用处。TCP 接收方能接收这样的报文,也就意味着,在 Zero Window 的情形下,TCP 发送方能发送这样的报文。除此之外,TCP 发送方还能发送另外一种包含数据的报文,这些数据是“已发送未确认”的数据,如图5-108所示:

        

       这就引出几个问题:

  • “已发送未确认”的数据,发送过去,会被怼回来吗?会,如果当时接收方的接收缓存依然为0的话。
  • 既然会被怼回来,那为什么还要发送呢?有什么意义呢?另外为什么它可以发送,其他数据却不能发送,这些数据又有什么特别吗?
  • 已发送未确认”的数据可以发送,那么如果这些数据为空呢?那么 TCP 发送方发送什么?永远不发送数据吗?换一种问法,就是:Zero Window,TCP 发送方的滑动窗口何时会变为大于0?

        当然是接收方的接收缓存腾挪出了接收空间(数据提交了给应用层),TCP 发送方的滑动窗口才能大于0,因为此时收到接收方回复的ack报文中WIN窗口大于0。这个机制可以总结为:TCP 的接收换从0变成大于0时,会给对方发送1个 ACK 报文,通知对方自己的接收缓存的大小。这个机制似乎一点毛病都没有,但是ACK 报文并不会收到对方针对该 ACK 报文的 ACK 报文(不然ack一直互相确认就没啥意义了),如图5-110所示:

        

       然而这种方式有可能会造成死锁的问题(由于B接收方发送的ack报文在传输过程中丢弃了)。如下,我们现在来看看发生了什么事情:

  • (1)B 向 A发送了接收缓存由0变成大于0的通知(ACK 报文),但是通知报文丢了,而 B 并不知道,也不会再重发
  • (2)A 没有收到 B 发送的通知报文,它还一直以为滑动窗口为0,所以也就一直不再发送数据。

       为了解决这个问题,从而就出现了Zero Probe 报文(零窗口探测报文)。广义的 Zero Probe 报文有两种:第1种报文是广义 Zero Probe 报文(已发送未确认的报文)、第2种报文是狭义的 Zero Probe 报文(也就是没有待确认数据时的零窗口探测报文)。首先说一下第一种报文。

1.1 广义 Zero Probe 报文

       如果 TCP 发送方还有“已发送未确认”数据,那么此时它就会发送该数据。现在我们知道,TCP 发送此数据的目的:是当作 Zero Probe 来使用的。这样的报文也称为“广义 Zero Probe 报文”。TCP 发送广义 Zero Probe 报文,如果对方恰好腾挪出了接收缓存,那么就回以1个 ACK 报文,告知自己的接收缓存大小。如此一来,打破死锁,TCP 双方又可以愉快地收发报文了。

      如果对方没有收到报文(广义 Zero Probe 报文丢失),或者对方的接收缓存还是为0,将这个报文给怼了回来,或者回怼的报文丢失了,这都没关系,对于 TCP 发送方来说,它认为都是一样的:滑动窗口还是为0,还是需要发送广义 Zero Probe 报文。处理这种情况,TCP 是周期发送该报文,RFC 793 建议的周期是2分钟。

1.2 狭义的 Zero Probe 报文

        如果 TCP 发送方没有“已发送未确认”数据,但是它同时还有数据需要发送。那么它就会发送 Zero Probe 报文。Zero Probe 报文是 ACK 报文,它的数据长度为0,它的 SEQ 等于 SND.NXT - 1。正是由于 SEQ 的取值,使得 Zero Probe 报文虽然也是 ACK 报文,但是它与普通的 ACK 报文不同,如下图5-111所示:

        

       一个正常的报文,其 SEQ 应该等于 SND.NXT,但是 Zero Probe 报文的 SEQ 却等于 SND.NXT - 1 = RCV.NXT - 1,这一点 TCP 接收方是能感知到的。所以 TCP 接收方收到这个报文以后,就会把它当作 Zero Probe 报文,然后回以1个 ACK 报文,以告知对方此时自己的接收窗口大小。如果 Zero Probe 报文丢失了,如果 TCP 接收方回应的 ACK 报文同样告知接收缓存为0,如果 TCP 接收方回应的 ACK 报文丢失了,对于 TCP 发送方来说,都是一样的:它需要继续发送 Zero Probe 报文。

       为此,TCP 启动了一个 Zero Probe Timer(零窗口探测定时器),也叫“坚持定时器”。只要定时器时间到, TCP 发送方就会发送 Zero Probe 报文,直到收到对方的 ACK 报文告知自己其接收缓存大于0。TCP的坚持定时器使用1、2、4、8、16……64秒这样的普通指数退避序列来作为每一次的溢出时间。也就是说,定时器的第1个周期是1秒,第2个周期是2秒,第n个周期是2n-1秒,直到最大值64秒,以后的周期都是64秒。

       总结:以上讲述了两种 Zero Probe 报文,对应了两种场景。对于第3种场景:既没有“已发送待确认”的数据,也没有“需要发送”的数据,TCP 的策略是:不进行零窗口探测(不发送 Zero Probe 报文),当然也不会启动坚持定时器。

Keep Alive(保活)

         Keep Alive Probe(保活探测)报文——说是保活,实际上就是看看对方是不是还活着,它可真的不能保证对方不死。保活探测报文跟 Zero Probe 报文格式是一样的,都是1个 ACK 报文,而且最关键的,它的 SEQ = SND.NXT - 1。可以参考图5-111,对于一个长时间没有数据传输的连接来说,其 RCV.NXT = SND.NXT。所以,对于 TCP Client 来说,它收到这样1个 ACK 报文,同样知道这是1个探测报文。至于这个报文叫 Zero Probe 还是叫 Keep Alive Probe,这都没关系,这只是我们人类给这个报文所取得名字(场景不同,名字不同)而已,对于 TCP 来说都是一样的:收到这个报文,回以1个 ACK

         当TCP Server 收到 TCP Client 的 ACK 以后,它发现对方还活着,那么就不能释放连接。那就继续再等待一段时间,如果这段时间内连接上仍然是空的,仍然是没有数据传输,TCP Server 就继续发送 Keep Alive Probe 报文。RFC 1122 推荐的这个等待时间是不小于2小时。TCP Server 如果没有收到 TCP Client 的 ACK 报文,会有几种情形:(1)网络中断;(2)TCP Client 已经 Crash(崩溃)或者正在重启的过程中;(3)TCP Client 的 ACK 报文在网络上丢失了。无论哪种情形,TCP Server 也不能贸然释放连接,它还得多发几次探测报文,以确保 TCP Client 确实是没有“活着”

        如果 TCP Client Crash 以后又重启了,那么它收到 TCP Server 发送过来的探测报文以后,会回以1个 RST 报文。这样也好,双方重新建立新的连接进行数据传输。需要说明一下,以上关于 TCP Server 主动发起保活探测,是基于实际的应用场景来表达的。理论上讲,无论是 TCP Client 还是 TCP Server,都可以发起保活探测。

       总的来说,RFC 1122 似乎并不太赞同 Keep Alive 机制,所以它建议:

  • (1)Keep Alive 机制应该设置一个开关选项,允许用户关闭该机制,并且这个选项默认必须是关闭的
  • (2)空闲时间也须交于用户设置,并且默认值是不小于2小时。当在这段时间内,连接上没有数据传输,TCP 才能发送 Keep Alive Probe 报文。

      基本上很多 TCP 的具体实现,都支持了 Keep Alive 机制,比如 Linux。在Linux的 /etc/sysctl.conf 文件中有如下配置(值可以修改):

       

Window Scale

        Window Scale 是 TCP 的1个选项(Option),在讲述这个选项之前,我们先看1个问题:

        A 和 B 之间相距1500km(公里),两者之间运行 TCP,那么从 A 传输到 B的最大数据带宽是多少(1秒钟最多能传输多少字节的数据)?要回答这个问题,我们需要明确两点:

  • (1)TCP 发送方的滑动窗口最大是216(64K),因为标识滑动窗口大小的 Window 字段,其占位16个 bits。我们把这个最大值(64K)记为 MWS(max window size);
  • (2)TCP 发送方在连续发送 MWS 个字节后,至少要停顿1次,等待对方的 ACK 报文,才能发送下一批数据;

       如下图5-113所示:

       

      假设:(1)TCP 1个报文可以发送 64K 个字节;(2)网络传输时延只有光速所引发的时延;(3)TCP 接收方处理时间为0,即图5-113中的 Δtr = 0。T0 时刻,A 的滑动窗口为64K。T1 时刻,A 发送了64K 数据给 B,此时它的滑动窗口变为0,不能再发送数据。T2 时刻,A 收到了 B 的 ACK 报文,滑动窗口又变为了64K(然后它又可以继续发送报文)。那么 T2 - T1,就是光速在 A 和 B 之间1个来回所经历的时间,也就是0.01秒。由此,我们可以计算出 A 传输到 B 的最大数据带宽是:64K ÷ 0.01 = 6.4MBps = 64Mbps。

      以上的计算是假设 TCP 1个报文能包含64K字节的数据,稍微有点不真实,如果我们考虑到 MSS(max segment size),TCP 最大数据带宽计算的示意,如图5-114所示:

      

        上图5-114中,为了计算最大带宽,我们假设从 T1 到 T64,TCP 连续发送64个报文(每个报文1K字节),并且假设从 T1 到 T64之间的时间间隔 Δts = 0,当然我们也假设 Δtr = 0。但是从 T64 与 T65 之间,TCP 发送方必须要等待对方的 ACK 报文,才能发送下一批数据。基于图5-114,我们仍然可以计算出 TCP 最大数据带宽是64Mbps。在两端相距1500km的情况下,TCP 理论最大带宽是64Mbps。在两端相距150km的情形下,TCP 理论最大带宽是640Mbps。无论是64M还是640M,在当今这样的年代,这样的理论最大带宽无论如何都可以说是 TCP 的缺点,因为它无法有效利用物理层、数据链路层的技术发展成果。

       增大TCP 发送方的滑动窗口(接收方的接收缓存)就非常有必要。但是从协议的角度,TCP 该如何增大它的 Window 字段呢?TCP(RFC 1323)给出的解决方案是 TCP Window Scale Option,如图5-115所示:

       

       TCP Window Scale Option,Kind = 3,一共占用3个 bytes,其中数据部分占用1个 byte,其含义是 shift.cnt。假设 shift.cnt = 10,那么:(1)假设原来的 Window = W(比如 64K);(2)新的 Window = 2^{shift.cn} * W = 210 * 64K = 64M。也就是说,shift.cn(TCP Window Scale Option)将原来的 Window 放大了 2^{shift.cn}倍。因为shift.cn 最大可以等于255,那么原来的 Window 最大可以放大2^{255}倍,但实际上无法达到这么大,因为不要说计算机没有那么大的内存,仅仅是 TCP 协议本身就限制了 shift.cn 的取值范围。由于TCP 的 Sequence Number 字段,它占位32字节,也就说最大值是232(4G),考虑到一种极端的情况,如下图5-116所示:

        

       上图5-116说的是一个比较极端的情况,A 对 B 来了一个连续点射,1次只发送1个字节,从T1 时刻开始,一直到 T3 时刻,一共发送了232个字节,同时 B 在 T5 时刻回应1个 ACK 报文。但是在 T3 时刻,问题已经出现了:由于 SEQ 只有32 bits,它的最大值是2^{32}-1 ,也就说说在 T3 时刻,SEQ 必须要翻转,其值只能等于1。这时候,我们再看 T4 时刻,B一共收到了2个 SEQ = 1 的报文,它必须要抛弃其中1个问题,这显然是不符合 A 的本意。这个例子虽然极端,但是 TCP 从逻辑自洽的角度考虑,必须限制 Window 的 size 要小于2^{32}-1

       注意一下,TCP Window Scale Option 只能在创建 TCP 连接的 SYN 报文或者 ACK/SYN 中生效,也就是说 TCP Window Scale Option 必须也只能在创建连接时确定。并且说明2点:(1)shift.cnt 最大值是14;(2)前文关于滑动窗口最大数据带宽的估算,从 TCP 协议的角度来讲,也有硬伤,但是不影响基本逻辑的理解。

3.1 为什么shift.cnt 最大值是14?

       Window Scale Option 必须在 SYN 或者 SYN/ACK 报文中协商(通知),其他报文中如果携带有 Window Scale Option,TCP 将会忽略这个选项。需要注意的是,虽然 TCP 通过 SYN、SYN/ACK 报文协商,但是 Window Scale Option 在这两个报文中并不会生效,也就是说这两个报文的 Window Size 并不会放大。这也比较好理解:这个报文仅仅是打个招呼(协商/通知),下一个报文才会生效。

3.1.1 shift.cnt 的大小与 SEQ 的绕接

       在以前的叙述中,我们为了易于表达和易于理解,都特意回避了绕接这个话题,两个序列号 SEQ1、SEQ2,如果 SEQ1 < SEQ2,我们就认为是 SEQ1 先发送、SEQ2 后发送。可是如果叠加上绕接的概念,那么谁先谁后呢?如下图5-117所示:

       

       图5-117中,SEQ1 = 100,SEQ2 = 110,如果不考虑绕接,SEQ1(所对应的报文)显然比 SEQ2(所对应的报文)先发送。但是,如果考虑绕接呢?我们怎么能确认:为什么不是 SEQ2 先发送,然后过了一段时间 SEQ 绕接之后,再发送的 SEQ1?有专门的算法来解决这个问题,这个算法不仅仅是 Linux 的算法,也是 TCP 标准的算法,代码如下:

       

      这段代码非常简单,两个序列号 seq1、seq2 都是无符号整数(__u32),函数名“before”的意思,就是判断谁在前谁在后,而它的判断算法就是:

  • (1)__u32 a = seq1-seq2;
  • (2)将 a 转换为有符号整数:__s32 b = (__s32) a;
  • (3)如果 b < 0,那么 seq1 就在 seq2 之前;否则,seq1 就在 seq2 之后

       具体算法咋就不继续深究了,咋们直接搬结论:如果两者(SEQ1、SEQ2)相差不大(小于最大值的一半),就没有绕接;否则,就是绕接。RFC 7323 是这么表述的:s < t  if 0 < (t - s) < 2^31。这句话的意思是:(1)都当作32位无符号整数进行计算;(2)如果 t - s 大于0并且小于最大值的一半(最大值是232),那么就认为 s 在 t 的前面(没有绕接)。

       TCP 关于 SEQ 是否绕接的算法,都需要发送方的配合。如果发送方不配合,TCP 这个算法就是错的。为了能正确判断 SEQ是否绕接,TCP 只能牺牲发送效率,硬性规定:接收窗口(也就是发送方的滑动窗口)的 size,其最大值只能是 SEQ 空间的一半,即2^{31}。回忆一下“5.2.12.2 通用处理思路”所介绍的 TCP 接收到报文以后,关于报文 SEQ 的判断:

       

       以上对于这两个判断世的详细分析不参见之前的文档。TCP 规定了 Window 的 size,但是对于1个报文来说,它所包含的数据的长度,最大可能是 Window 的 size 的2倍。RFC 7323 的描述是:the sender and receiver  windows can be out of phase by at most the window size如此一来,TCP Window 的 size 的最大值只能是2^{30}。因为 TCP Window 原来的最大值是2^{16}(占有16个 bits),那么也就推导出 Window Scale Option 中的 shift.cnt 的最大值只能是14

       既然 TCP Window 的size 的最大值只能是2^{30},那么 TCP 理论上最大的传输带宽是多少呢?我们可以按照本节一开始所描述的方法计算如下(假设两地相距1500公里):

       TCP 理论最大带宽 = 2^{30} / 0.01 秒 = 100GBps = 1000Gbps = 1Tbps。可以看到,即使在即将到来的5G时代,TCP 也不会过时。

 

 

 

 

 

 

  • 1
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值