C/C++教程

TCP传输协议---滑动窗口、流量控制

本文主要是介绍TCP传输协议---滑动窗口、流量控制,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

滑动窗口、流量控制

  • 一. 滑动窗口协议
    • ①. 滑动窗口介绍
    • ②. 传统方式传输
    • ③. 滑动窗口传输方式
    • ④. 窗口大小
    • ⑤. 发送方的滑动窗口
    • ⑥. 数据全部发送完毕
    • ⑦. ACK确认应答
    • ⑧. 接收方的滑动窗口
    • ⑨. 接收窗口和发送窗口的大小
  • 二. 流量控制
    • ①. 流量控制
    • ②. 操作系统缓冲区与滑动窗口
    • ③. 收缩窗口、丢包的现象
    • ④. 窗口关闭
    • ⑤. TCP 解决窗口关闭时,潜在的死锁现象
    • ⑥. 糊涂窗口综合症
    • ⑦. 解决糊涂窗口综合症方法

一. 滑动窗口协议

①. 滑动窗口介绍

  1. 窗口对应的是一段可以被发送者发送的字节序列,其连续的范围称之为窗口;

  2. 滑动则是指这段“允许发送的范围”是可以随着发送的过程而变化的,方式就是按顺序滑动。

②. 传统方式传输

  • TCP 是每发送一个数据,都要进行一次确认应答。当上一个数据包收到了应答了, 再发送下一个。

在这里插入图片描述

传输方式有一个缺点:数据包的往返时间越长,通信的效率就越低。

③. 滑动窗口传输方式

  • TCP 引入了窗口,就可以指定窗口大小,窗口大小就是指无需等待确认应答,而可以继续发送数据的最大值。
  • 即使在往返时间较长的情况下,它也不会降低网络通信的效率。

在这里插入图片描述

用滑动窗口方式并行处理

  • 图中的 ACK 600 确认应答报文丢失,也没关系,因为可以通过下一个确认应答进行确认,只要发送方收到了 ACK 700 确认应答,就意味着 700 之前的所有数据「接收方」都收到了。这个模式就叫累计确认或者累计应答。

④. 窗口大小

TCP 头里有一个字段叫 Window,也就是窗口大小。

  • 这个字段是接收端告诉发送端自己还有多少缓冲区可以接收数据。于是发送端就可以根据这个接收端的处理能力来发送数据,而不会导致接收端处理不过来。

  • 所以,通常窗口的大小是由接收方的窗口大小来决定的。

  • 发送方发送的数据大小不能超过接收方的窗口大小,否则接收方就无法正常接收到数据。

⑤. 发送方的滑动窗口

  • 发送方缓存的数据,根据处理的情况分成四个部分,其中深蓝色方框是发送窗口,紫色方框是可用窗口:

在这里插入图片描述

  • #1 是已发送并收到 ACK确认的数据:1~31 字节
  • #2 是已发送但未收到 ACK确认的数据:32~45 字节
  • #3 是未发送但总大小在接收方处理范围内(接收方还有空间):46~51字节
  • #4 是未发送但总大小超过接收方处理范围(接收方没有空间):52字节以后

⑥. 数据全部发送完毕

  • 当发送方把数据「全部」都一下发送出去后,可用窗口的大小就为 0 了,表明可用窗口耗尽,在没收到 ACK 确认之前是无法继续发送数据了

在这里插入图片描述

⑦. ACK确认应答

  • 当收到之前发送的数据 32~36 字节的 ACK 确认应答后,如果发送窗口的大小没有变化,则滑动窗口往右边移动 5 个字节,因为有 5 个字节的数据被应答确认,接下来 52~56 字节又变成了可用窗口,那么后续也就可以发送 52~56 这 5 个字节的数据了。

SND.WND、SND.UN、SND.NXT

  1. SND.WND:表示发送窗口的大小(大小是由接收方指定的);

  2. SND.UNA:是一个绝对指针,它指向的是已发送但未收到确认的第一个字节的序列号,也就是 #2 的第一个字节。

  3. SND.NXT:也是一个绝对指针,它指向未发送但可发送范围的第一个字节的序列号,也就是 #3 的第一个字节。

  4. 指向 #4 的第一个字节是个相对指针,它需要 SND.UNA 指针加上 SND.WND 大小的偏移量,就可以指向 #4 的第一个字节了。

可用窗口大 = SND.WND -(SND.NXT - SND.UNA)

⑧. 接收方的滑动窗口

接收窗口处理的情况划分成三个部分:

  • #1 + #2 是已成功接收并确认的数据(等待应用进程读取);
  • #3 是未收到数据但可以接收的数据;
  • #4 未收到数据并不可以接收的数据;

在这里插入图片描述

其中三个接收部分,使用两个指针进行划分:

  1. RCV.WND:表示接收窗口的大小,它会通告给发送方。
  2. RCV.NXT:是一个指针,它指向期望从发送方发送来的下一个数据字节的序列号,也就是 #3 的第一个字节。
  3. 指向 #4 的第一个字节是个相对指针,它需要 RCV.NXT 指针加上 RCV.WND 大小的偏移量,就可以指向 #4 的第一个字节了。

⑨. 接收窗口和发送窗口的大小

  • 并不是完全相等,接收窗口的大小是约等于发送窗口的大小的。

  • 因为滑动窗口并不是一成不变的。比如,当接收方的应用进程读取数据的速度非常快的话,这样的话接收窗口可以很快的就空缺出来。那么新的接收窗口大小,是通过 TCP 报文中的 Windows 字段来告诉发送方。那么这个传输过程是存在时延的,所以接收窗口和发送窗口是约等于的关系。

二. 流量控制

  • TCP 利用滑动窗口实现流量控制。流量控制是为了控制发送方发送速率,保证接收方来得及接收。 接收方发送的确认报文中的窗口字段可以用来控制发送方窗口大小,从而影响发送方的发送速率。将窗口字段设置为 0,则发送方不能发送数据。

①. 流量控制

在这里插入图片描述

流量控制

  1. 客户端向服务端发送请求数据报文。这里是把服务端作为发送方。
  2. 服务端收到请求报文后,发送确认报文和 80 字节的数据,于是可用窗口 Usable 减少为 120 字节,同时 SND.NXT 指针也向右偏移 80 字节后,指向 321,这意味着下次发送数据的时候,序列号是 321。
  3. 客户端收到 80 字节数据后,于是接收窗口往右移动 80 字节,RCV.NXT 也就指向 321,这意味着客户端期望的下一个报文的序列号是 321,接着发送确认报文给服务端。
  4. 服务端再次发送了 120 字节数据,于是可用窗口耗尽为 0,服务端无法再继续发送数据。
  5. 客户端收到 120 字节的数据后,于是接收窗口往右移动 120 字节,RCV.NXT 也就指向 441,接着发送确认报文给服务端。
  6. 服务端收到对 80 字节数据的确认报文后,SND.UNA 指针往右偏移后指向 321,于是可用窗口 Usable 增大到 80。
  7. 服务端收到对 120 字节数据的确认报文后,SND.UNA 指针往右偏移后指向 441,于是可用窗口 Usable 增大到 200。
  8. 服务端可以继续发送了,于是发送了 160 字节的数据后,SND.NXT 指向 601,于是可用窗口 Usable 减少到 40。
  9. 客户端收到 160 字节后,接收窗口往右移动了 160 字节,RCV.NXT 也就是指向了 601,接着发送确认报文给服务端。
  10. 服务端收到对 160 字节数据的确认报文后,发送窗口往右移动了 160 字节,于是 SND.UNA 指针偏移了 160 后指向 601,可用窗口 Usable 也就增大至了 200。

②. 操作系统缓冲区与滑动窗口

  • 客户端作为发送方,服务端作为接收方,发送窗口和接收窗口初始大小为 360;
  • 服务端非常的繁忙,当收到客户端的数据时,应用层不能及时读取数据。

在这里插入图片描述

  1. 客户端发送 140 字节数据后,可用窗口变为 220 (360 - 140)。
  2. 服务端收到 140 字节数据,但是服务端非常繁忙,应用进程只读取了 40 个字节,还有 100 字节占用着缓冲区,于是接收窗口收缩到了 260 (360 - 100),最后发送确认信息时,将窗口大小通告给客户端。
  3. 客户端收到确认和窗口通告报文后,发送窗口减少为 260。
  4. 客户端发送 180 字节数据,此时可用窗口减少到 80。
  5. 服务端收到 180 字节数据,但是应用程序没有读取任何数据,这 180 字节直接就留在了缓冲区,于是接收窗口收缩到了 80 (260 - 180),并在发送确认信息时,通过窗口大小给客户端。
  6. 客户端收到确认和窗口通告报文后,发送窗口减少为 80。
  7. 客户端发送 80 字节数据后,可用窗口耗尽。
  8. 服务端收到 80 字节数据,但是应用程序依然没有读取任何数据,这 80 字节留在了缓冲区,于是接收窗口收缩到了 0,并在发送确认信息时,通过窗口大小给客户端。
  9. 客户端收到确认和窗口通告报文后,发送窗口减少为 0。

③. 收缩窗口、丢包的现象

在这里插入图片描述

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

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

  • 为了防止这种情况发生,TCP 规定是不允许同时减少缓存又收缩窗口的,而是采用先收缩窗口,过段时间再减少缓存,这样就可以避免了丢包情况。

④. 窗口关闭

  • 如果窗口大小为 0 时,就会阻止发送方给接收方传递数据,直到窗口变为非 0 为止,这就是窗口关闭。
  • 那么,当发生窗口关闭时,接收方处理完数据后,会向发送方通告一个窗口非 0 的 ACK 报文,如果这个通告窗口的 ACK 报文在网络中丢失了,那麻烦就大了。
    在这里插入图片描述

窗口关闭潜在的危险

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

⑤. TCP 解决窗口关闭时,潜在的死锁现象

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

  • 如果持续计时器超时,就会发送窗口探测 ( Window probe ) 报文,而对方在确认这个探测报文时,给出自己现在的接收窗口大小。

在这里插入图片描述

窗口探测

  • 如果接收窗口仍然为 0,那么收到这个报文的一方就会重新启动持续计时器;

  • 如果接收窗口不是 0,那么死锁的局面就可以被打破了。

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

⑥. 糊涂窗口综合症

  • 如果接收方太忙了,来不及取走接收窗口里的数据,那么就会导致发送方的发送窗口越来越小。

  • 到最后,如果接收方腾出几个字节并告诉发送方现在有几个字节的窗口,而发送方会义无反顾地发送这几个字节,这就是糊涂窗口综合症。

  • 接收方的窗口大小是 360 字节,但接收方由于某些原因陷入困境,假设接收方的应用层读取的能力如下:

  • 接收方每接收 3 个字节,应用程序就只能从缓冲区中读取 1 个字节的数据;(读取三分之一)

  • 在下一个发送方的 TCP 段到达之前,应用程序还从缓冲区中读取了 40 个额外的字节;

在这里插入图片描述

  • 所以,糊涂窗口综合症的现象是可以发生在发送方和接收方:

    • 接收方可以通告一个小的窗口
    • 而发送方可以发送小数据

⑦. 解决糊涂窗口综合症方法

  1. 让接收方不通告小窗口给发送方
  2. 让发送方避免发送小数据

让接收方不通告小窗口

  • 当「窗口大小」小于 min( MSS,缓存空间/2 ) ,也就是小于 MSS 与 1/2 缓存大小中的最小值时,就会向发送方通告窗口为 0,也就阻止了发送方再发数据过来。

  • 等到接收方处理了一些数据后,窗口大小 >= MSS,或者接收方缓存空间有一半可以使用,就可以把窗口打开让发送方发送数据过来。

让发送方避免发送小数据

  • 使用 Nagle 算法,该算法的思路是延时处理,它满足以下两个条件中的一条才可以发送数据:

    • 要等到窗口大小 >= MSS 或是 数据大小 >= MSS
    • 收到之前发送数据的 ack 回包
  • 只要没满足上面条件中的一条,发送方一直在囤积数据,直到满足上面的发送条件。

这篇关于TCP传输协议---滑动窗口、流量控制的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!