转载自:[计算机网络] - TCP 重传、滑动窗口、流量控制、拥塞控制_3000~3500的数据早已被接收了,因为 ack 都到了 4000 了,已经意味着 4000 之前__浮生_的博客-CSDN博客

1. 重传机制

1.1 超时重传

重传机制的其中一个方式,就是在发送数据时,设定一个定时器,当超过指定的时间后,没有收到对方的 ACK 确认应答报文,就会重发该数据,也就是我们常说的超时重传

TCP 会在以下两种情况发生超时重传:

1.1.1 超时时间

我们先来了解一下什么是 RTT (Round-Trip Time 往返时延)

RTT 就是数据从网络一端传送到另一端所需的时间,也就是包的往返时间

超时重传时间是以 RTO (Retransmission Timeout 超时重传时间)表示。

假设在重传的情况下,超时时间 RTO 「较长或较短」时,会发生什么事情呢?

精确的测量超时时间 RTO 的值是非常重要的,这可让我们的重传机制更高效。
根据上述的两种情况,我们可以得知,超时重传时间 RTO 的值应该略大于报文往返 RTT 的值

实际上「报文往返 RTT 的值」是经常变化的,因为我们的网络也是时常变化的。也就因为「报文往返
RTT 的值」 是经常波动变化的,所以「超时重传时间 RTO 的值」应该是一个动态变化的值

如果超时重发的数据,再次超时的时候,又需要重传的时候,TCP 的策略是超时间隔加倍

也就是每当遇到一次超时重传的时候,都会将下一次超时时间间隔设为先前值的两倍。两次超时,就说明网络环境差,不宜频繁反复发送。

超时触发重传存在的问题是,超时周期可能相对较长。那是不是可以有更快的方式呢?

于是就可以用「快速重传」机制来解决超时重发的时间等待。

1.2 快速重传

TCP 还有另外一种快速重传(Fast Retransmit)机制,它不以时间为驱动,而是以数据驱动重传


快速重传的工作方式是当收到三个相同的 ACK 报文时,会在定时器过期之前,重传丢失的报文

快速重传机制只解决了一个问题,就是超时时间的问题,但是它依然面临着另外一个问题。就是重传的时候,是重传之前的一个,还是重传所有的问题。

比如对于上面的例子,是重传 Seq2 呢?还是重传 Seq2、Seq3、Seq4、Seq5 呢?因为发送端并不清楚这连续的三个 Ack 2 是谁传回来的。

根据 TCP 不同的实现,以上两种情况都是有可能的。可见,这是一把双刃剑。

为了解决不知道该重传哪些 TCP 报文,于是就有 SACK 方法。

1.3 SACK 方法

SACK( Selective Acknowledgment 选择性确认)需要在 TCP 头部「选项」字段里加一个 SACK 的东西,它可以将缓存的地图发送给发送方,这样发送方就可以知道哪些数据收到了,哪些数据没收到,知道了这些信息,就可以只重传丢失的数据

如下图,发送方收到了三次同样的 ACK 确认报文,于是就会触发快速重发机制,通过 SACK 信息发
现只有 200~299 这段数据丢失,则重发时,就只选择了这个 TCP 段进行重复。

如果要支持 SACK ,必须双方都要支持。在 Linux 下,可以通过 net.ipv4.tcp_sack 参数打开这个功
能(Linux 2.4 后默认打开)。

1.4 Duplicate SACK

Duplicate SACK 又称 D-SACK ,其主要使用了 SACK 来告诉「发送方」有哪些数据被重复接收了
下面举例两个例子,来说明 D-SACK 的作用。

1.4.1 ACK 丢包

1.4.2 网络延时

可见, D-SACK 有这么几个好处:

  1. 可以让「发送方」知道,是发出去的包丢了,还是接收方回应的 ACK 包丢了;
  2. 可以知道是不是「发送方」的数据包被网络延迟了;
  3. 可以知道网络中是不是把「发送方」的数据包给复制了;

在 Linux 下可以通过 net.ipv4.tcp_dsack 参数开启/关闭这个功能(Linux 2.4 后默认打开)。

2.滑动窗口

2.1 引入窗口概念的原因

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

所以,这样的传输方式有一个缺点:数据包的往返时间越长,通信的效率就越低
为解决这个问题,TCP 引入了窗口这个概念。即使在往返时间较长的情况下,它也不会降低网络通信的效率。
那么有了窗口,就可以指定窗口大小,窗口大小就是指无需等待确认应答,而可以继续发送数据的最大值

窗口的实现实际上是操作系统开辟的一个缓存空间,发送方主机在等到确认应答返回之前,必须在缓冲区中保留已发送的数据。如果按期收到确认应答,此时数据就可以从缓存区清除。
假设窗口大小为 3 个 TCP 段,那么发送方就可以「连续发送」 3 个 TCP 段,并且中途若有 ACK
丢失,可以通过「下一个确认应答进行确认」。如下图:

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

2.2 窗口大小

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

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

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

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

2.3 发送方的滑动窗口

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

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

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

2.3.1 程序如何表示发送方的四个部分

TCP 滑动窗口方案使用三个指针来跟踪在四个传输类别中的每一个类别中的字节。其中两个指针是绝对指针(指特定的序列号),一个是相对指针(需要做偏移)。

那么可用窗口大小的计算就可以是:
可用窗口大 = SND.WND -(SND.NXT - SND.UNA)

2.4 接收方的滑动窗口

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

接收窗口和发送窗口的大小是相等的吗?

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

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

3.流量控制

发送方不能无脑的发数据给接收方,要考虑接收方处理能力。

如果一直无脑的发数据给对方,但对方处理不过来,那么就会导致触发重发机制,从而导致网络流量的无端的浪费。

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

下面举个例子,为了简单起见,假设以下场景:

3.1 操作系统缓冲区与滑动窗口的关系

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

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

考虑以下场景:

当服务端系统资源非常紧张的时候,操作系统可能会直接减少了接收缓冲区大小,这时应用程序又无法及时读取缓存数据,那么这时候就有严重的事情发生了,会出现数据包丢失的现象。

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

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

3.2 窗口关闭

在前面我们都看到了,TCP 通过让接收方指明希望从发送方接收的数据大小(窗口大小)来进行流量控制。

如果窗口大小为 0 时,就会阻止发送方给接收方传递数据,直到窗口变为非 0 为止,这就是窗口关闭

3.2.1 窗口关闭潜在的危险

接收方向发送方通告窗口大小时,是通过 ACK 报文来通告的。

那么,当发生窗口关闭时,接收方处理完数据后,会向发送方通告一个窗口非 0 的 ACK 报文,如果这个通告窗口的 ACK 报文在网络中丢失了,那麻烦就大了。

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

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

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

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

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

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

3.3 糊涂窗口综合症

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

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

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

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

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

3.3.1 让接收方不通告小窗口

接收方通常的策略如下:

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

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

3.3.2 让发送方避免发送小数据

发送方通常的策略:

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

只要没满足上面条件中的一条,发送方一直在囤积数据,直到满足上面的发送条件。

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

可以在 Socket 设置 TCP_NODELAY 选项来关闭这个算法(关闭 Nagle 算法没有全局参数,需要根据每个应用自己的特点来关闭)
setsockopt(sock_fd, IPPROTO_TCP, TCP_NODELAY, (char *)&value, sizeof(int));

4. 拥塞控制

流量控制是避免「发送方」的数据填满「接收方」的缓存,但是并不知道网络中发生了什么。

一般来说,计算机网络都处在一个共享的环境。因此也有可能会因为其他主机之间的通信使得网络拥堵。

在网络出现拥堵时,如果继续发送大量数据包,可能会导致数据包时延、丢失等,这时 TCP 就会重传数据,但是一重传就会导致网络的负担更重,于是会导致更大的延迟以及更多的丢包,这个情况就会进入恶性循环被不断地放大…

所以,TCP 不能忽略网络上发生的事,它被设计成一个无私的协议,当网络发送拥塞时,TCP 会自我牺牲,降低发送的数据量。

于是,就有了拥塞控制,控制的目的就是避免「发送方」的数据填满整个网络

为了在「发送方」调节所要发送数据的量,定义了一个叫做「拥塞窗口」的概念。

4.1 拥塞窗口

拥塞窗口 cwnd是发送方维护的一个 的状态变量,它会根据网络的拥塞程度动态变化的

我们在前面提到过发送窗口 swnd 和接收窗口 rwnd 是约等于的关系,那么由于加入了拥塞窗口的概念后,此时发送窗口的值是swnd = min(cwnd, rwnd),也就是拥塞窗口和接收窗口中的最小值。

拥塞窗口 cwnd 变化的规则:

那么怎么知道当前网络是否出现了拥塞呢?

其实只要「发送方」没有在规定时间内接收到 ACK 应答报文,也就是发生了超时重传,就会认为网络出现了拥塞

4.2 拥塞控制算法

拥塞控制主要是四个算法:

4.2.1 慢启动

TCP 在刚建立连接完成后,首先是有个慢启动的过程,这个慢启动的意思就是一点一点的提高发送数据包的数量,如果一上来就发大量的数据,这不是给网络添堵吗?

慢启动的算法记住一个规则就行:当发送方每收到一个 ACK,拥塞窗口 cwnd 的大小就会加 1

这里假定拥塞窗口 cwnd 和发送窗口 swnd 相等,下面举个例子:

那慢启动涨到什么时候是个头呢?

有一个叫慢启动门限 ssthresh (slow start threshold)状态变量。

  • 当 cwnd < ssthresh 时,使用慢启动算法。
  • 当 cwnd >= ssthresh 时,就会使用「拥塞避免算法」。

4.2.2 拥塞避免算法

前面说道,当拥塞窗口 cwnd 「超过」慢启动门限 ssthresh 就会进入拥塞避免算法。

一般来说 ssthresh 的大小是 65535 字节。

那么进入拥塞避免算法后,它的规则是:每当收到一个 ACK 时,cwnd 增加 1/cwnd

接上前面的慢启动的例子,现假定 ssthresh 为 8

4.2.3 拥塞发生算法

当网络出现拥塞,也就是会发生数据包重传,重传机制主要有两种:

这两种使用的拥塞发送算法是不同的。

4.2.3.1 发生超时重传的拥塞发生算法

当发生了「超时重传」,则就会使用拥塞发生算法。

这个时候,sshresh 和 cwnd 的值会发生变化:

  • ssthresh 设为 cwnd/2
  • cwnd 重置为 1

    接着,就重新开始慢启动,慢启动是会突然减少数据流的。这真是一旦「超时重传」,马上回到解放前。但是这种方式太激进了,反应也很强烈,会造成网络卡顿。

4.2.3.2 发生快速重传的拥塞发生算法

还有更好的方式,前面我们讲过「快速重传算法」。当接收方发现丢了一个中间包的时候,发送三次前一个包的 ACK,于是发送端就会快速地重传,不必等待超时再重传。

TCP 认为这种情况不严重,因为大部分没丢,只丢了一小部分,则 ssthresh 和 cwnd 变化如下:

  • cwnd = cwnd/2 ,也就是设置为原来的一半;
  • ssthresh = cwnd;
  • 进入快速恢复算法

4.2.4 快速恢复算法

快速重传和快速恢复算法一般同时使用,快速恢复算法是认为,你还能收到 3 个重复 ACK 说明网络也不那么糟糕,所以没有必要像 RTO 超时那么强烈。

正如前面所说,进入快速恢复之前,cwnd 和 ssthresh 已被更新了:

然后,进入快速恢复算法如下:


也就是没有像「超时重传」一夜回到解放前,而是还在比较高的值,后续呈线性增长。

计算机网络TCP拥塞控制窗口大小变化、重传、滑动窗口、流量控制等相关推荐

  1. TCP连续ARQ协议和滑动窗口协议

    TCP协议通过使用连续ARQ协议和滑动窗口协议,来保证数据传输的正确性,从而提供可靠的传输. 一.ARQ协议 ARQ协议,即自动重传请求(Automatic Repeat-reQuest),是OSI模 ...

  2. 计算机网络-TCP拥塞控制

    目录 1 拥塞概念 2 拥塞控制的一般原理 3 拥塞控制的方法 3.1 慢开始 (Slow start) 3.2 拥塞避免算法 3.3 快重传算法 3.4 快恢复算法 3.5 加法增大,乘法减小 (A ...

  3. tcp拥塞控制编程实验c语言代码,C语言 计算机网络TCP拥塞控制模拟程序

    帮助你更好地认识TCP拥塞控制的机制 #include "stdio.h" #include "stdlib.h" void show() { //system ...

  4. tcp checksum incorrect_TCP 协议:滑动窗口

    发送窗口/可用窗口: 第3部分是表示对方的接收窗口大小:第4部分是应用程序已调用write方法明确告知需要发送的 字节大小,但超过了接收方的可处理范围. 其他第3部分大小称为可用窗口,第2部分+第3部 ...

  5. 网络 TCP三次握手及滑动窗口

    三次握手 客户端向服务器发出触发请求syn=1:因为这时还没有得到服务器的回应,所以ack=0 服务器接收到客户端的触发请求,回复ack=1,表示已经接收到客户端的请求:同时服务器也向客户端发出触发请 ...

  6. 计算机网络传输层(tcp滑动窗口与流量控制、拥塞控制)

    ④ TCP的滑动窗口 TCP的滑动窗口是以字节为单位的,是缓存的一部分,用来暂时存放字节流. 为了便于理解,我们只考虑A向B发送数据,B给出确认的场景.即A有发送窗口,B有接收窗口. 当发送方收到接收 ...

  7. TCP滑动窗口和拥塞控制机制

    滑动窗口协议 滑动窗口协议(Sliding Window Protocol)属于TCP协议的一种应用,用于网络数据传输时的流量控制,以避免拥塞的发生.该协议允许发送方在停止并等待确认前发送多个数据分组 ...

  8. 网络(6)-TCP/IP对拥塞控制、滑动窗口如何实现可靠性?

    一.拥塞控制 在拥塞控制上,采用广受好评的TCP拥塞控制算法(也称AIMD算法).该算法主要包括四个主要部分: 1.慢启动 每当建立一个TCP连接时或一个TCP连接发生超时重传后,该连接便进入慢启动阶 ...

  9. TCP/IP(十一)TCP滑动窗口和拥塞控制

    目前建立在TCP协议上的网络协议特别多,有telnet,ssh,有ftp,有http等等.这些协议又可以根据数据吞吐量来大致分成两大类:(1)交互数据类型,例如telnet,ssh,这种类型的协议在大 ...

最新文章

  1. 简述PHP中有哪些运算符,PHP运算符简述
  2. JavaScript开发区块链只需200行代码
  3. java虚拟机的生命周期_深入理解Java虚拟机——JVM的生命周期
  4. svn强制要求提交注释
  5. python 获取当前文件夹下所有文件名
  6. pip install 报错UnicodeDecodeError: 'ascii' codec can't decode byte 0xb5 in
  7. 生产环境频繁内存溢出,原来就是因为这个“String类”
  8. 第二次课动手动脑的问题以及课后实验性的问题
  9. set 存放类或结构体的打印
  10. springboot+vue公众号页面授权获得微信openId
  11. box-shadow兼容IE8浏览器写法
  12. 解决报错:info There appears to be trouble with your network connection. Retrying...
  13. 链路追踪Zipkin
  14. Ubuntu 20.04 安装 Seismic Unix
  15. java 实现EME2000(国家大地坐标系)转ECEF坐标系(地心地固坐标系)
  16. poi实现word文档转pdf格式
  17. 名编辑电子杂志大师教程 | 如何删除电子画册中不要的页面?
  18. 会议室应用中的“三块屏”
  19. 《无极限之危情速递》观后感
  20. Canonical Juju 使用笔记

热门文章

  1. 密度聚类:OPTICS算法简单易懂版
  2. Ubuntu 设置多用户smba共享服务
  3. 超有用的前端配色网站
  4. 想做数码管显示,单片机IO口资源不够?看看WTV890语音芯片能做些啥
  5. 抢票软件依旧跑得欢 记者25分钟抢两张热门票
  6. 引领西装潮流文化的报喜鸟何以构建大国品牌
  7. linux编辑conf,Linux:我如何编辑resolv.conf
  8. 最全Pycharm教程(1)——定制外观
  9. Android Studio——Spinner 修改字体颜色和字体大小
  10. JAVA毕业设计飞机航班信息查询系统演示视频2021计算机源码+lw文档+系统+调试部署+数据库