TCP
非常重要,TCP 三次握手、四次挥手、滑动窗口、可靠传输、拥塞控制 都需要深入掌握细节,能够在思想上理解 TCP 的整个传输流程,这样才能应对有变化的选择题和解答题。
TCP 特点
- 面向连接:发送数据前后需要分别通过三次握手和四次挥手进行连接的建立和断开。
- 可靠交付:保证数据传输的无差错、不丢失、不重复、有序。
- 面向字节流:以滑动窗口的形式对字节按照顺序进行发送和接收。
- 全双工:通信双方在一个 TCP 连接中都可以发送和接收数据。
TCP 首部
- 源端口号(Source Port):16 位字段,指示发送端的端口号。
- 目标端口号(Destination Port):16 位字段,指示接收端的端口号。
- 序列号(Sequence Number):32 位字段,用于标识 TCP 报文段中第一个数据字节的序列号。这个字段用于实现 TCP 的可靠性机制,如数据的按序传递和重传。
- 确认号(Acknowledgment Number):32 位字段,如果设置了 ACK 标志位,该字段包含了期望接收的下一个数据字节的序列号。这个字段用于确认已经成功接收的数据。
- 首部长度(Header Length):4 位字段,指示 TCP 首部的长度,以 32 位字为单位。这个字段用于指示首部的长度,因为 TCP 首部长度可以变化,根据选项的存在而变化。
- 保留(Reserved):6 位字段,保留供未来使用,目前必须设置为 0。
- 控制标志位(Flags):408 教材通常重点讲 URG、ACK、PSH、RST、SYN、FIN 六个标志;现代 TCP 首部还定义了 ECE、CWR、NS 等扩展标志。六个经典标志分别是:
- URG(紧急指针有效位):用于指示紧急数据。
- ACK(确认位):用于指示确认号字段有效。
- PSH(推送位):用于指示接收端应立即交付数据给应用层,而不需要等待缓冲区满。
- RST(复位位):用于强制释放连接,重置连接状态。
- SYN(同步位):用于建立连接,用于初始化序列号。
- FIN(终止位):用于关闭连接。
- 窗口大小(Window Size):16 位字段,通告 本端当前可用的接收窗口。对方用它进行流量控制,防止压垮接收缓冲区;它不直接表示网络是否拥塞。若握手时协商了窗口扩大选项,后续窗口还要按比例因子解释。
- 校验和(Checksum):16 位字段,用于检测 TCP 首部和数据部分的传输中的错误。
- 紧急指针(Urgent Pointer):16 位字段,仅当 URG 标志位设置时才有效。用于指示紧急数据的末尾位置。
- 选项(Options):可选字段,用于包含一些额外的控制信息,如最大报文段长度、时间戳等。长度可变,最长可达 40 字节。
- 填充(Padding):根据选项字段的长度而变化,用于确保 TCP 首部的总长度是 32 位的倍数。
三次握手
TCP 通过 三次握手 建立连接,其具体过程如上图所示,每次握手的状态变化和字段解释如下:
第一次握手(客户端 → 服务端)
- 客户端状态变化:
CLOSED→SYN-SENT - 客户端发送: 一个带有
SYN=1的包,请求建立连接,并选择一个初始 序列号seq = x - 服务端状态:
LISTEN,等待连接
第二次握手(服务端 → 客户端)
- 服务端状态变化:
LISTEN→SYN-RECEIVED - 服务端收到 SYN 包后,回应一个
SYN + ACK包:SYN = 1:表示服务端也同意建立连接ACK = 1:确认客户端的 SYNseq = y:服务端自己的初始 序列号ack = x+1:确认号是客户端 序列号 + 1
第三次握手(客户端 → 服务端)
- 客户端状态变化:
SYN-SENT→ESTABLISHED - 客户端收到服务端的 SYN + ACK 后,回应一个
ACK包:ACK = 1:确认服务端的 SYNack = y+1:确认号是服务端 序列号 + 1
这三次握手的 主要目的 是:
- 第一次报文让服务端知道客户端希望建立连接,并携带客户端 ISN;仅凭这一步,客户端还不能确认服务端可达。
- 服务端收到 SYN 后,确认“客户端 → 服务端”方向可达,并用 SYN+ACK 携带自己的 ISN;客户端收到它后,确认“服务端 → 客户端”方向可达。
- 第三次 ACK 让服务端确认自己的 SYN 已被客户端收到,双方的初始序列号均已确认,连接进入
ESTABLISHED。
ISN
ISN(Initial Sequence Number,初始序列号) 是连接建立时双方 各自选取的第一个序列号,用于标识数据字节流的起始位置。
ISN 通常由随时间变化且难以预测的算法生成,用来降低旧连接残留报文与新连接混淆的风险,并增强对序列号猜测攻击的抵抗能力。
第三次握手可以携带应用层数据吗?
第三次握手发送的是一个 ACK 报文段,TCP 标准允许它同时携带数据(俗称 ACK + Data)。
不过实际中:
- 大多数客户端都是发送一个纯 ACK完成握手;
- 然后紧接着再发送第一个数据报文。
- TCP Fast Open 更特别:它可让客户端在 SYN 中携带数据,而不只是利用第三次 ACK,需双方额外支持并满足安全条件。
假设客户端第一次握手的 SYN 的 seqno 为 x,那么客户端发送的 第一个应用层字节的序列号 为 (x + 1) mod 2³²。 如果第三次握手携带数据,那么这个字节就在第三次握手报文中;否则就在握手完成后的第一个数据报文中。
四次挥手
第一次挥手:客户端发起关闭请求
- 客户端状态变化:
ESTABLISHED→FIN-WAIT-1 - 客户端动作: 应用层调用
close(),客户端发送FIN=1, seq=u,表示不再发送数据了,但仍可以接收数据
第二次挥手:服务端确认关闭请求
- 服务端状态变化:
ESTABLISHED→CLOSE-WAIT - 服务端动作:
- 收到
FIN后,发送ACK=1, seq=v, ack=u+1表示确认 - 通知上层应用准备关闭
- 收到
- 客户端状态变化:
FIN-WAIT-1→FIN-WAIT-2
第三次挥手:服务端发起关闭请求
- 服务端动作:
- 应用层处理完毕,发送
FIN=1, ACK=1, seq=w, ack=u+1
- 应用层处理完毕,发送
- 服务端状态变化:
CLOSE-WAIT→LAST-ACK - 客户端动作: 收到该
FIN报文后,进入TIME-WAIT状态
第四次挥手:客户端确认服务端关闭
- 客户端动作: 发送
ACK=1, seq=u+1, ack=w+1 - 客户端状态变化:
TIME-WAIT→ 等待 2 个最大报文生存时间(2×MSL)后 →CLOSED - 服务端状态变化: 收到 ACK 后 →
CLOSED
MSL 与 TIME-WAIT
最大报文段生存期(MSL,Maximum Segment Lifetime)是 TCP 报文段在网络中可能存活的最长时间。主动关闭方在发送最后一个 ACK 后进入 TIME-WAIT,通常等待 2×MSL,以便处理最终 ACK 丢失并让本连接的旧报文退出网络。不要把 MSL 误写成 Maximum Segment Length;表示最大报文段数据长度的是 MSS。
那么为什么要等 2×MSL?主要在于两点:
- 确保对方收到了 ACK 如果 ACK 丢了,对方会重发 FIN,此时还在 TIME-WAIT 的一方可以再次发送 ACK。
- 清除网络中的旧报文 等待 2×MSL,可以确保本连接中的所有残留数据段在网络中都已过期,不会影响后续新的连接。
第一次挥手一定由客户端发起吗?
第一次挥手是由发起连接关闭的一方发送的,通常情况下是客户端发送。但在某些特殊情况下,服务器端也可以主动发起连接关闭,不过这种情况相对较少见。
为什么握手通常是三次,挥手通常是四次?
三次握手: 第一次和第二次握手用于让客户端确认其发送的数据能够到达服务端,从而建立起 客户端到服务端 的半双工通信; 第二次和第三次握手则用于让服务端确认其发送的数据能够到达客户端,从而建立起 服务端到客户端 的半双工通信。 至此,TCP 的全双工通信连接正式建立。
四次挥手: 第一次和第二次挥手用于关闭 客户端到服务端 的半双工通信。此时,服务端仍可能有数据尚未发送完毕,因此不能立即关闭; 在服务端数据发送完毕后,通过第三次和第四次挥手来关闭 服务端到客户端 的半双工通信,至此连接完全断开。
更准确地说,TCP 连接的两个方向可以分别关闭:收到 FIN 只表示对方不再发送数据,本端仍可继续发送。若被动关闭方恰好也已无数据要发,它对第一个 FIN 的 ACK 可以与自己的 FIN 合并,报文数不一定严格等于四个;“四次挥手”描述的是 ACK 与 FIN 分开发送的典型情形。
下面的状态机可分别选择“正常建连”“主动关闭”“同时关闭”等事件,观察两端状态,而不是只背报文次数。
TCP 建连与释放状态机
选择发送 SYN、收到 SYN+ACK、主动关闭或收到 FIN 等事件,观察连接两端如何经过三次握手、四次挥手和 TIME-WAIT。
选择一个事件,观察状态如何迁移。 当前状态:CLOSED。
滑动窗口机制
上图展示了 滑动窗口 的基本结构。滑动窗口是字节流上的“移动视口”:发送窗口既包括 已经发送但尚未确认 的字节,也包括 尚未发送但当前允许发送 的字节;接收窗口表示接收方当前愿意接收的序列号范围。窗口内字节不一定都已在网络中传输,但它们在字节流中构成连续区段。
- 发送方 根据窗口大小,将窗口范围内的数据一次性发送出去,而不必等到每个报文段的确认返回。
- 接收方 在收到这些报文段后,会向 发送方 发送 ACK(确认)报文。该 ACK 包含两个重要信息:
- 确认号:表示下一个期望收到的字节序列号,累计确认该序号之前的连续字节。
- 接收窗口大小(即接收方当前还能接收的字节数),该值写在 ACK 报文的窗口字段中。
- 发送方 在收到 ACK 后,滑动窗口向前移动,窗口左端排除已确认的数据,右端随即腾出新的空间。此时,发送方就可以继续发送后续的数据。
绝对下标和序列号
可以把 TCP 发送的数据想象成一条无限长的字节流,每个字节在这条流中都有一个唯一的位置,这个位置就是绝对下标(absolute index)。它从连接开始不断递增,本质上是一个“不会回绕的理想编号”。
但 TCP 报文头部的 Sequence Number(seqno) 只有 32 位,因此它不可能直接表示这个无限增长的绝对位置,而是采用了一个取模映射的方式。
1. 从“字节流”到“窗口”
发送方并不是一次发送所有字节,而是维护一个滑动窗口:
- 窗口左边界:最早未被确认的字节(absolute index)
- 窗口右边界:允许发送的最大字节位置(由接收窗口决定)
也就是说:窗口内的每一个字节都有一个绝对下标,同时也必须映射成一个 seqno 才能发出去
2. 绝对下标 → 序列号(发送时)
TCP 在连接建立时会选择一个初始序列号(ISN)。 之后发送第一个数据字节时,并不是用 ISN,而是:
ISN:用于 SYNISN + 1:对应第一个数据字节
因此映射关系是:
若把首个数据字节的绝对下标定义为 0,则发送时的映射为
直觉理解:
absolute index = 0→seqno = ISN + 1absolute index = 1→seqno = ISN + 2- ……
seqno 本质就是 absolute index + 一个偏移量,再取 2³² 模
3. 序列号 → 绝对下标(接收时)
接收方拿到一个 seqno 时,会遇到一个问题:
seqno 是 32 位的,会“回绕”(wrap around),那它到底对应哪一个真实位置?
例如:
- seqno = 100,可能是第 100 字节
- 也可能对应下一轮的第 个位置
所以必须结合当前滑动窗口的位置来判断。
核心思路是:
👉 在所有可能的绝对下标中,选一个最接近当前接收窗口的那个
可以写成:
其中:
- 前半部分得到模 的位置;
- 整数 选择合适的回绕轮次,使结果落在当前窗口附近。
4. 为什么滑动窗口能解决歧义?
关键点在这里:
窗口尺度远小于 ,并且 TCP 使用模序列号比较规则。 传统未扩大窗口最大约 64 KiB;使用窗口扩大选项后可更大,但有效窗口仍受协议的序列号比较与实现约束。
因此在任意时刻:
- 合法的数据只会落在一个很小的区间内
- 不会跨越多个“2³²周期”
这就保证了:
每个 seqno 在当前窗口中只可能对应一个合理的 absolute index
可以用一句话总结整个机制:
TCP 用 absolute index 管理真实的数据流顺序,用 seqno(32位) 在网络上传输,通过 滑动窗口的位置 消除回绕带来的歧义。
更具体地:
- 发送方
- 用 absolute index 组织数据
- 映射成 seqno 发出去
- 维护发送窗口(哪些还没被 ACK)
- 接收方
- 收到 seqno
- 根据窗口位置还原 absolute index
- 决定是否接收、缓存、ACK
发送和接收窗口
TCP 的 发送窗口 可以按照逻辑划分为四个部分:
- 已经发送并且被确认的数据(字节流)
- 已经发送但还没有被确认
- 尚且还没有发送
- 暂时不可以发送
其中第 2、3 个部分构成 TCP 的发送窗口,当发送方收到 ackno 在第 2 个部分内的确认报文时,调整滑动窗口的大小后向前移动滑动窗口,并且发送接下来可以发送的数据。
TCP 的 接收窗口 可以按照逻辑划分为四个部分:
- 已经被应用层接收的数据
- 已经被 TCP 接收,但是还没有被应用层接收的数据
- 还没有接收到的数据
- 还不可以接收到的数据
窗口大小
在 TCP 传输过程中,窗口的概念决定了发送方能够“在等待确认之前”一次性发送多少数据。常见的窗口有三类:
- 发送窗口(swnd,send window)
- 接收窗口(rwnd,receive window)
- 拥塞窗口(cwnd,congestion window)
下面分别说明这三种窗口的含义、它们是如何相互影响的,以及在 TCP 运行期间会受到哪些因素的调节。
拥塞窗口大小
拥塞窗口(cwnd) 是 发送方 内部维护的一个控制变量,用来限制未确认数据的数量,以避免在网络中引入过多分组而造成拥塞。
cwnd 是 拥塞控制 的核心参数,TCP 的拥塞控制算法会根据 ACK、丢包、显式拥塞通知等信号动态调整它。
发送窗口大小
发送窗口大小 是发送方实际能够使用的窗口,它等于拥塞窗口与接收窗口中的较小者:
- 当发送方收到接收方的 ACK 报文时,报文中会携带 window 字段,这个字段的数值反映了接收方当前公开的接收窗口(rwnd)。发送方据此更新 rwnd。
- 同时,发送方根据 ACK 的到达情况(是否成功、是否出现重复 ACK、是否发生超时)来调整 cwnd,这就是拥塞控制算法(如慢启动、拥塞避免、快速恢复等)的工作机制。
- 只要 cwnd 或 rwnd 任意一个发生变化,发送窗口(swnd)也会随之重新计算,从而影响后续能够发送的数据量。
接收窗口大小
接收窗口大小 由接收方根据自身的处理能力和缓冲区使用情况动态决定:
- 缓冲区余量:接收方为每个 TCP 连接分配了一块接收缓冲区(receive buffer)。当缓冲区中已有的数据量接近上限时,接收方会把 rwnd 调小,以防止发送方继续发送导致缓冲区溢出。
- 应用层消费速率:如果上层应用程序(如文件写入、播放器)处理数据的速度慢于网络到达速度,接收方同样会降低 rwnd,让发送方放慢发送节奏。
- 系统资源限制:操作系统可以通过套接字选项(如
SO_RCVBUF)限制每个连接的最大接收缓存,这也是 rwnd 的上限。
当接收方的缓冲区被消费掉一定量后,它会在随后的 ACK 中把 rwnd 调大,通知发送方可以恢复更高的发送速率。这样形成了发送方与接收方之间的“流量控制”循环。
初始发送窗口大小
在一个 TCP 连接刚建立时,双方都需要一个起始窗口值,以便开始数据的交换。初始窗口大小 受以下因素影响:
- 初始拥塞窗口(IW,Initial Window):现代实现常按 RFC 6928 取不超过约 10 个 MSS 的初始窗口,但 408 传统题目往往明确给出或默认从 1 MSS 开始。做题必须以题设采用的模型为准。
- 接收方的接收缓冲区:在三次握手的 SYN 包中,接收方会把自己的 rwnd(即接收窗口)放入 Window Size 字段,发送方据此确定初始的 swnd。如果接收方的缓冲区较大,则初始 rwnd 也会相应较大。
- 系统默认配置:不同的操作系统或协议栈会在内核参数中预设一个默认的 rwnd(比如 Linux 的
net.ipv4.tcp_rmem),这些参数会在未显式设置时使用。
综上,初始窗口大小 实际上是 cwnd 与 rwnd 两者的交叉结果:
在连接建立后,随着数据的发送、确认以及接收方缓冲区的消耗,这三个窗口会不断被重新评估和调节,从而实现 TCP 的可靠传输、拥塞控制和流量控制三大核心功能。
窗口移动过程
TCP 可以看成在一条连续的字节流上维护一个“滑动窗口”,窗口表示当前允许发送且尚未确认的数据范围。
- 初始状态: 发送方根据接收方通告的窗口大小,确定一个发送窗口
[左边界, 右边界),并一次性发送窗口内的数据(按序列号对应的字节序列)。 - 收到 ACK: 接收方确认已正确接收的数据(ACK = 下一个期望字节的序列号)。
- 窗口右移(滑动): 发送方收到 ACK 后:
- 将窗口左边界移动到 ACK 指示的位置(已确认的数据被“移出窗口”)
- 窗口整体向前滑动
- 右边界随之扩展(如果接收窗口允许)
- 继续发送: 新进入窗口的字节可以继续发送,实现“流水线式”传输,而无需逐个等待确认。
本质就是一句话:
ACK 推动窗口向前滑动,释放发送空间,从而实现连续高效的数据传输。
下面的交互用一段具体字节序列推进 seq、ack、已发送未确认范围与新开放窗口,静态分区图保留作结构底稿。
TCP 字节序号、累计 ACK 与窗口移动
用 ISN=1000、接收窗口 8 B 的小例子逐步推进发送范围、累计确认与窗口边界,并观察中间字节丢失时 ACK 如何停在缺口。
当前查看:建立字节起点。SYN 使用 seq=1000 并消耗一个序列号,所以第一个数据字节序号是 1001。对端通告 rwnd=8 B。
流量控制
流量控制是 TCP 的端到端机制,旨在协调发送方和接收方的数据速率,防止接收方缓冲区被压垮。TCP 的英文全称是 Transmission Control Protocol,但这不是“流量控制”一词的英文释义。
在 TCP 中,流量控制通过 滑动窗口机制 实现,具体过程如下:
接收方通告窗口大小 接收方根据自身的处理速度、可用缓存空间以及当前的负载,计算出一个 窗口大小(Receiver Window),并在每个 ACK 报文中将该值告知发送方。窗口大小代表接收方在收到下一个 ACK 之前,能够接受的最大未确认字节数。
发送方依据窗口限制发送数据 发送方在发送数据时,必须确保已发送且尚未被确认的字节总量不超过接收方通告的窗口大小。若窗口为零,发送方会暂停发送新数据,只等待接收方释放缓冲区并更新窗口;此时若仍需要保持连接活跃,发送方会周期性发送 Zero‑Window Probe(零窗口探测)报文,以探测对方是否已恢复接收能力。
窗口的动态调整 随着接收方处理数据并释放缓存,窗口大小会在后续的 ACK 中被重新通告,发送方随即可以利用增加的窗口发送更多数据。这个过程在整个连接期间持续循环,使得发送速率始终与接收方的实际承载能力保持同步。
需要注意的是,流量控制 关注的是单个连接内部的发送/接收平衡,而拥塞控制 则针对网络整体的拥塞状态(例如链路容量、路径上的竞争)进行调节,两者相互独立但常常协同工作,以实现端到端的可靠与高效传输。
零窗口控制:如果接收方缓冲区已满,它可以通告零窗口。发送方暂停新数据发送,但会用持续计时器安排零窗口探测,避免“窗口更新报文丢失后双方永久等待”的死锁。
可靠传输机制
TCP 的 可靠传输 通过多种机制共同实现,下文将对三个关键机制进行介绍。
序列号和确认号
序列号(seqno, sequence number)和 确认号(ackno, acknowledge number)是 TCP 首部的两个字段,TCP 协议通过 序列号 来记录目前已经发送了哪些数据,通过 确认号 记录哪些数据已经被接收方所接收。
发送方发送 序列号 和 接收方返回 确认号 的交互可能存在以下几种情况:
- 如果发送的数据段丢失了,接收方不会发送更新的 确认号,这会最终导致发送方超时并重传丢失的数据段。
- 如果数据段乱序到达,接收方通常会重复发送相同的累计 确认号,即仍然期望的下一个连续字节序列号;是否缓存乱序数据取决于实现与选项。
- 如果数据段到达了接收方,并且是按顺序的,接收方发送一个新的 确认号,提示发送方到目前为止的所有数据都已正确接收。
超时重传
TCP 是一种 可靠传输协议,它要确保发送的数据 被对方接收并确认。但网络中可能发生:
- 包丢失(网络拥堵)
- ACK 丢失(确认丢了)
- 延迟很大(RTT 不稳定)
为了应对这些情况,TCP 维护重传计时。经典描述不是“每个报文段各有一个独立计时器”,而是通常对 最早尚未确认的数据 维护一个重传计时器;累计 ACK 推进后,计时器停止或为新的最早未确认数据重新启动。
如果某个 segment 的定时器超时了,就说明发送方在 超时时间阈值 内没有接收到该 segment 的确认,发送方就会触发 超时重传(Retransmission Timeout),重新发送超时的 segment。
RTO
RTO 是 TCP 为等待 ACK 设置的超时时间,如果超时没收到 ACK,就会重传数据包。
那么 超时重传的时间时如何确定的?(了解即可)
超时重传时间可用平滑 RTT 与偏差估计:
SampleRTT 是一次报文往返时间样本,EstimatedRTT 是加权平均,DevRTT 描述波动。常用权重为 、。真实实现还受最小/最大 RTO、定时器粒度、指数退避以及 Karn 算法等规则约束。
换句简单的话说,RTO 是在“当前平均往返时间”的基础上,加上“4 倍的波动范围”,这样可以容忍一定的网络抖动,减少误判重传。
校验和
TCP 首部包含一个 校验和(Checksum)字段,用于检测传输中的比特差错。如果接收方检测到校验和错误,就丢弃该报文段;它通常不会发送一个“请重传此段”的专用请求,而是由于累计 ACK 不前进或发送方计时器超时,最终触发重传。
TCP 校验和与 IPv4 首部校验和 都采用反码和思想,但校验范围不同。TCP 校验还覆盖由源/目的 IP、协议号和 TCP 长度组成的伪首部、TCP 首部及数据;IPv4 首部校验和只覆盖 IPv4 首部,IPv6 基本首部没有首部校验和字段。
因此,TCP 校验和不仅检查 TCP 首部和载荷,还能通过伪首部发现部分端点地址或协议号投递错误。校验通过只表示“未检测到差错”,不能证明绝对无错。
拥塞控制
拥塞控制是 TCP 根据网络反馈限制在途数据量和发送速率,避免向网络注入过多数据而造成路由器队列溢出、时延升高和丢包。它主要由发送方维护 cwnd,与接收方用 rwnd 实现的流量控制不同。
一段链路上可以同时存在许多 TCP 连接。经典 TCP 通过“加性增大、乘性减小”等机制在吞吐量、稳定性与连接间公平性之间折中;它并不能保证任意环境下达到严格的全局最优。当 TCP 从超时、重复 ACK 或显式拥塞通知中推断拥塞时,会相应调整发送速率。
慢开始
慢开始 是 TCP 连接开始时的一个阶段,相较于直接以较高的速率发送数据,慢开始会以一个较低的速率开始,然后 逐步试探 当前网络传输的能力,以指数的速率增加发送速率。慢开始的流程如下:
- 初始化:408 经典模型常令
cwnd = 1 MSS;现代实现的初始窗口可能更大,若题目给出初值则以题设为准。 - 指数增长:在“每个报文段产生一个 ACK”的简化模型中,每确认一个新报文段,
cwnd增加约 1 MSS,所以一个 RTT 内收到一整个窗口的确认后,cwnd约翻倍。指数增长是 按 RTT 观察的结果,不是每收到一个 ACK 就翻倍。 - 转换阈值:当
cwnd达到ssthresh(慢开始阈值)时,TCP 会从慢开始模式转换到拥塞避免模式。
按段确认的简化写法为 ;它表示每收到一个确认新数据的 ACK,窗口增加约 1 MSS。
拥塞窗口指数增长与 ACK 的关系
若把 cwnd 的单位写成 MSS,简化模型中发送方每收到一个确认新数据的 ACK,执行 cwnd += 1。因为窗口越大,一个 RTT 内收到的 ACK 越多,所以在理想情况下每经过一个 RTT,cwnd 近似翻倍。
这并不是说在慢开始阶段,接收到一个确认,拥塞窗口直接指数增长,这是很多很多容易理解错误的一个点。
什么是 MSS?
MSS 是 Maximum Segment Size 的简称,即 TCP 报文段中数据部分的最大长度。常见的 1460 B 来自以太网 MTU 1500 B 减去无选项 IPv4 首部 20 B 和 TCP 首部 20 B;若使用 IPv6、TCP 选项或其他链路 MTU,数值会不同。
拥塞避免
当 TCP 进入拥塞避免(Congestion Avoidance)阶段后,拥塞窗口(cwnd)的增长方式从慢启动的指数增长改为 线性增长。
若 cwnd 以 MSS 为单位,经典简化模型中每收到一个确认新数据的 ACK,执行
一个 RTT 内约收到 cwnd 个 ACK,因此合计增长约 1 MSS,表现为线性增长。若以字节为单位,常写成每个 ACK 增加约 字节。
在以 MSS 为单位的 408 简化模型中:慢开始每确认一个段近似
cwnd += 1;拥塞避免每个 ACK 近似cwnd += 1/cwnd。
TCP 通过两种方式 检测网络是否出现拥塞:
- 超时(Timeout):发送的数据在预期的时间内没有收到相应的 ACK,说明该段可能已经丢失。
- 三个冗余 ACK(Triple Duplicate ACK):连续收到三个冗余的 ACK,表明网络中有数据段在传输过程中被丢弃,但后续的 ACK 仍然能够到达。
三个冗余的 ACK 的准确理解
TCP 规范里说的 “收到 3 个冗余 ACK” 的意思是:
- 假设某个包序号 N 丢失了,接收方 已经收到 N+1、N+2 等包,它会连续发送确认号为 N 的 ACK(重复 ACK)。
- 第一次收到 ACK=N 时,它是 正常 ACK(不是冗余的)。
- 接下来连续收到 3 个相同的 ACK=N,这 3 个就是所谓的 冗余 ACK。
- 当发送方收到这 3 个冗余 ACK 后,就触发快速重传,重发包 N。
所以 总共收到 4 个 ACK=N 才会触发快速重传:
- 第 1 个 ACK=N → 正常 ACK
- 第 2 个 ACK=N → 冗余 ACK 1
- 第 3 个 ACK=N → 冗余 ACK 2
- 第 4 个 ACK=N → 冗余 ACK 3 → 触发快速重传
两种拥塞信号的处理不能混为一谈(以下采用 408 常见的 TCP Reno 模型):
- 发生 RTO 超时:把
ssthresh设为丢失前窗口的一半,并把cwnd降到 1 MSS,重新进入慢开始; - 收到 3 个重复 ACK:执行快速重传,并进入快速恢复,不立即把
cwnd降到 1 MSS。具体窗口变化见下节。
通过上述线性增长与灵活的拥塞检测/恢复机制,TCP 能够在保持高吞吐量的同时,尽可能避免网络过载,从而实现可靠且高效的端到端传输。
快速重传与快速恢复
在传统的 TCP 实现中,重传计时器负责触发超时重传:只要计时器到期而仍未收到对应的数据段的 ACK,发送方就会重新发送该段。在高带宽‑低时延的网络环境下,这种“等计时器”的方式往往效率低下,因为计时器的超时时间往往远大于实际的往返时延(RTT),导致数据恢复延迟。
在 快速重传(Fast Retransmission),当发送方 连续收到三个冗余的 ACK 时,说明网络中已经有一个 数据段丢失,而后续的 ACK 只能确认已经成功到达的后续段。此时,TCP 就会 立即重传 那个被认定为丢失的段,而不必等到重传计时器超时,这一过程被称为快速重传。
sequenceDiagram
participant S as 发送方 (Sender)
participant R as 接收方 (Receiver)
Note over S,R: 假设包 N 丢失
S->>R: 发送包 N-1
S->>R: 发送包 N (丢失)
S->>R: 发送包 N+1
S->>R: 发送包 N+2
S->>R: 发送包 N+3
Note over R: 包 N 丢失,但已收到 N+1, N+2, N+3
R->>S: ACK N (第一次收到 ACK N)
R->>S: ACK N (冗余 ACK 1)
R->>S: ACK N (冗余 ACK 2)
R->>S: ACK N (冗余 ACK 3 -> 触发快速重传)
Note over S: 收到第 3 个冗余 ACK -> 快速重传包 N
S->>R: 重传包 N
触发快速重传后,TCP 会切换到 快速恢复
(Fast Recovery)阶段。该阶段的核心目标是:
- 防止拥塞窗口(cwnd)骤降到 1 MSS,从而避免吞吐量剧烈下降;
- 利用已经收到的重复 ACK 来“填补”因丢包而空出的窗口,保持管道尽可能饱和。
在快速重传和快速恢复中,cwnd 的变化过程 如下:
慢开始阈值减半可写为 。
- 快速重传触发后
ssthresh ← cwnd / 2cwnd ← ssthresh + 3·MSS
- 快速恢复期间(每收到一个重复 ACK)
cwnd ← cwnd + MSS- 这种线性增长相当于“用已经确认的 ACK 为窗口“买票”,使得发送方能够继续发送新数据,而不会因为单个丢包而立刻停滞。
- 退出快速恢复(收到一个新的、非重复的 ACK)
- 该 ACK 表明丢失的数据段已经被成功重传并得到确认。
cwnd ← ssthresh(即把窗口恢复到慢启动阈值)- 随后进入 拥塞避免(Congestion Avoidance)阶段,cwnd 以后将以 每 RTT 增加 1 MSS 的速度线性增长。
总结:
- 当
cwnd < ssthresh时,使用 慢开始 算法,cwnd以指数增长 - 当
cwnd >= ssthresh时,使用 拥塞避免 算法,cwnd线性增长 - 若发生 RTO 超时,设置
ssthresh为原窗口的一半、cwnd = 1 MSS,进入慢开始; - 若收到 3 个重复 ACK,则不等 RTO,立即快速重传;Reno 设置
ssthresh为原窗口的一半并进入快速恢复,收到确认重传成功的新 ACK 后令cwnd = ssthresh,转入拥塞避免。
不同 TCP 拥塞控制算法(Tahoe、Reno、NewReno、CUBIC、BBR 等)的细节并不完全相同。408 题若未特别说明,通常按教材给出的 Tahoe/Reno 简化曲线计算;务必先看题设是否包含快速恢复。
下面的交互把 cwnd、ssthresh、ACK 和丢包事件按 RTT 逐步推进;静态曲线只作结果底稿。
TCP Reno 拥塞窗口逐轮变化
按 408 常见简化模型,从 cwnd=1 MSS、ssthresh=8 MSS 开始,逐步观察慢开始、拥塞避免、三个重复 ACK、快速恢复和 RTO 超时。
当前查看:初始窗口。采用教材模型:cwnd=1 MSS,ssthresh=8 MSS,处于慢开始。