【计算机网络】TCP面试知识(4)流量控制

发送⽅不能⽆脑的发数据给接收⽅,要考虑接收⽅处理能⼒。

如果⼀直⽆脑的发数据给对⽅,但对⽅处理不过来,那么就会导致触发᯿发机制,从⽽导致⽹络流量的⽆端的浪费。

为了解决这种现象发⽣,TCP 提供⼀种机制可以让「发送⽅」根据「接收⽅」的实际接收能⼒控制发送的数据量,这就是所谓的流量控制。

举例说明,首先假设:

  • 客户端是接收⽅,服务端是发送⽅
  • 假设接收窗⼝和发送窗⼝相同,都为 200
  • 假设两个设备在整个传输过程中都保持相同的窗⼝⼤⼩,不受外界影响

在这里插入图片描述

在这里插入图片描述

根据上图的流量控制,说明下每个过程:

  • 客户端向服务端发送请求数据报⽂。这⾥要说明下,本次例⼦是把服务端作为发送⽅,所以没有画出服务端的接收窗⼝。
  • 服务端收到请求报⽂后,发送确认报⽂和 80 字节的数据,于是可⽤窗⼝ Usable 减少为 120 字节,同时SND.NXT 指针也向右偏移 80 字节后,指向 321,这意味着下次发送数据的时候,序列号是 321。
  • 客户端收到 80 字节数据后,于是接收窗⼝往右移动 80 字节, RCV.NXT 也就指向 321,这意味着客户端期望的下⼀个报⽂的序列号是 321,接着发送确认报⽂给服务端。
  • 服务端再次发送了 120 字节数据,于是可⽤窗⼝耗尽为 0,服务端⽆法再继续发送数据。
  • 客户端收到 120 字节的数据后,于是接收窗⼝往右移动 120 字节, RCV.NXT 也就指向 441,接着发送确认报⽂给服务端。
  • 服务端收到对 80 字节数据的确认报⽂后, SND.UNA 指针往右偏移后指向 321,于是可⽤窗⼝ Usable增⼤到 80。
  • 服务端收到对 120 字节数据的确认报⽂后, SND.UNA 指针往右偏移后指向 441,于是可⽤窗⼝ Usable增⼤到 200。
  • 服务端可以继续发送了,于是发送了 160 字节的数据后, SND.NXT 指向 601,于是可⽤窗⼝ Usable 减少到 40。
  • 客户端收到 160 字节后,接收窗⼝往右移动了 160 字节, RCV.NXT 也就是指向了 601,接着发送确认报⽂给服务端。
  • 服务端收到对 160 字节数据的确认报⽂后,发送窗⼝往右移动了 160 字节,于是 SND.UNA 指针偏移了160 后指向 601,可⽤窗⼝ Usable 也就增⼤⾄了 200。

一、操作系统缓冲区与滑动窗⼝的关系

前⾯的流量控制例⼦,我们假定了发送窗⼝和接收窗⼝是不变的,但是实际上,发送窗⼝和接收窗⼝中所存放的字节数,都是放在操作系统内存缓冲区中的,⽽操作系统的缓冲区,会被操作系统调整。

当应⽤进程没办法及时读取缓冲区的内容时,也会对我们的缓冲区造成影响。

1、那操作系统的缓冲区,是如何影响发送窗⼝和接收窗⼝的呢?

(1)例1

首先观察:当应⽤程序没有及时读取缓存时,发送窗⼝和接收窗⼝的变化。

考虑以下场景:

  • 客户端作为发送⽅,服务端作为接收⽅,发送窗⼝和接收窗⼝初始⼤⼩为 360 ;
  • 服务端⾮常的繁忙,当收到客户端的数据时,应⽤层不能及时读取数据。

在这里插入图片描述

在这里插入图片描述

根据上图的流量控制,说明下每个过程:

  • 客户端发送 140 字节数据后,可⽤窗⼝变为 220 (360 - 140)。
  • 服务端收到 140 字节数据,但是服务端⾮常繁忙,应⽤进程只读取了 40 个字节,还有 100 字节占⽤着缓冲区,于是接收窗⼝收缩到了 260 (360 - 100),最后发送确认信息时,将窗⼝⼤⼩通告给客户端。
  • 客户端收到确认和窗⼝通告报⽂后,发送窗⼝减少为 260。
  • 客户端发送 180 字节数据,此时可⽤窗⼝减少到 80。
  • 服务端收到 180 字节数据,但是应⽤程序没有读取任何数据,这 180 字节直接就留在了缓冲区,于是接收窗⼝收缩到了80(260 - 180),并在发送确认信息时,通过窗⼝⼤⼩给客户端。
  • 客户端收到确认和窗⼝通告报⽂后,发送窗⼝减少为 80。
  • 客户端发送 80 字节数据后,可⽤窗⼝耗尽。
  • 服务端收到 80 字节数据,但是应⽤程序依然没有读取任何数据,这 80 字节留在了缓冲区,于是接收窗⼝收缩到了 0,并在发送确认信息时,通过窗⼝⼤⼩给客户端。
  • 客户端收到确认和窗⼝通告报⽂后,发送窗⼝减少为 0。

可⻅最后窗⼝都收缩为 0 了,也就是发⽣了窗⼝关闭。当发送⽅可⽤窗⼝变为 0 时,发送⽅实际上会定时发送窗⼝探测报⽂,以便知道接收⽅的窗⼝是否发⽣了改变。

(2)例2

当服务端系统资源⾮常紧张的时候,操⼼系统可能会直接减少了接收缓冲区⼤⼩,这时应⽤程序⼜⽆法及时读取缓存数据,那么这时候就有严᯿的事情发⽣了,会出现数据包丢失的现象。

在这里插入图片描述

说明下每个过程:

  • 客户端发送 140 字节的数据,于是可⽤窗⼝减少到了 220。
  • 服务端因为现在⾮常的繁忙,操作系统于是就把接收缓存减少了 120 字节,当收到 140 字节数据后,⼜因为应⽤程序没有读取任何数据,所以 140 字节留在了缓冲区中,于是接收窗⼝⼤⼩从 360 收缩成了 100,最后发送确认信息时,通告窗⼝大小给对⽅。
  • 此时客户端因为还没有收到服务端的通告窗⼝报⽂,所以不知道此时接收窗⼝收缩成了 100,客户端只会看⾃⼰的可⽤窗⼝还有 220,所以客户端就发送了 180 字节数据,于是可⽤窗⼝减少到 40。
  1. 服务端收到了 180 字节数据时,发现数据⼤⼩超过了接收窗⼝的大小,于是就把数据包丢失了。
  • 客户端收到第 2 步时,服务端发送的确认报⽂和通告窗⼝报⽂,尝试减少发送窗⼝到 100,把窗⼝的右端向左收缩了 80,此时可⽤窗⼝的⼤⼩就会出现诡异的负值。

所以,如果发⽣了先减少缓存,再收缩窗⼝,就会出现丢包的现象。

【解决方案】

为了防⽌这种情况发⽣,TCP 规定是不允许同时减少缓存⼜收缩窗⼝的,⽽是采⽤先收缩窗⼝,过段时间再减少缓存,这样就可以避免了丢包情况。

二、窗口关闭

TCP 通过让接收⽅指明希望从发送⽅接收的数据⼤⼩(窗⼝⼤⼩)来进⾏流量控制。

如果窗⼝⼤⼩为 0 时,就会阻⽌发送⽅给接收⽅传递数据,直到窗⼝变为⾮ 0 为⽌,这就是窗⼝关闭。

1、窗⼝关闭潜在的危险

接收⽅向发送⽅通告窗⼝⼤⼩时,是通过 ACK 报⽂来通告的。

那么,当发⽣窗⼝关闭时,接收⽅处理完数据后,会向发送⽅通告⼀个窗⼝⾮ 0 的 ACK 报⽂,如果这个通告窗⼝的 ACK 报⽂在⽹络中丢失了,那麻烦就⼤了。

在这里插入图片描述

这会导致发送⽅⼀直等待接收⽅的⾮ 0 窗⼝通知,接收⽅也⼀直等待发送⽅的数据,如不采取措施,这种相互等待的过程,会造成了死锁的现象。

【TCP 是如何解决窗⼝关闭时,潜在的死锁现象呢?】

为了解决这个问题,TCP 为每个连接设有⼀个持续定时器,只要 TCP 连接⼀⽅收到对⽅的零窗⼝通知,就启动持续计时器。

如果持续计时器超时,就会发送窗⼝探测 ( Window probe ) 报⽂,⽽对⽅在确认这个探测报⽂时,给出⾃⼰现在的接收窗⼝⼤⼩。

在这里插入图片描述

  • 如果接收窗⼝仍然为 0,那么收到这个报⽂的⼀⽅就会重新启动持续计时器;
  • 如果接收窗⼝不是 0,那么死锁的局⾯就可以被打破了。

窗⼝探测的次数⼀般为 3 次,每次⼤约 30-60 秒(不同的实现可能会不⼀样)。如果 3 次过后接收窗⼝还是 0 的话,有的 TCP 实现就会发 RST 报⽂来中断连接。

三、糊涂窗⼝综合症

如果接收⽅太忙了,来不及取⾛接收窗⼝⾥的数据,那么就会导致发送⽅的发送窗⼝越来越⼩。

到最后,如果接收⽅腾出⼏个字节并告诉发送⽅现在有⼏个字节的窗⼝,⽽发送⽅会义⽆反顾地发送这⼏个字节,这就是糊涂窗⼝综合症。

要知道,我们的 TCP + IP 头有 40 个字节,为了传输那⼏个字节的数据,要达上这么⼤的开销,这太不经济了。

就好像⼀个可以承载 50 ⼈的⼤巴⻋,每次来了⼀两个⼈,就直接发⻋。除⾮家⾥有矿的⼤巴司机,才敢这样玩,不然迟早破产。要解决这个问题也不难,⼤巴司机等乘客数ᰁ超过了 25 个,才认定可以发⻋。

【举例说明】,考虑以下场景:
接收⽅的窗⼝⼤⼩是 360 字节,但接收⽅由于某些原因陷⼊困境,假设接收⽅的应⽤层读取的能⼒如下:

  • 接收⽅每接收 3 个字节,应⽤程序就只能从缓冲区中读取 1 个字节的数据;
  • 在下⼀个发送⽅的 TCP 段到达之前,应⽤程序还从缓冲区中读取了 40 个额外的字节;

在这里插入图片描述

每个过程的窗⼝⼤⼩的变化,在图中都描述的很清楚了,可以发现窗⼝不断减少了,并且发送的数据都是⽐较⼩的了。

所以,糊涂窗⼝综合症的现象是可以发⽣在发送⽅和接收⽅:

  • 接收⽅可以通告⼀个⼩的窗⼝
  • 发送⽅可以发送⼩数据

于是,要解决糊涂窗⼝综合症,就解决上⾯两个问题就可以了。

  • 让接收⽅不通告⼩窗⼝给发送⽅
  • 让发送⽅避免发送⼩数据

1、【解决方案】怎么让接收⽅不通告⼩窗⼝呢?

接收⽅通常的策略如下:
当「窗⼝⼤⼩」⼩于 min( MSS,缓存空间/2 ) ,也就是⼩于 MSS 与 1/2 缓存⼤⼩中的最⼩值时,就会向发送⽅通告窗⼝为 0 ,也就阻⽌了发送⽅再发数据过来。

等到接收⽅处理了⼀些数据后,窗⼝⼤⼩ >= MSS,或者接收⽅缓存空间有⼀半可以使⽤,就可以把窗⼝打开让发送⽅发送数据过来。

2、【解决方案】怎么让发送⽅避免发送⼩数据呢?

发送⽅通常的策略:
使⽤ Nagle 算法,该算法的思路是延时处理,它满⾜以下两个条件中的⼀条才可以发送数据:

  • 要等到窗⼝⼤⼩ >= MSS 或是 数据⼤⼩ >= MSS
  • 收到之前发送数据的 ack 回包

只要没满⾜上⾯条件中的⼀条,发送⽅⼀直在囤积数据,直到满⾜上⾯的发送条件。

另外,Nagle 算法默认是打开的,如果对于⼀些需要⼩数据包交互的场景的程序,⽐如,telnet 或 ssh 这样的交互性⽐较强的程序,则需要关闭 Nagle 算法。

可以在 Socket 设置 TCP_NODELAY 选项来关闭这个算法(关闭 Nagle 算法没有全局参数,需要根据每个应⽤⾃⼰的特点来关闭)

setsockopt(sock_fd, IPPROTO_TCP, TCP_NODELAY, (char *)&value, sizeof(int));

整理自小林coding所著的《图解网络》,仅做学习用,侵删

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值