程序员阿沛
发布于 2026-06-27 / 0 阅读
0
0

计算机网络基础七传输层之TCP滑动窗口流量控制拥塞控制连接管理和TCP中的计时器

计算机网络基础(七) 传输层之TCP滑动窗口、流量控制、拥塞控制、连接管理和TCP中的计时器

**

01 TCP的滑动窗口

什么是滑动窗口?

通信过程中一个传输方向上所有的字节的序号(seq)可以看做一个序列,而窗口则是序列中的一个子集。该窗口用于做流量控制以及拥塞控制。

滑动窗口以字节为单位而不是分组。

一个连接的两端都各有一对窗口,它们分别是发送窗口和接收窗口,因此一个连接有4个窗口,且窗口的大小是动态变化的。

发送窗口包含 已发送但未确认的数据 和 准备发送的数据。

接收窗口包含 按序到达但未被应⽤程序接收的数据、不按序到达 的数据。

窗口指针保存在套接字中。

在不考虑拥塞的情况,发送端A的发送窗口和接收方B的接收窗口大小从整个传输过程来看是一致的(但不是强一致,并不总是一样大)。

发送窗口越大,发送方在收到确认前能连续发送的数据越多,在不考虑网络拥塞因素下传输效率越高。

滑动窗口工作过程

下面我们只观察发送方的发送缓冲区和接收方的接收缓冲区。

1、某一时刻,A 收到了 B 的确认报⽂段:报文携带的窗口值 20 字节,确认号为 31。A
就可以把落⼊发送窗⼝中的序号字节(也就是下图的31~50号字节)⼀次连续性全部发送出去:边发送边接收B的确认。

2、下一刻A发送了31~41号字节, 在接收方B确认这些字节前会保留在窗口中以便超时重传时使用。

然而在传输过程中,31号字节因为某种原因晚于32、32号字节到达B。B的接收窗口显示,B没有收到31,32~33是未按需到达的数据,要临时存放在接收窗口,不能上交给应用进程。此时B的接收窗口也不能移动。

3、下一刻B收到了31,B将31~33字节交付应用层,并从接收缓冲区中删除,且B接收窗口右移3个字节,发送ack=33的确认报文(假设确认报文的窗口大小字段仍是20)。A收到确认后窗口右移3字节。

不过如果A发送的速度过快,将窗口内所有数据发送给B,却没有收到B的任何确认之前,也就是下图中P2=P3时,A的可用发送窗口为0,会停止发送。

缓冲区与窗口的关联

滑动窗口本身不是一个数组之类的容器,而且简单的两个指向socket发送缓存或接收缓存的开始指针和结束指针。也就是说,滑动窗口表示的是socket缓冲区中哪一块数据能发送,或者说接收来的数据能放到socket缓冲区的哪个位置。

我们先看发送方的发送缓存与发送窗口:

发送窗⼝通常只是 发送缓存的⼀部分,具体来说 发送缓存 = 应用程序写入发送缓冲区的最后一个字节 -
最后被接收方确认的字节。缓冲区中,最后被接收方确认的字节指针之前的数据由于已经发送和收到确认,因此被释放出缓冲区。

接收方的接收缓存与接收窗口同理:

02 流量控制和拥塞控制

** 流量控制 **

流量控制是指 动态控制滑动窗口的大小使得发送方发送数据的速率略小于或等于接收方接收的速率 ,防止接收方的接收缓存溢出造成分组丢失。

流量控制的实现是通过在接收方的ACK报文携带窗口大小,同步给发送方。假设ACK报文中携带的窗口大小值为X,发送方收到这个ACK报文之后,就会调整自己的可用窗口(也就是上图中P3-P2的部分)为X。

需要注意的是发送窗口的p2-p1取决于ack号,P3-P2取决于ack报文中的窗口大小。

下面是一个流量控制的过程(不考虑拥塞的情况下):

图中rwnd(receiver window)表示容许的接收方窗口。

持续计时器

考虑一种情况,如果B向A发送了一个零窗口报文后,A就会停止向B发数据。后来B又向A发送了一个rwnd=400的ACK报文M。但报文M在网络中丢失了,永远到不了A了,A就会一直在等B的非零窗口通知,B也在等A发过来的数据,陷入死锁局面。此时该如何解决呢?

TCP为每个连接设有一个 持续计时器
,只要A收到零窗口通知就启动该计时器,假设计时器到期A还没收到B的非零窗口报文,A就会发送一个仅携带1字节的“零窗口探测报文”。对端B就会确认这个报文的时候携带新的rwnd值,然后A就能又开始发送数据。如果A接收到的rwnd仍为0则重新启动持续计时器。

拥塞控制

什么情况下叫做拥塞?在网络中,链路容量(也就是带宽)、交换机和路由器中的缓存和处理机都是网络的资源,在某段时间,若对网络中某一资源的需求超过了该资源能提供的部分,导致分组在链路中丢失,这种情况就叫拥塞。

拥塞的特点:

1、网络拥塞是由网络资源中的短板资源所决定的,只有所有类型的网络资源同时提高供给才会真正改善网络性能(例如你提高了带宽,但是路由器的缓存较小,瓶颈就转移到了路由器缓存那里)。

2、拥塞趋于恶化,例如某个路由器没有足够的缓存,缓存溢出会导致丢包和端系统重传,而一旦重传又会加重网络拥塞。

3、 拥塞的直接表现就是丢包和重传 ,当端系统的重传次数明显增加,就表明网络很可能发生了拥塞。

举个例子:如果是带宽出现瓶颈,则RTT会增加,导致超时重传;如果是路由器缓存瓶颈,分组到达路由器后因缓存溢出而丢包,又会导致超时重传。因此重传就是拥塞的表现。

查看拥塞的指标(了解即可):

• 由于缺少缓存空间⽽被丢弃的分组的百分数;

• 平均队列⻓度;

• 超时重传的分组数;

• 平均分组时延;

• 分组时延的标准差,等等。

简单的记就是: 丢包率、重传率和时延

拥塞控制是防⽌过多的数据注⼊到⽹络中,使⽹络中的路由器或 链路不致过载。

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

流量控制是解决端与端的发送与接收速率不匹配的问题,需要发送方同步接收方的接收速度;

拥塞控制是解决端系统的通信量与网络链路资源不匹配引起的路由器和链路过载问题,需要控制端系统注入到网络的数据量和速度。

TCP中的拥塞控制

首先需要知道,TCP的套接字中有2个关于窗口的变量:rwnd和cwnd,他们分别表示 接收方窗口和拥塞窗口
,用来分别做流量控制和拥塞控制的。TCP头部的窗口大小其实指的是rwnd。

_ 发送方的实际发送窗口 = Min( _ 接收方的 _ rwnd, 发送方的cwnd)。 _

接下来我们假设接收方的接收缓存无限大,无需流量控制,发送方的发送窗口 = 发送方的拥塞窗口cwnd。

端系统如何主动感知网络拥塞,或者说端系统在什么情况下认为网络发送拥塞?

1、当发送方重传计时器启动时(分组确认超时),就认为网络拥塞;

2、发送方接收到3个冗余确认(即3个ack号相同的确认)就认为网络拥塞,因为这说明窗口发出的多个报文乱序到达,中间的某个报文丢失。

这2种情况都会导致发送方重传,但后者不一定是网络拥塞,而可能报文意外丢失,但也不排除是拥塞的可能。

拥塞控制的4种方法

慢开始、拥塞避免、快重传和快恢复。

这4种方法的基本思路是,只要网络没有拥塞,拥塞窗口就可以增大些,出现拥塞就减小些。

慢开始算法(慢启动)

慢开始的思路是从小到大以指数方式增加拥塞窗口的数值。慢开始发生在刚建立连接后的数据收发。

一开始发送方并不清楚网络的拥塞情况,就先将cwnd初始值设置为1~2个SMSS(SMSS就是发送方MSS,MSS是TCP报文数据体能容纳的最大字节数),新的RFC标准则把初始cwnd设置为2~4个,至于取2还是3还是4,取决于SMSS有多大。

在每收到⼀个对新的报⽂段的确认(重传的确认不算)后,发送方的拥塞窗⼝就增加⼀个 SMSS 的数值,因此该阶段cwnd会呈指数级别增长。

慢启动的窗口增长速度其实不慢(因为是指数级别增长),之所以叫慢启动是因为它的初始cwnd值很小。

顺带一提 ,新建立的连接会用到慢启动,TCP 还实现了 慢启动重启 (SSR)
机制。这种机制会在持久连接空闲一定时间后重置拥塞窗口为初始cwnd值。道理很简单,在连接空闲的同时,因为发送方没有发送数据,所以发送方不知道网络状况在这段期间发生了什么变化,是拥塞了还是顺畅了,发送方都不知道。为了避免拥塞,发送方理应将拥塞窗口重置回“安全的”默认值。

为了不让窗口无限的指数增长,提出了慢开始门限,当窗口大小超过了慢开始门限 ssthresh 则使用 拥塞避免算法 线性的增长窗口。

当 cwnd < ssthresh 时,使⽤慢开始算法;

当 cwnd >= ssthresh 时,停⽌使⽤慢开始算法⽽改⽤拥塞避免算法;

拥塞避免算法

该算法是指:当cwnd超过慢开始门限后,每经过一个RTT,拥塞窗口就线性增长 cwnd = cwnd + 1。

快速重传算法

该算法是指:如果发送方连续收到3个重复ack号的确认,说明接收方收到了乱序的报文(某个中间报文丢失或者迟到),发送方会立即进行重传,而不是等到超时时间用完才重传,避免发送方误认为发生了网络拥塞。

中间报文的丢失或迟到极可能是意外丢失或迟到,而不是因为网络拥塞导致的丢失,但不排除拥塞的可能性。但如果接收方发送出3个连续相同ACK号的报文后,又能很快的收到发送方重传的数据,那接收方就能明白并不是拥塞,而只是中间报文意外丢失或迟到。

快速重传可以是网络的吞吐量提高20%。

拥塞惩罚和快恢复算法

拥塞惩罚是指端系统检测到网络拥塞时,降低自己cwnd窗口的行为。

拥塞惩罚按超时重传和快速重传分为两种惩罚方式:

当发生超时重传时,发送方会认为网络出现拥塞,拥塞窗口cwnd会变成1。

当发生快速重传时,该分组很可能是意外丢失或迟到,但不排除拥塞的可能,因此cwnd会变为 cwnd/2。

快恢复算法是指当发生快速重传时,当前拥塞窗口大小减小一半,之后直接执行拥塞避免算法线性增长cwnd , 而不是执行慢开始算法指数增长cwnd。

下面是TCP拥塞控制流程图:

整个拥塞惩罚机制逻辑如下。

超时重传的情况下:

• 慢开始⻔限 ssthresh = max(cwnd/2,2);

• cwnd = 1;

• 执⾏慢开始算法。

快速重传的情况下(快速恢复):

• 慢开始⻔限 ssthresh = 当前拥塞窗⼝ cwnd / 2 ;

• 新拥塞窗⼝ cwnd = 慢开始⻔限 ssthresh ;

• 开始执⾏拥塞避免算法,使拥塞窗⼝缓慢地线性增⼤。

无论是超时重传还是快速重传,都会导致慢开始门限减半,这会导致多次惩罚后,不再会执行指数增长,而是全变成线性增长。

主动队列管理 AQM

对TCP拥塞控制影响最大的网络层策略是分组丢弃策略。该策略的内容为,到达路由器的分组会按先进先出原则放入到缓存队列中,一旦队列已满,后到达的分组会被丢弃。

这种丢弃策略会导致 一连串分组的丢失和超时重传,这一方向的所有TCP连接都进入慢开始状态,这种情况叫做全局同步
,全局同步会导致通信量突然下降,不一会儿通信量又突然增大(因为慢开始会导致报文指数增长)。

为了避免全局同步,我们可以在队列长度到达某个警戒线时主动丢弃部分分组,而不是在分组数量达到最大队列长度时被动丢弃所有分组,这就是
主动队列管理AQM

AQM有不同的实现方式,比较主流的是随机早期检测RED。

RED规定路由器维持一个最小门限THmin和最大门限THmax。

队列⻓度L⼩于最⼩⻔限 THmin,将新到达的分组放⼊队列进⾏排队;

队列⻓度L 超过最⼤⻔限 THmax,将新到达的分组丢弃;

队列⻓度L 在最⼩⻔限 THmin 和最⼤⻔限 THmax 之间,按 照概率 p 丢弃新到达的分组。而且随着队列长度L的增加,p也会变大。

03 TCP的连接管理

TCP连接要解决3个问题:

1. 使对方知道自己的存在,且确认双方能发送能接收;

2. 允许双方协商一些参数(如最大窗口值,是否用窗口扩大选项和时间戳选项等);

3. 分配运输资源(缓存大小,连接表中的项目);

连接建立需要3个报文:

下面是连接的状态变化

在连接之前,A和B会先创建传输控制块TCB,存储了连接相关的重要信息如 TCP连接表,指向发送和接收缓存的指针,指向重传队列的指针,seq和ack等。

SYN不能携带数据,但需要消耗一个序号。ACK报文是可以携带数据的。

为什么建立连接是两次握手而不是三次?

是为了防止已失效的连接请求报文段传到了B产生错误。具体情境如下:

A
发送的SYN报文丢失,A又重发了一个SYN报文,并建立连接成功,后来关闭了连接,通信结束。但丢失的SYN报文此时到达B,B发送第二次握手的报文(ACK报文)后直接进入连接状态,而A没有发起建立连接的请求,不会理睬这个ACK报文,但B会一直等待A发送数据报文段过来,B的资源白白浪费。

下面是四次挥手断开连接

需要注意:

通信双方都可以主动发起关闭连接的请求报文。

FIN可以携带数据,但如果不携带数据也会消耗一个序号。

当被动关闭者B进入CLOSE-
WAIT状态时,TCP连接处于半关闭状态,此时B可以发送数据,A无法发送数据,但可以接收数据。因此这个状态下,B可能还会继续发送消息给A。

A进入TIME-WAIT状态后,必须经过时间等待计时器设置的时间2MSL后才能进入CLOSED状态。

一个问题:A 为什么必须等待 2MSL (MSL是最长报文段寿命)的时间后才真正关闭连接?

1、防止第四次挥手的ACK丢失后B无法进入CLOSED状态。

假设A在第三次挥手之后直接进入CLOSED,而且最后一个ACK丢失,B会重发第三次挥手,假设A之前的端口是X,这时有两个情况:

一个是A之前的端口又开始建立新的连接,那么A收到该FIN报文之后,会回应一个RST报文给B;

一个是A之前的端口没有再开启过了,那么B的FIN报文不会得到ACK回应,B会不停的重传。

2、保证本次连接产生的所有报文(FIN、SYN和数据报文)在这2MSL内从网络中消失,不会和新连接的报文发生混淆(尤其是新连接和旧连接的客户端端口是相同的情况下)。

TCP半关闭

半关闭是指建立连接的两端只有其中一端发送FIN报文,关闭双向连接的某一个方向。主动发送FIN的一端之后就无法向对端发送数据,只能接受对端发送的数据和发送ACK报文段。

一端发送FIN报文之后,另一端发送FIN报文之前的连接状态称为“半关闭状态”。

套接字的close()提供了全关闭操作,而shutdown()则提供了半关闭操作,实际应用中半关闭很少用到。

TCP同时打开与关闭

同时打开是指通信双方A和B,A发送SYN报文给B,并在报文段到达B之前,B也发送SYN报文给A。同时打开只会出现在A和B都是服务器端的情况下。

连接建立超时

如果一个客户端发起连接请求时,服务器是关闭的,那么客户端会在连接等待超时后再重新发送SYN报文,并且每次超时,超时时间都会翻倍。这一行为被称为指数回退。

在Linux中net.ipv4.tcp_syn_retries参数可以配置重发SYN的次数,而net.ipv4.tcp_synack_retries则是第二次握手的SYN报文的重发次数。这两个参数通常选择一个较小值5。

TCP有限状态机

TCP中的相关计时器

超时重传计时器:当发送方发送TCP分组后就会启动超时重传计时器,超过一定时间没收到对端的ACK报文,发送方就会重新发送分组;

零窗口持续计时器:零窗口时发送探测报文的计时器;

Time-Wait计时器:time-wait等待2MSL的计时器;

保活计时器:防止TCP连接长时间空闲;

发送报文计时器:防止发送方长时间没有发送报文;

除了保活计时器外,这些计时器在上一章和本章的内容中都有介绍过。

保活计时器⽤来防⽌在TCP连接出现⻓时期的空闲以及判断对方是否故障下线。

保活计时器通常设置为2小时 。若服务器过了2小时还没有收到客户的信息,它就发送探测报⽂段。若发送了10个
探测报⽂段(每⼀个相隔75秒)还没有响应,就假定客户出了故障,因而就终⽌该连接。


评论