TCP 重传、滑动窗口、流量控制与拥塞控制

为了实现可靠性传输,需要考虑很多事情,例如数据的破坏、丢包、重复以及分片顺序混乱等问题。如不能解决这些问题,也就无从谈起可靠传输。那么,TCP 是通过序列号、确认应答、重发控制、连接管理以及窗口控制等机制实现可靠性传输的。今天,将重点介绍 TCP 的重传机制、滑动窗口、流量控制、拥塞控制。对重传与拥塞这段「历史往事」感兴趣的,还可以配合着看看《TCP 的那些事儿》。

TCP 实现可靠传输的方式之一,是通过序列号与确认应答。在 TCP 中,当发送端的数据到达接收主机时,接收端主机会返回一个确认应答消息,表示已收到消息。

但在错综复杂的网络里,数据传输并不总能这么顺利——万一数据在传输过程中丢失了呢?所以 TCP 针对数据包丢失的情况,会用重传机制解决。

接下来说说常见的重传机制:

超时重传

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

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

  • 一是数据包丢了
  • 二是 ACK 确认应答丢了

超时时间应该设置为多少呢?

我们先来了解一下什么是 RTT(Round-Trip Time 往返时延):RTT 就是数据从网络一端传送到另一端所需的时间,也就是包的往返时间。

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

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

RTO 过长和过短各有问题:

  • RTO 较大:丢包后要等很久才会重传,效率低,性能差
  • RTO 较小:包可能还没丢就重发了,不必要的重传会徒增网络流量,加剧网络拥塞

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

至此,可能大家觉得超时重传时间 RTO 的值计算,也不是很复杂嘛。好像就是在发送端发包时记下 t0,然后接收端再把这个 ack 回来时再记一个 t1,于是 RTT = t1 – t0。没那么简单,这只是一个采样,不能代表普遍情况。

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

我们来看看 Linux 是如何计算 RTO 的呢?估计往返时间,通常需要采样以下两个:

  • 采样 RTT 的时间,进行加权平均,算出平滑的 RTT,也就是 SRTT
  • 采样 RTT 与 SRTT 之间的差距,也就是 DevRTT,差距越大说明网络越不平稳

RFC 6298 建议使用以下的公式计算 RTO:

SRTT   = (1 - α) * SRTT + α * RTT
DevRTT = (1 - β) * DevRTT + β * |RTT - SRTT|
RTO    = μ * SRTT + ∂ * DevRTT

其中 SRTT 是平滑后的 RTT,DevRTT 是平滑后的 RTT 与最新 RTT 之间的差距。α = 0.125,β = 0.25,μ = 1,∂ = 4 是 RFC 6298 给出的推荐值,Linux 的实现也采用这组参数。别问怎么来的,问就是大量实验中调出来的。

如果超时重发的数据,再次超时的时候,又需要重传的时候,TCP 的策略是超时间隔加倍。也就是每当遇到一次超时重传的时候,都会将下一次超时时间间隔设为先前值的两倍。两次超时,就说明网络环境差,不宜频繁反复发送。

超时触发重传存在的问题是,超时周期可能相对较长。那是不是可以有更快的方式呢?于是就可以用「快速重传」机制来解决超时重发的时间等待。

快速重传

TCP 还有另外一种快速重传(Fast Retransmit)机制,它不以时间为驱动,而是以数据驱动重传。快速重传机制是如何工作的呢?其实很简单,我们直接看收发双方的过程。假设发送方发出了 1、2、3、4、5 五份数据:

  • 接收方收到 Seq1 后,回复 ACK 2
  • 但 Seq2 丢失了,Seq3 到达时,接收方依然回复 ACK 2
  • 随后 Seq4、Seq5 也陆续到达,接收方还是回复 ACK 2
  • 发送方连续收到三个重复的 ACK 2,就知道是 Seq2 丢失了,于是在定时器过期之前直接重传 Seq2

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

快速重传机制只解决了一个问题,就是超时时间的问题,但是它依然面临着另外一个问题。就是重传的时候,是重传之前的一个,还是重传所有的问题。比如对于上面的例子,是重传 Seq2 呢?还是重传 Seq2、Seq3、Seq4、Seq5 呢?因为发送端并不清楚这连续的三个 Ack 2 是谁传回来的。根据 TCP 不同的实现,以上两种情况都是有可能的。可见,这是一把双刃剑。

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

SACK 方法

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

举个栗子:假设发送方连续发出了 100199、200299、300399、400499 四个报文段,其中 200299 这个报文段在途中丢失,其余的都到达了接收方。接收方收不到 200299,只能反复回复 ACK 200,同时通过 SACK 选项告诉发送方「300399、400499 我已经收到了」。

当发送方收到了三次同样的 ACK 确认报文,就会触发快速重传机制;通过 SACK 信息可以知道只有 200~299 这段数据丢失,于是重发时只重传这个 TCP 段。

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

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

栗子一号:ACK 丢包

「发送方」发出的数据包接收方都正常收到了,也回复了 ACK,但其中某个 ACK 在回程中丢了。发送方迟迟收不到这个 ACK,于是触发重传,把数据又发了一遍。接收方发现这些数据是重复收到的,就通过 SACK 把重复的部分告知发送方。发送方由此可以判断:丢的不是自己发出去的数据包,而是接收方回应的 ACK 包。

栗子二号:网络延时

这一次数据包并没有丢,只是在网络里「玩了一会儿」,迟迟没有到达。发送方等不及,超时重传了数据。之后,先发的包和重传的包先后到达接收方,接收方通过 SACK 把重复收到的部分告诉发送方。发送方由此可以判断:是网络延迟变大了,而不是接收方出了什么问题。

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

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

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

引入窗口概念的原因

我们都知道 TCP 是每发送一个数据,都要进行一次确认应答。当上一个数据包收到了应答了,再发送下一个。这个模式就有点像我和你面对面聊天,你一句我一句。但这种方式的缺点是效率比较低的。如果你说完一句话,我在处理其他事情,没有及时回复你,那你不是要干等着我做完其他事情后,我回复你,你才能说下一句话,很显然这不现实。

所以,这样的传输方式有一个缺点:数据包的往返时间越长,通信的效率就越低。为解决这个问题,TCP 引入了窗口这个概念。即使在往返时间较长的情况下,它也不会降低网络通信的效率。(窗口的动态变化过程,可以配合《窗口滑动与拥塞控制图解》来看。)

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

接着上面的例子:假设发送方依次发出了 100199、200299、300399、400499、500599、600699 这几个报文段,即使中途 ACK 600 这个确认应答报文丢失了,也没关系,因为可以通过下一个确认应答进行确认——只要发送方收到了 ACK 700 确认应答,就意味着 700 之前的所有数据「接收方」都收到了。这个模式就叫累计确认或者累计应答。

窗口大小由哪一方决定?

TCP 头里有一个字段叫 Window,也就是窗口大小。这个字段是接收端告诉发送端自己还有多少缓冲区可以接收数据。于是发送端就可以根据这个接收端的处理能力来发送数据,而不会导致接收端处理不过来。所以,通常窗口的大小是由接收方决定的。发送方发送的数据大小不能超过接收方的窗口大小,否则接收方就无法正常接收到数据。

发送方的滑动窗口

我们先来看看发送方的窗口。发送方缓存里的数据,根据处理的情况分成四个部分:

  • #1 已发送并已确认的数据
  • #2 已发送但未确认的数据
  • #3 未发送但可以发送的数据
  • #4 未发送且不可发送的数据

其中 #2 + #3 的范围就是发送窗口,#3 则是「可用窗口」。

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

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

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

  • SND.WND:发送窗口的大小(相对指针,需要加上 SND.UNA 的偏移)
  • SND.UNA:绝对指针,指向已发送但未被确认的第一个字节的序列号
  • SND.NXT:绝对指针,指向未发送但可发送的第一个字节的序列号

那么可用窗口大小的计算就可以是:

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

接收方的滑动窗口

接下来我们看看接收方的窗口,接收窗口相对简单一些,根据处理的情况划分成三个部分:

  • 已接收并已确认的数据
  • 未收到但可以接收的数据
  • 未收到且不可以接收的数据

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

  • RCVD.WND:表示接收窗口的大小
  • RCVD.NXT:表示下一个期望接收的数据序列号

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

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

发送方不能无脑的发数据给接收方,要考虑接收方处理能力。如果一直无脑的发数据给对方,但对方处理不过来,那么就会导致触发重发机制,从而导致网络流量的无端的浪费。为了解决这种现象发生,TCP 提供一种机制可以让「发送方」根据「接收方」的实际接收能力控制发送的数据量,这就是所谓的流量控制。

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

  • 客户端是发送方,服务端是接收方
  • 双方的接收窗口和发送窗口相同,都为 200 字节,且每个报文段的大小为 100 字节

整个过程大致如下:客户端一口气发出了 180 字节的数据;服务端由于应用进程没有及时读取数据,处理不过来,于是在确认时只确认了其中一部分,并把自己的接收窗口调小,通过 ACK 告诉客户端;客户端收到后,也跟着把自己的发送窗口缩小,能继续发送的数据随之变少;等服务端应用读走了一些数据,接收窗口重新变大,再通过 ACK 通告给客户端,客户端的发送窗口随之扩大。窗口的大小,就这样随着 ACK 报文里的 Window 字段动态地收缩与打开。

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

前面的流量控制例子,我们假定了发送窗口和接收窗口是不变的,但是实际上,发送窗口和接收窗口中所存放的字节数,都是放在操作系统内存缓冲区中的,而操作系统的缓冲区,会被操作系统调整。当应用进程没办法及时读取缓冲区的内容时,也会对我们的缓冲区造成影响。那操作系统的缓冲区,是如何影响发送窗口和接收窗口的呢?

我们先来看看第一个例子:当应用程序没有及时读取缓存时,发送窗口和接收窗口会发生什么变化。这种情况下,接收方的应用进程一直不读取数据,接收缓存里的数据越堆越多,接收窗口只能一步步缩小,发送方的窗口也随之收缩。最后,窗口都收缩为 0,也就是发生了窗口关闭。当发送方可用窗口变为 0 时,发送方实际上会定时发送窗口探测报文,以便知道接收方的窗口是否发生了改变,这个内容后面会说,这里先简单提一下。

我们先来看看第二个例子。当服务端系统资源非常紧张的时候,操作系统可能会直接减少了接收缓冲区大小,这时应用程序又无法及时读取缓存数据,那么这时候就有严重的事情发生了:接收缓冲区被砍小之后,接收窗口来不及同步收缩,发送方还在按旧的窗口大小发送数据,超出的部分接收方根本装不下,于是出现数据包丢失的现象。

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

窗口关闭

在前面我们都看到了,TCP 通过让接收方指明希望从发送方接收的数据大小(窗口大小)来进行流量控制。如果窗口大小为 0 时,就会阻止发送方给接收方传递数据,直到窗口变为非 0 为止,这就是窗口关闭。

窗口关闭潜在的危险

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

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

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

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

糊涂窗口综合症

如果接收方太忙了,来不及取走接收窗口里的数据,那么就会导致发送方的发送窗口越来越小。到最后,如果接收方腾出几个字节并告诉发送方现在有几个字节的窗口,而发送方会义无反顾地发送这几个字节,这就是糊涂窗口综合症。

要知道,我们的 TCP + IP 头有 40 个字节,为了传输那几个字节的数据,要搭上这么大的开销,这太不经济了。就好像一个可以承载 50 人的大巴车,每次来了一两个人,就直接发车。除非家里有矿的大巴司机,才敢这样玩,不然迟早破产。要解决这个问题也不难,大巴司机等乘客数量超过了 25 个,才认定可以发车。

现举个糊涂窗口综合症的栗子:接收方的窗口大小是 360 字节,但接收方由于某些原因陷入困境,应用层每次只能读取很少的字节,接收窗口只能一点点地被腾出来。可以看到,窗口在不断缩小,发送方每次发送的数据也越来越小。

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

  • 接收方:处理太慢,来不及读取数据,通告了很小的窗口
  • 发送方:明知数据很小,还是发了出去

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

怎么让接收方不通告小窗口呢?

接收方通常的策略如下:当「窗口大小」小于 min(MSS,缓存空间/2),也就是小于 MSS 与 1/2 缓存大小中的最小值时,就会向发送方通告窗口为 0,也就阻止了发送方再发数据过来。等到接收方处理了一些数据后,窗口大小 >= MSS,或者接收方缓存空间有一半可以使用,就可以把窗口打开让发送方发送数据过来。

怎么让发送方避免发送小数据呢?

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

  • 要等到窗口大小 >= MSS 或是 Data 长度 >= MSS
  • 收到之前发送数据的 ACK 回包

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

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

可以在 Socket 设置 TCP_NODELAY 选项来关闭这个算法(关闭 Nagle 算法没有全局参数,需要根据每个应用自己的特点来关闭)。另外,Nagle 算法和延迟 ACK 凑在一起时还会衍生出不少「疑难杂症」,感兴趣可以看《TCP 协议疑难杂症全景解析》。

为什么要有拥塞控制呀,不是有流量控制了吗?

前面的流量控制是避免「发送方」的数据填满「接收方」的缓存,但是并不知道网络中发生了什么。一般来说,计算机网络都处在一个共享的环境。因此也有可能会因为其他主机之间的通信使得网络拥堵。在网络出现拥堵时,如果继续发送大量数据包,可能会导致数据包时延、丢失等,这时 TCP 就会重传数据,但是一重传就会导致网络的负担更重,于是会导致更大的延迟以及更多的丢包,这个情况就会进入恶性循环被不断地放大……

所以,TCP 不能忽略网络上发生的事,它被设计成一个无私的协议,当网络发生拥塞时,TCP 会自我牺牲,降低发送的数据量。于是,就有了拥塞控制,控制的目的就是避免「发送方」的数据填满整个网络。为了在「发送方」调节所要发送数据的量,定义了一个叫做「拥塞窗口」的概念。

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

拥塞窗口 cwnd 是发送方维护的一个状态变量,它会根据网络的拥塞程度动态变化的。我们在前面提到过发送窗口 swnd 和接收窗口 rwnd 是约等于的关系,那么由于引入了拥塞窗口的概念后,此时发送窗口的值是 swnd = min(cwnd, rwnd),也就是拥塞窗口和接收窗口中的最小值。

那么怎么知道当前网络是否出现了拥塞呢?其实只要「发送方」没有在规定时间内接收到 ACK 应答报文,也就是发生了超时重传,就会认为网络出现了拥塞。

拥塞控制有哪些控制算法?

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

  • 慢启动
  • 拥塞避免
  • 拥塞发生
  • 快速恢复

慢启动

TCP 在刚建立连接完成后,首先是有个慢启动的过程,这个慢启动的意思就是一点一点的提高发送数据包的数量,如果一上来就发大量的数据,这不是给网络添堵吗?慢启动的算法记住一个规则就行:当发送方每收到一个 ACK,拥塞窗口 cwnd 的大小就会加 1。这里假定拥塞窗口 cwnd 和发送窗口 swnd 相等,假定发包按轮次被确认,那么发包个数的变化就是 1 变 2、2 变 4、4 变 8,可以看出慢启动算法,发包的个数是指数性的增长。

那慢启动涨到什么时候是个头呢?有一个叫慢启动门限 ssthresh(slow start threshold)状态变量。

拥塞避免算法

前面说到,当拥塞窗口 cwnd「超过」慢启动门限 ssthresh 就会进入拥塞避免算法。ssthresh 的初始值一般取 65535 字节(16 位窗口字段能表示的最大值),之后会随着拥塞的发生被动态调整。那么进入拥塞避免算法后,它的规则是:每当收到一个 ACK 时,cwnd 增加 1/cwnd。接上前面的慢启动的栗子,现假定 ssthresh 为 8,那么 cwnd 的增长就从指数增长变成了每个轮次加 1 的线性增长:8 变 9、9 变 10、10 变 11……

所以,我们可以发现,拥塞避免算法就是将原本慢启动算法的指数增长变成了线性增长,还是增长阶段,但是增长速度缓慢了一些。

就这么一直增长下去,网络就会慢慢进入拥塞的状况,于是就会出现丢包现象,这时就需要对丢失的数据包进行重传。当触发了重传机制,也就进入了「拥塞发生算法」。

拥塞发生

当网络出现拥塞,也就是会发生数据包重传,重传机制主要有两种:超时重传和快速重传,这两种重传使用的拥塞发生算法是不同的,接下来分别来说说。

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

当发生了「超时重传」,则就会使用拥塞发生算法。这个时候,ssthresh 和 cwnd 的值会发生变化:

  • ssthresh 会更新为 cwnd/2(更严格地说,RFC 5681 建议取 max(cwnd/2, 2*MSS),给门限留一个下限,防止收缩得过小)
  • cwnd 会重置为 1,也就是重新开始慢启动的初始值

接着,就重新开始慢启动,数据流会被猛地砍到接近于零。这真是一旦「超时重传」,马上回到解放前。但是这种方式太激进了,反应也很强烈,会造成网络卡顿。就好像本来在秋名山高速漂移着,突然来个紧急刹车,轮胎受得了吗。。。

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

还有更好的方式,前面我们讲过「快速重传算法」。当接收方发现丢了一个中间包的时候,发送三次前一个包的 ACK,于是发送端就会快速地重传,不必等待超时再重传。TCP 认为这种情况不严重,因为大部分没丢,只丢了一小部分,则 ssthresh 和 cwnd 变化如下:

  • ssthresh 会更新为 cwnd/2
  • cwnd 也会更新为 cwnd/2

快速恢复

快速重传和快速恢复算法一般同时使用。快速恢复算法认为,你还能收到 3 个重复 ACK,说明网络也不那么糟糕,所以没有必要像 RTO 超时那么强烈。正如前面所说,进入快速恢复之前,cwnd 和 ssthresh 已被更新了。然后,进入快速恢复算法如下:

  • 拥塞窗口 cwnd = ssthresh + 3(3 的意思是确认有 3 个数据包被收到了)
  • 重传丢失的数据包
  • 如果再收到重复的 ACK,那么 cwnd 加 1
  • 如果收到新数据的 ACK,就把 cwnd 设置为 ssthresh,重新进入拥塞避免算法

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

巨人的肩膀

  • [1] 趣谈网络协议专栏.刘超.极客时间
  • [2] Web协议详解与抓包实战专栏.陶辉.极客时间
  • [3] TCP/IP详解 卷1:协议.范建华 译.机械工业出版社
  • [4] 图解TCP/IP.竹下隆史.人民邮电出版社
  • [5] TCP那些事(上).陈皓.酷壳博客.https://coolshell.cn/articles/11564.html
  • [6] TCP那些事(下).陈皓.酷壳博客.https://coolshell.cn/articles/11609.html

   转载规则


《TCP 重传、滑动窗口、流量控制与拥塞控制》 由 吴杭沉 采用 知识共享署名 4.0 国际许可协议 进行许可。
 上一篇
TCP 滑动窗口与拥塞控制图解 TCP 滑动窗口与拥塞控制图解
TCP 协议作为一个可靠的面向流的传输协议,其可靠传输与流量控制由滑动窗口协议保证,而拥塞控制则由拥塞窗口结合一系列的控制算法实现。 滑动窗口协议关于这部分自己不晓得怎么叙述才好,因为理解的部分更多,下面就用自己的理解来介绍下 TCP 的精
2021-11-16
下一篇 
服务端出现大量 CLOSE_WAIT 的排查思路 服务端出现大量 CLOSE_WAIT 的排查思路
问题:服务端出现大量 CLOSE_WAIT 状态,什么原因?有什么危害?应该怎么办? CLOSE_WAIT 是什么时候出现的回答这个问题,首先需要知道 CLOSE_WAIT 状态是什么时候出现的?还是回到经典的四次挥手过程,这个过程得不断
2021-11-02
  目录