本篇讲述TCP建立连接后数据传输过程中的规则和优化方法,包括TCP重传、滑动窗口、流量控制、拥塞控制等内容。

滑动窗口

为什么需要滑动窗口?解决什么问题?

如果采用停等机制(发送一个数据包后必须等待确认才能发送下一个),虽然简单可靠,但网络利用率极低,特别是在高延迟网络中。

为解决这个问题,TCP 引入了窗口这个概念。即使在往返时间较长的情况下,它也不会降低网络通信的效率。

那么有了窗口,就可以指定窗口大小,窗口大小就是指无需等待确认应答,而可以继续发送数据的最大值。

窗口的实现实际上是操作系统开辟的一个缓存空间,发送方主机在等到确认应答返回之前,必须在缓冲区中保留已发送的数据。如果按期收到确认应答,此时数据就可以从缓存区清除。

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

总的来说:

发送方和接收方在内核各自都有一个缓冲区,发送缓冲区和接收缓冲区上都各有一个窗口,发送方的窗口表示可发送的最大数据量,接收方的窗口表示可接收的最大数据量。

  • 发送方有了发送窗口后,那么发送方可以不用等待已发送数据的确认报文,就可以继续发送下一批数据,提高了发送的速率。
  • 接收方有了接收窗口后,可以实现流量控制,把接收方的接收窗口告诉给发送方,让发送方按自己的接收情况来发送数据,避免发送方发送的数据过快,导致接收方处理不过来。

相关考题

题目1:【判断题】在TCP的滑动窗口中,接收方的接收窗口必须大于发送方的发送窗口才能保证数据传输的顺利进行。( )

答案:错误

解析:TCP的数据传输只需要发送方的发送窗口不超过接收方通告的接收窗口即可顺利进行。接收方通过窗口通告机制告知发送方自己的接收能力,发送方会根据这个通告调整自己的发送速率,确保不会发送超过接收方处理能力的数据。实际上,发送窗口通常会小于等于接收窗口,而不是必须要求接收窗口大于发送窗口。

流量控制

一般来说,我们总是希望数据传输得更快一些。但如果发送方把数据发送得过快,接收方就可能来不及接收,这就会造成数据的丢失。

所谓流量控制(flow control),就是让发送方的发送速率不要太快,要让接收方来得及接收。利用滑动窗口机制可以很方便地在TCP连接上实现对发送方的流量控制:

  • TCP接收方利用自己的接收窗口的大小来限制发送方发送窗口的大小。
  • TCP发送方收到接收方的零窗口通知后,应启动持续计时器。持续计时器超时后,向接收方发送零窗口探测报文

那么,为什么需要零窗口通知呢?这就需要聊到窗口关闭带来的潜在危险了:

什么是窗口关闭?如何解决潜在死锁现象?

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

那么,当发生窗口关闭时,接收方处理完数据后,会向发送方通告一个窗口非0的 ACK 报文,如果这个通告窗口的 ACK 报文在网络中丢失了,那就会陷入互相等待的死锁现象。

为了解决这个问题,TCP 为每个连接设有一个持续定时器,只要 TCP 连接一方收到对方的零窗口通知,就启动持续计时器。
如果持续计时器超时,就会发送窗口探测(Window probe)报文,而对方在确认这个探测报文时,给出自己现在的接收窗口大小。

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

窗口探测的次数一般为3次,每次大约 30-60 秒(不同的实现可能会不一样)。

如果3次过后接收窗口还是 0的话,有的 TCP 实现就会发 RST 报文来中断连接。

相关考题

题目1:【单选题】在TCP流量控制中,当接收方的接收缓冲区已满,通告发送方接收窗口为0后,发送方如何处理( )

A. 立即终止连接
B. 持续发送数据,直到接收方处理完缓冲区数据
C. 停止发送数据,定期发送窗口探测报文
D. 继续发送数据,但数据大小限制为1字节

答案:C

解析:当接收方通告接收窗口为0时(零窗口),TCP发送方会停止发送新的数据,以避免接收方缓冲区溢出。但为了避免死锁(如果接收方后续的非零窗口通告丢失),发送方会启动持续定时器(Persist Timer),定期发送窗口探测报文(Window Probe),检查接收方的窗口是否已恢复。这种机制确保了即使在接收方窗口通告丢失的情况下,连接也能最终恢复传输。

题目2:【问答题】分析TCP滑动窗口中的”糊涂窗口综合症”(Silly Window Syndrome)是什么,如何避免?

参考答案

“糊涂窗口综合症”是指接收方的可用窗口很小,而发送方仍然发送小数据包的情况,导致网络传输效率低下的问题。

原因分析:

  1. 接收方处理数据慢,导致接收缓冲区几乎始终处于满状态
  2. 接收方每次只能处理少量数据,于是通告一个很小的窗口
  3. 发送方看到有可用窗口就立即发送,哪怕只能发送很少的数据
  4. 这导致大量小数据包在网络中传输,TCP头部开销与实际数据量比例严重失调

避免措施:

  1. 接收方避免措施(Clark解决方案):
    • 接收方不立即通告小窗口
    • 等待接收缓冲区有足够空间(至少是最大报文段大小MSS的一半或整个接收缓冲区的一半),或者有紧急数据需要发送时才通告非零窗口
  2. 发送方避免措施(Nagle算法):
    • 在任意时刻,未被确认的小数据包不能超过一个
    • 等到有足够数据可以发送最大段大小(MSS)的数据时再发送
    • 或者收到之前发送数据的确认后再发送新的小数据包

通过这些措施,可以有效避免网络中传输过多的小数据包,提高网络效率。

题目3:【问答题】请解释TCP的零窗口通告可能导致的问题,以及”窗口探测”机制如何解决这些问题。

参考答案

当接收方通告零窗口时,发送方将停止发送数据,导致数据传输暂停。如果接收方处理完部分数据后发送的非零窗口通告丢失,而发送方又不知道窗口已经重新打开,将导致连接陷入死锁状态。

窗口探测机制的解决方案:

  1. 持续定时器:当发送方收到零窗口通告时,会启动持续定时器(Persist Timer)。
  2. 探测包发送:定时器到期时,发送方会发送一个1字节的”窗口探测”包。
  3. 指数退避:如果收到的响应仍然是零窗口,持续定时器会以指数退避方式增加间隔(类似于重传超时),减少网络负担。
  4. 窗口恢复检测:窗口探测包的响应会包含接收方当前的窗口大小,如果窗口已经打开,发送方就可以恢复正常传输。

窗口探测机制通过周期性地”轻敲”接收方,确保在接收方缓冲区恢复可用空间后,数据传输能够及时恢复,有效防止了由于网络丢包可能导致的永久性连接死锁。

拥塞控制

为什么需要拥塞控制?

网络资源(如路由器缓冲区、链路带宽)是有限的。如果所有TCP连接都不加控制地发送数据,很容易导致网络中的缓冲区溢出,造成大量丢包,从而引发更多重传,形成恶性循环,最终导致网络性能急剧下降,甚至崩溃。拥塞控制机制能够让TCP连接感知网络状况,并相应调整发送速率,保持网络稳定运行。

TCP流量控制和拥塞控制的区别?

流量控制主要关注端到端通信防止发送方发送数据过快而超过接收方的处理能力,它通过接收窗口(rwnd)实现,由接收方根据自身缓冲区状态进行控制。

而拥塞控制则着眼于整个网络环境,旨在防止过多数据注入网络导致网络拥塞,它通过拥塞窗口(cwnd)实现,由发送方基于网络反馈(如包丢失、延迟增加等拥塞信号)来调整发送速率。这两种控制机制相辅相成,共同保障了TCP通信的稳定性和效率。

为了方便研究,我们通常使用控制变量的方法,即只假定存在流量控制和拥塞控制窗口中的一种情况,而不是两者共同作用。

什么是拥塞窗口?和发送窗口有什么关系呢?

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

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

拥塞窗口 cwnd 变化的规则:

  • 只要网络中没有出现拥塞,cwnd 就会增大;

  • 但网络中出现了拥塞,cwnd 就减少;

拥塞控制是如何实现的?

拥塞控制由一共四种拥塞控制算法组成。话不多说,上图:

1.慢启动

  • 连接初始阶段,拥塞窗口(cwnd)从1个MSS(最大报文段大小)开始
  • 每收到一个ACK,cwnd增加1个MSS——这会导致cwnd呈指数增长
  • 直到达到慢启动阈值(ssthresh)或出现丢包。当 cwnd < ssthresh 时,使用慢启动算法;当 cwnd >= ssthresh 时,就会使用「拥塞避免算法」。一般来说 ,ssthresh 的大小是 65535 字节,这里假设其为8.

2.拥塞避免

  • 当cwnd达到ssthresh后进入此阶段;
  • 每个往返时间(RTT)内,cwnd仅增加1个MSS,使cwnd呈线性增长,更加平缓,直到检测到拥塞发生。

2.5. 超时重传

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

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

  • ssthresh 设为 cwnd/2
  • cwnd 重置为 1 (是恢复为 cwnd 初始化值,linux系统默认为10,这里假定为 1)

接着,就重新开始慢启动。但是这种方式太激进了,反应也很强烈,会造成网络卡顿。

那么有没有更好的方法呢?

3.快重传

  • 接收方接收到失序数据包时立即发送重复ACK
  • 发送方收到3个重复ACK时,TCP 认为这种情况不严重,因为大部分没丢,只丢了一小部分。于是会立即重传丢失的数据包,不等待超时。这减少了等待重传的时间。

ssthreshcwnd 变化如下:

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

4.快恢复

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

延续上方快重传的步骤,在ssthresh = cwnd之后:

  • 拥塞窗口 cwnd = ssthresh + 3
  • 重传丢失的数据包;
  • 如果再收到重复的 ACK,那么 cwnd 增加 1;
  • 如果收到新数据的 ACK,则把 cwnd 设置为第一步中的 ssthresh 的值,回到拥塞避免状态。

只关注数字的变化是比较晦涩难懂的。这里较好的理解是:

1.在快速恢复的过程中,首先 ssthresh=cwnd/2,然后 cwnd=ssthresh+3,表示网络可能出现了阻塞,所以需要减小 cwnd 以避免,加3代表快速重传时已经确认接收到了3个重复的数据包;

2.随后继续重传丢失的数据包,如果再收到重复的 ACK,那么 cwnd 增加 1。加1代表每个收到的重复的 ACK 包,都已经离开了网络。这个过程的目的是尽快将丢失的数据包发给目标。

3.如果收到新数据的 ACK 后,把 cwnd 设置为第一步中的 ssthresh 的值,恢复过程结束。

相关考题

题目1:【单选题】关于TCP拥塞控制的慢启动阶段,下列说法正确的是( )

A. 拥塞窗口初始值为接收窗口的一半
B. 拥塞窗口按照线性规律增长
C. 每个RTT内拥塞窗口增加一个MSS
D. 拥塞窗口按照指数规律增长

答案:D

解析:在TCP拥塞控制的慢启动阶段,拥塞窗口(cwnd)的初始值通常为1个MSS(最大报文段大小),而不是接收窗口的一半,排除A。在慢启动阶段,每收到一个ACK,拥塞窗口增加1个MSS,而不是每个RTT才增加1个MSS,排除C。由于在一个RTT内可能收到多个ACK(与窗口大小相关),因此cwnd增长速度是指数性的(大约每个RTT翻倍),而不是线性的,排除B,选D。到达慢启动阈值后,TCP会进入拥塞避免阶段,此时才变为线性增长。

题目2:【多选题】TCP拥塞控制中的快速恢复算法具有以下哪些特点( )

A. 在发生超时重传时触发
B. 当收到三个重复ACK时触发
C. 将拥塞窗口设置为1个MSS
D. 将拥塞窗口设置为新的慢启动阈值加上收到的重复ACK数量
E. 每收到一个重复ACK,拥塞窗口增加1个MSS

答案:B、D、E

解析:快速恢复算法是在收到三个重复ACK时触发的,而不是在超时重传时,因此A错误,B正确。超时重传被认为是严重拥塞的信号,会导致拥塞窗口重置为1个MSS并进入慢启动阶段;而收到三个重复ACK被认为是轻微拥塞(只是个别包丢失),此时会进入快速恢复阶段,不会将窗口重置为1,因此C错误。在快速恢复阶段,拥塞窗口初始设置为新的慢启动阈值(通常是旧cwnd的一半)加上收到的重复ACK数量(通常是3),因此D正确。在快速恢复阶段,每收到一个额外的重复ACK,可以认为有一个数据包已离开网络,因此拥塞窗口会增加1个MSS,所以E正确。

重传机制

为什么需要重传机制?

在实际网络环境中,数据包可能因为网络拥塞、路由器缓冲区溢出或物理传输错误等原因丢失。如果没有重传机制,一旦数据包丢失,接收方将永远无法获得完整数据,这对要求可靠传输的应用是不可接受的。

下面我们将讲解四种重传机制:超时重传、快速重传、SACK、D-SACK。

1.超时重传

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

如果在超时时间内没有收到确认(ACK),则认为发送的数据包丢失,进行重传。超时时间通常基于RTT(往返时间)动态计算。

那么问题来了,重传的超时时间RTO到底为多少最好呢?很显然,RTO短了会引起不必要的重传,长了会降低网络传输效率。并且每次网络传输的时间肯定是不一样的,不能直接设成固定值。

于是乎,TCP使用自适应算法计算RTO(Retransmission Timeout,重传超时时间):

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

2.快速重传

上面讲到了些许,这里再进行总结:

  • 接收方在收到失序的数据包时,会立即发送对最后一个按序到达的数据包的确认;
  • 如果发送方连续收到三个相同的ACK(称为三重ACK),则无需等待超时,立即重传后续包。

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

举个例子:

假设发送方发了6个数据,编号的顺序是 Seq1~Seq6 ,但是 Seq2、Seq3 都丢失了,那么接收方在收到 Seq4、Seq5、Seq6 时,都是回复 ACK2 给发送方,但是发送方并不清楚这连续的 ACK2 是接收方收到哪个报文而回复的, 那是选择重传 Seq2 一个报文,还是重传 Seq2 之后已发送的所有报文呢(Seq2、Seq3、Seq4、Seq5、Seq6)呢?

  • 如果只选择重传 Seq2 一个报文,那么重传的效率很低。因为对于丢失的 Seq3 报文,还得在后续收到三个重复的 ACK3 才能触发重传。
  • 如果选择重传 Seq2 之后已发送的所有报文,虽然能同时重传已丢失的 Seq2 和 Seq3 报文,但是Seq4、Seq5、Seq6 的报文是已经被接收过了,对于重传 Seq4 ~Seq6 折部分数据相当于做了一次无用功,浪费资源。

可以看到,不管是重传一个报文,还是重传已发送的报文,都存在问题。

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

3.SACK

图解网络这里写的很精炼,也没什么要补充的,直接贴图了:

4.D-SACK

Duplicate SACK 又称 D-SACK,其主要使用了 SACK 来告诉「发送方」有哪些数据被重复接收了。这玩意儿能够很好地解决两个场景下的问题:ACK报文丢失和网络延时。

例1:ACK丢包

  • 「接收方」发给「发送方」的两个 ACK 确认应答都丢失了,所以发送方超时后,重传第一个数据包(3000~3499)
  • 于是「接收方」发现数据是重复收到的,于是回了一个 SACK=30003500,告诉「发送方」30003500 的数据早已被接收了,因为 ACK 都到了 4000 了,已经意味着 4000 之前的所有数据都已收到,所以这个 SACK 就代表着 D-SACK 。
  • 这样「发送方」就知道了,数据没有丢,是「接收方」的 ACK 确认报文丢了。

例2:网络延时

  • 数据包(1000~1499)被网络延迟了,导致「发送方」没有收到 Ack 1500 的确认报文。
  • 而后面报文到达的三个相同的 ACK 确认报文,就触发了快速重传机制,但是在重传后,被延迟的数据包(1000~1499)又到了「接收方」;
  • 所以「接收方」回了一个 SACK=1000~1500。因为 ACK 已经到了 3000,所以这个 SACK是 D-SACK 表示收到了重复的包。
  • 这样发送方就知道快速重传触发的原因不是发出去的包丢了,也不是因为回应的 ACK包丢了,而是因为网络延迟了。

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

1.可以让「发送方」知道,是发出去的包丢了,还是接收方回应的 ACK 包丢了;

2.可以知道是不是「发送方」的数据包被网络延迟了;

3.可以知道网络中是不是把「发送方」的数据包给复制了。

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

相关考题

题目1:【单选题】关于TCP的超时重传机制,下列说法正确的是( )

A. TCP使用固定的超时时间,通常为3秒
B. 超时时间计算只考虑最近一次的RTT
C. 当发生超时重传时,下一次的超时时间会变为原来的2倍
D. 快速重传机制是基于时间的重传方式

答案:C

解析:TCP的重传超时时间(RTO)是基于RTT自适应计算的,不是固定值,排除A。RTO计算会考虑历史RTT和当前RTT的加权平均,不仅仅是最近一次RTT,排除B。当发生超时重传时,TCP会使用指数退避算法,将下一次超时时间设为原来的2倍,以避免网络拥塞情况下频繁重传,C正确。快速重传机制是基于接收到3个重复ACK触发的重传,属于基于确认的重传方式,不是基于时间的,排除D。

题目2:【问答题】简述TCP快速重传和超时重传的区别,并分析它们各自的优缺点。

参考答案

TCP快速重传和超时重传的区别:

  1. 触发机制不同:

    • 超时重传是基于时间的,当发送方在RTO时间内未收到ACK时触发重传
    • 快速重传是基于接收方反馈的,当发送方收到3个重复的ACK时触发重传
  2. 响应速度不同:

    • 快速重传通常比超时重传响应更快,因为它不需要等待超时计时器到期
    • 超时重传需要等待RTO到期,可能会导致较长的停顿
  3. 对网络状况的判断不同:

    • 超时重传认为网络可能处于严重拥塞状态,会导致发送窗口急剧减小(慢启动)
  • 快速重传认为网络轻微拥塞,仅丢失个别包,只会适度减小发送窗口(进入快速恢复)

优缺点分析:

  • 超时重传
    • 优点:可以处理严重的网络问题,包括ACK全部丢失的情况
    • 缺点:响应较慢,可能导致较长的传输暂停;RTO设置不当可能导致不必要的重传或过长的等待
  • 快速重传
    • 优点:响应快速,减少传输暂停时间;对网络利用率影响较小
    • 缺点:需要接收方能够接收到后续的数据包才能触发;如果连续多个包丢失,效率较低

结语

在总结本文之前,这里有一道综合题可供你训练,考察的仍然主要是拥塞控制的内容:

【综合题】已知TCP连接的初始拥塞窗口为2个MSS,慢启动阈值为16个MSS,MSS大小为1KB,接收窗口足够大。在传输过程中,第1个RTT内发送了2个报文段并全部确认;第2个RTT内发送了4个报文段,其中第5号报文段丢失;发送方收到3个重复ACK后立即进入快速恢复算法。请回答:

  1. 在第3个RTT开始时拥塞窗口大小为多少?
  2. 在收到3个重复ACK后,新的拥塞窗口和慢启动阈值各是多少?
  3. 在新数据被确认后,拥塞窗口变为多少?
  4. 绘制整个过程中拥塞窗口的变化图

参考答案

  1. 在第3个RTT开始时,拥塞窗口大小为8个MSS。
    • 初始cwnd = 2MSS
    • 第1个RTT后:收到2个ACK,cwnd = 2 + 2 = 4MSS
    • 第2个RTT后:收到4个ACK,cwnd = 4 + 4 = 8MSS
  2. 收到3个重复ACK后:
    • 新的慢启动阈值 ssthresh = cwnd/2 = 8/2 = 4MSS
    • 新的拥塞窗口 cwnd = ssthresh + 3 = 4 + 3 = 7MSS
  3. 在新数据被确认后(进入拥塞避免阶段):
    • cwnd = ssthresh = 4MSS
  4. 拥塞窗口变化图:
1
2
3
4
5
6
7
8
9
10
11
12
cwnd
|
8| *----
7| *
6| \
5| \
4| *----- *------>
3| /
2|*-*
1|
0+--------------------------> RTT
0 1 2 3 4 5

于是我们再次回到这个熟悉的问题:TCP是如何保证可靠性的?

  1. 校验和:对TCP头部和数据进行校验,检测传输错误
  2. 序列号和确认号:跟踪已发送和已接收的数据字节
  3. 重传机制:丢失或错误的数据包会被重新传输
  4. 滑动窗口:实现流量控制,确保接收方能处理所有数据
  5. 拥塞控制:适应网络状况,避免网络拥塞导致的大量丢包

正是事无巨细的头部设计+精妙的四大优秀机制,使得我们在互联网上能够使用TCP进行正常、安全、可靠地交流。不得不承认,虽然学得很痛苦,但是它确实是无可争议的伟大协议。

“The Internet works because open protocols like TCP/IP allow every network to connect to every other network on a global scale. It’s what made the Internet revolutionary.

——Steve Jobs