鱼宵头像

21 TCP 协议深化:三次握手、四次挥手、拥塞控制

系列导航|全 104 篇目录|上一篇:20 嵌入式系统架构|下一篇:22 路由与交换

鱼宵|系列「软考架构师实战精通营(教材版)」
本篇考点权重:高频(依据近 5 年真题分布统计,供参考)

1. 本篇目标

这篇把 TCP 从"可靠传输"这四个字拆成能画、能算、能答题的三块:连接怎么建(三次握手)、怎么拆(四次挥手)、拆不动的时候怎么退让(拥塞控制)。学完你能默写两条状态链、能算握手报文里的序号与确认号、能按 RTT 轮次把 cwnd 一路推下来,也能说清 TIME_WAIT 到底在等什么、为什么不能省。这一篇是卷 1 网络部分的分水岭——后面的路由、QoS、防火墙,判断依据全在 TCP 的行为上。

2. 对应教材

清华社《系统架构设计师教程(第 2 版)》第 2 章 计算机网络(2.4 网络协议,TCP 与 UDP 部分)与第 17 章 网络架构设计(17.1 网络设计基础、17.2 网络设计方法与演进;具体小节号与页码待核实)。官方大纲对应条目:TCP/IP 协议族、传输层协议、可靠传输与流量控制、拥塞控制机制(以 ruankao.org.cn 发布的大纲为准)。

综合题里 TCP 每年稳定出 1~2 道,题型集中在三种:握手/挥手的序号计算、TIME_WAIT 与状态判断、拥塞窗口的数值推演。案例题不直接考 TCP 细节,但"连接数打满"“TIME_WAIT 堆积”"重传率高"这类故障描述,判断依据全在本篇。

3. 零基础开胃课

先想一件事:你在电话里说"喂,听得到吗",对方回"听得到,你呢",你说"我也听得到"——这通电话才算接通。

拆开看这三句在干什么:

  • 你说"喂"——我在,你能收到我的声音吗;
  • 对方说"听得到,你呢"——我能收到你,同时我在,你能收到我吗;
  • 你说"我也听得到"——我能收到你,双向通了。

三句话,两个方向各确认一次,缺一次就不敢说"通了"。TCP 的第一次和第三次握手,就是把这两句"你听得到吗"用报文说出来。

再想第二件事:为什么挂电话要说四句?

你:“我说完了,挂了啊”(FIN)
对方:“知道了”(ACK)——但他手上可能还有半句话没说,不能一起挂
对方:“我也说完了,挂了啊”(FIN)
你:“知道了”(ACK)

"我要挂"和"我知道你要挂"是两件事,"你要挂"和"我也要挂"也是两件事——所以四次。TCP 的挥手就是这四句。

第三个类比讲拥塞:早高峰上高架,你踩油门(cwnd 增大)跑得越来越快,直到前面堵死(丢包)——这时候只有两个选择:要么老实退回起点重新排(超时重传,cwnd 归 1),要么从最近的出口绕一下、速度减半但不停车(快重传快恢复)。拥塞控制就是这套"踩油门—撞墙—减速—再踩"的规则,只不过它每秒要做几万次。

这三个类比记住,后面所有状态名和公式都是它们的细节化。

4. 概念讲解

4.1 TCP 在 IP 之上补了什么

定义:TCP(Transmission Control Protocol,传输控制协议)是面向连接的、可靠的、基于字节流的全双工传输层协议,为应用层提供"像管道一样连续、不丢不乱"的数据通道。

IP 层只负责"尽力而为"地把一个个包送出去,不保证顺序、不保证到达、不保证不重复。TCP 在它上面补了四件事:

补的能力靠什么实现一句话
面向连接三次握手建立、四次挥手释放先建管道,再灌水
可靠传输序号 + 确认 + 超时重传 + 校验和丢了我再发一遍
有序交付32 位序号,按序重组到得乱,装得齐
流量与拥塞控制滑动窗口 + 拥塞窗口别把对方和我都灌爆

关键区别(易错):TCP 是"字节流"不是"报文流"。应用层写三次 send,接收端可能一次 recv 拿到全部——TCP 不保留消息边界,边界要应用层自己划(HTTP 用 Content-Length 或分块,就是这个原因)。

你该带走什么:TCP 的全部机制都在回答一个问题——“包会丢、会乱、会重复,怎么让上层看起来像一根不漏的管子”。

4.2 首部里哪几个字段必须记

TCP 首部固定 20 字节(选项可扩展到 60 字节),关键字段如下:

字段长度作用考试怎么用
源端口 / 目的端口各 16 位定位两端进程四元组的一半
序号 seq32 位本报文第一个字节的编号计算题必用
确认号 ack32 位期望收到的下一个字节编号等于已收字节数 + 1
数据偏移4 位首部长度(单位 4 字节)20 字节 → 值 5
六个标志位各 1 位URG/ACK/PSH/RST/SYN/FIN判断报文类型
窗口16 位接收窗口,告诉对方"我还能收多少"流量控制
校验和16 位覆盖首部 + 数据 + 伪首部出错即丢

六个标志位是必背的:

  • URG:紧急指针有效(很少用);
  • ACK:确认号有效(连接建立后几乎所有报文都置 1);
  • PSH:催促接收端尽快上交应用层;
  • RST:复位连接(“这连接我不要了”,用于拒绝或异常中止);
  • SYN:同步序号,建连接用;
  • FIN:发送方没有数据要发了,请求关闭。

两个易考点:

  1. SYN 和 FIN 各占一个序号,纯 ACK 不占序号。这是握手/挥手序号计算题的根子。
  2. ack 号是"期望下一个",不是"已收到最后一个"。收到 seq=1000 且长度 1 的 SYN,回 ack=1001。

你该带走什么:seq 说"我这个字节是第几号",ack 说"我下一个想收第几号",两者差 1 就是收了一个字节。

4.3 三次握手:三条报文,四个状态

设客户端初始序号 ISN = x,服务端 ISN = y。

步方向报文发送方进入接收方进入
①C → SSYN=1, seq=xSYN_SENTSYN_RCVD
②S → CSYN=1, ACK=1, seq=y, ack=x+1SYN_RCVDESTABLISHED
③C → SACK=1, seq=x+1, ack=y+1ESTABLISHEDESTABLISHED

逐条读:

  • ① 客户端说"我想连你,我的起始编号是 x"。这一个 SYN 占掉序号 x,所以客户端下次要发数据得从 x+1 开始。
  • ② 服务端回"我同意,我的起始编号是 y,我期待你从 x+1 开始发"。这个 SYN 占掉序号 y。
  • ③ 客户端说"收到,我期待你从 y+1 开始发"。这条纯 ACK 不占序号,所以客户端下一个数据字节仍是 x+1。

四个状态的变化顺序:

客户端:CLOSED → SYN_SENT →(收到 ② 后)ESTABLISHED
服务端:CLOSED → LISTEN →(收到 ① 后)SYN_RCVD →(收到 ③ 后)ESTABLISHED

半连接队列与全连接队列(高频细节):

服务端收到 ① 后,把这条"半成品连接"放进 SYN 队列(半连接队列);收到 ③ 后移到 ACCEPT 队列(全连接队列),等着应用层 accept() 取走。两个队列各有上限(Linux 的 tcp_max_syn_backlog 与 somaxconn)。

SYN Flood 攻击就是打第一个队列:攻击者狂发 ① 但从不回 ③,SYN 队列被占满,正常用户的 ① 也进不来——服务端看起来"还活着"但谁也连不上。防御手段(SYN Cookie、缩短 SYN_RCVD 超时、加大队列)都在这个点上做文章。

你该带走什么:三条报文、四个状态、两个队列。考"第三个报文序号是多少",答案永远是 seq=x+1, ack=y+1。

4.4 为什么是三次,不是两次也不是四次

两次不够:客户端发 SYN,服务端回 SYN+ACK,如果到此为止——

  • 服务端以为"连接已建立",开始等数据;
  • 但它无法确认客户端收到了自己的 SYN+ACK。如果这条 ② 在路上丢了,客户端还在 SYN_SENT,服务端却在等数据,双方状态不一致,服务端会一直挂着一个空连接。

四次多余:服务端的 SYN 和 ACK 可以合在一条报文里发(② 就是 SYN+ACK),因为"我同意"和"我的起始序号是多少"没有先后依赖,合并不会造成歧义。挥手之所以不能合并,是因为 FIN 之后服务端可能还有数据要发,ACK 必须先回。

一句话记住:握手三次是为了双向确认(我能发你、你能发我、你收到了我的确认);挥手四次是因为"关"这件事双方各要独立说一次,中间还隔着"未发完的数据"。

一个必须知道的例外:第三个 ACK 可以携带数据。规范上允许,实践中 Linux 默认不这么干(避免被 SYN Flood 变种利用)。考试问"第三次握手能不能带数据",答"能,但通常不带"。

4.5 四次挥手与状态机

设客户端(主动关闭方)FIN 序号 = m,服务端 FIN 序号 = n。

步方向报文主动关闭方进入被动关闭方进入
①C → SFIN=1, seq=mFIN_WAIT_1CLOSE_WAIT
②S → CACK=1, ack=m+1FIN_WAIT_2CLOSE_WAIT
③S → CFIN=1, ACK=1, seq=n, ack=m+1TIME_WAITLAST_ACK
④C → SACK=1, seq=m+1, ack=n+1TIME_WAIT(2MSL 后 CLOSED)CLOSED

状态链(默写级):

主动关闭方:ESTABLISHED → FIN_WAIT_1 → FIN_WAIT_2 → TIME_WAIT →(2MSL)→ CLOSED
被动关闭方:ESTABLISHED → CLOSE_WAIT → LAST_ACK → CLOSED

两个常被考到的中间状态:

  • CLOSE_WAIT:收到对方的 FIN、回了 ACK,但自己还有数据没发完。这个状态堆积说明应用层没调 close()——代码里漏关连接的经典表现,netstat 里成千上万个 CLOSE_WAIT 就是它。
  • FIN_WAIT_2:主动方已经发了 FIN、收到了 ACK,正等服务端的 FIN。如果对端迟迟不发,会由 tcp_fin_timeout 兜底回收。

同时关闭(少考但要知):双方几乎同时发 FIN,会走 CLOSING 状态,而不是 FIN_WAIT_2。这时双方都等对方的 ACK,收到后进 TIME_WAIT。

你该带走什么:挥手的状态链比握手长,考试爱考"某一步之后双方各是什么状态",把两条链背成顺口溜最快。

4.6 TIME_WAIT 与 2MSL 到底在等什么

主动关闭方发完最后一个 ACK,不直接进 CLOSED,而是进 TIME_WAIT,等 2MSL(Maximum Segment Lifetime,报文最大生存时间)才真正关闭。Linux 上 MSL 取 30 秒,2MSL 就是 60 秒。

两个理由,都要能说:

  1. 保证最后一个 ACK 能送到。如果 ④ 丢了,服务端会重发 ③(FIN)。此时客户端还在 TIME_WAIT,能再回一次 ACK;如果已 CLOSED,收到 FIN 只能回 RST,服务端会认为"连接异常终止"。
  2. 让旧连接的残包在网络中消散。同一个四元组(源 IP、源端口、目的 IP、目的端口)在 2MSL 内不会被新连接复用,避免上一个连接的延迟包被新连接误收。

工程上的两面:

  • 主动关闭方(比如高并发的 Web 服务器)会积压大量 TIME_WAIT,占端口、占内存;
  • 被动关闭方不产生 TIME_WAIT,而是产生 CLOSE_WAIT 堆积(那是代码问题)。

常用调优(知道即可,不考):tcp_tw_reuse(复用到新连接)、缩短 tcp_fin_timeout、改用长连接。不要用 tcp_tw_recycle——它在 NAT 环境下会造成大量连接失败,已被内核移除。

你该带走什么:TIME_WAIT 不是"等对方",是"等自己的 ACK 生效 + 等旧包死掉",两个理由都要写得出来。

4.7 可靠传输:序号、确认、重传

TCP 保证可靠靠四件套:

  1. 序号:每个字节一个编号,接收端按号拼装;
  2. 确认:ack=N 表示"N-1 及之前全收到,我下一个要 N"(累计确认);
  3. 超时重传:发出去没在规定时间内收到确认,重发;
  4. 校验和:首部 + 数据 + 伪首部一起算,出错直接丢,等重传。

累计确认的双刃剑:一次 ack 可以确认一大段,效率高;但如果中间丢了一个包,后面的确认全部重复同一个 ack——这正是"3 个重复 ACK"的来源,也是快重传的触发条件。

超时时间 RTO 怎么定:不能写死。RTO 由 RTT 的测量值动态算(Jacobson 算法):

  • 采样每次往返时间 RTT;
  • 维护平滑 RTT(SRTT)和 RTT 偏差(RTTVAR);
  • RTO = SRTT + 4 × RTTVAR,并有下限(Linux 200ms)。

为什么要 4 倍偏差:网络时延抖动大的链路(无线、跨洋),只按平均值算必然频繁误判超时,4 倍偏差是给抖动留余量。RFC 6298 就是这么定的。

快速重传:不等超时。收到 3 个重复 ACK 就认为"那一个包八成丢了",立刻重传,不用等 RTO 到点。这就是 4.10 要讲的"快重传"。

你该带走什么:可靠 = 序号定位 + 累计确认 + 超时重传 + 校验和;RTO 是算出来的,不是拍出来的。

4.8 流量控制:别把接收方灌爆

流量控制解决的是"发送方太快,接收方缓冲区装不下"。做法是滑动窗口:

  • 接收方在每个 ACK 里带一个窗口字段 rwnd,告诉发送方"我还能收多少字节";
  • 发送方的"已发送未确认"数据量不得超过 rwnd;
  • 收到新 ACK,窗口右移,才能继续发。

零窗口:接收方缓冲区满,rwnd=0。发送方停发,但会定期发零窗口探测(window probe),防止"窗口更新报文丢了,双方僵死"。

糊涂窗口综合征(Silly Window Syndrome):接收方每次只腾出几个字节就通告一个小窗口,发送方就发一个很小的包,首部开销远大于数据,效率极低。两个解法:

  • 接收方:窗口小于 MSS 时不通告,等攒够再报(受延迟确认影响);
  • 发送方:Nagle 算法,小数据攒够一个 MSS 或等到 ACK 再发。

流量控制 vs 拥塞控制(最容易混的一对):

流量控制拥塞控制
保护谁接收方缓冲区整个网络
依据接收方通告的 rwnd发送方推测的 cwnd
手段滑动窗口慢启动、拥塞避免、快重传快恢复
实际发送量min(rwnd, cwnd)同左

你该带走什么:rwnd 管"对方装不装得下",cwnd 管"网络扛不扛得住",实际窗口取两者较小值。

4.9 拥塞控制四阶段

TCP 拥塞控制有四个阶段,按发生顺序串成一条曲线:

① 慢启动(Slow Start)

  • 初始 cwnd = 1 MSS(Linux 现代实现初始 10 MSS,教材口径按 1 讲);
  • 每收到一个 ACK,cwnd 加 1 MSS——效果是每经过一个 RTT,cwnd 翻倍(1→2→4→8→16);
  • 涨到 ssthresh(慢启动门限) 就停,转拥塞避免。

“慢启动"这个名字是历史遗留:它其实涨得最快,叫"慢"是因为起点只有 1 个 MSS,比旧实现一上来就发一大窗要"慢”。

② 拥塞避免(Congestion Avoidance)

  • 每个 RTT 只加 1 MSS(线性增长,1→2→…的斜率变成 +1/轮);
  • 探到网络容量上限,直到丢包。

③ 超时 → 回到慢启动

一旦发生超时重传(说明网络严重拥塞):

  • ssthresh = max(cwnd/2, 2);
  • cwnd = 1,重新慢启动。

这是"重罚":一次超时就把窗口砍回起点,因为超时意味着丢包严重、链路可能已经堵死。

④ 3 个重复 ACK → 快重传 + 快恢复

收到 3 个重复 ACK,说明"只是丢了一个包,后面还在流动"(不是全面拥塞),于是轻罚:

  • 快重传:立刻重传丢失的那个报文,不等 RTO;
  • 快恢复:ssthresh = cwnd/2,cwnd = ssthresh + 3(3 是已收到的重复 ACK 数),然后直接进入拥塞避免,不回慢启动。

为什么 +3:那 3 个重复 ACK 说明有 3 个包已经离开了网络、到了接收方,网络上实际少在途 3 个,所以窗口可以补回 3。

四个阶段的判定口诀:

  • 窗口翻倍 → 慢启动;
  • 窗口 +1/轮 → 拥塞避免;
  • 窗口归 1 → 超时;
  • 窗口减半再 +3 → 快恢复。

你该带走什么:两种丢包两种罚法。超时是"重罚回起点",3 个重复 ACK 是"轻罚减半"。

4.10 一段 cwnd 数值推演(考试原题型)

给定:初始 cwnd = 1 MSS,初始 ssthresh = 16 MSS,按 RTT 轮次推进。

轮次阶段cwnd
0慢启动1
1慢启动2
2慢启动4
3慢启动8
4慢启动(到门限)16
5拥塞避免17
…每轮 +1…
12拥塞避免24
12 末超时ssthresh = 12, cwnd = 1
13慢启动1
14慢启动2
15慢启动4
16慢启动8
17慢启动(到新门限)12
18拥塞避免13
…每轮 +1…
22拥塞避免17
22 末3 个重复 ACKssthresh = 8,cwnd 置为 11
23拥塞避免首轮11
24拥塞避免12
25拥塞避免13

关键节点核算:

  • 第 4 轮 cwnd=16,正好等于 ssthresh,从下一轮开始线性("到门限"这一轮仍算慢启动);
  • 超时发生在 cwnd=24:ssthresh = 24/2 = 12,cwnd = 1;
  • 第 17 轮 cwnd 从 8 翻倍到 16 会超过 12,所以取 12(不能越门限);
  • 22 轮 cwnd=17 收到 3 个重复 ACK:ssthresh = 17/2 ≈ 8(向下取整),cwnd = 8 + 3 = 11。

做题的三个坑:

  1. ssthresh 减半后要向下取整,17/2 写 8 不写 8.5;
  2. cwnd 到门限那一轮不越界,8 翻倍成 16 超过 12 时要截到 12;
  3. 快恢复不回慢启动,cwnd 从 11 继续线性,不要从 1 重来。

你该带走什么:这类题只要把"翻倍 / +1 / 归 1 / 减半+3"四个动作贴在轮次上,画一条折线就不会错。

4.11 TCP 与 UDP 怎么选

维度TCPUDP
连接面向连接(握手挥手)无连接
可靠性确认重传,保证不丢不乱尽力而为,可能丢可能乱
传输单位字节流数据报(保留边界)
首部20 字节起8 字节
流量/拥塞控制有无
广播/组播不支持支持
时延特性重传会带来抖动时延稳定但可能丢
典型应用HTTP、FTP、SSH、数据库连接DNS、视频直播、VoIP、游戏、MQTT over UDP 变体

选择逻辑(一句话):

  • 不能丢、能容忍延迟 → TCP(文件、网页、支付);
  • 不能等、能容忍丢一点 → UDP(直播、语音、实时游戏);
  • 要在 UDP 上要可靠性 → QUIC(HTTP/3)自己实现重传与拥塞控制,绕开 TCP 的队头阻塞。

"TCP 队头阻塞"值得知道:TCP 按序交付,前面一个包丢了,后面的包即使到了也要在缓冲区等着——这就是 HTTP/2 多路复用被 TCP 拖累的原因,也是 QUIC 改用 UDP 的动机。

你该带走什么:可靠性、时延、开销三者不可兼得,选协议就是选放弃哪一个。

4.12 解题三步法

第一步:认题型。TCP 题只有四类——

题型识别特征用什么
握手序号给 ISN,问第三条报文seq/ack 各加 1
挥手状态问某步之后双方状态两条状态链
TIME_WAIT问为什么等、等多久两个理由 + 2MSL
拥塞窗口给轮次问 cwnd四阶段动作

第二步:套规则。

  • 序号题:SYN/FIN 各占 1,纯 ACK 不占;ack = 对方 seq + 1(带 SYN/FIN)或 + 数据长度。
  • 状态题:主动方 FIN_WAIT_1→FIN_WAIT_2→TIME_WAIT,被动方 CLOSE_WAIT→LAST_ACK。
  • 窗口题:翻倍、+1、归 1、减半+3 四个动作。

第三步:统单位。

  • 序号是字节编号,不是报文编号;
  • cwnd 单位是 MSS,不是字节,也不是报文个数;
  • 时间单位 MSL 是秒,2MSL 别写成 2 秒。

一个标准答题模板(握手序号题):

已知客户端 ISN=x、服务端 ISN=y。第一条 SYN 占 1 个序号,故第二条 ack=x+1;第二条 SYN 占 1 个序号,故第三条 ack=y+1;第三条为纯 ACK 不占序号,故其 seq 仍为 x+1。答案:seq=x+1, ack=y+1。

4.13 陷阱十条

  1. 握手是三次,不是"三次报文四次":第三条可以带数据,但不是必须带。
  2. ack 号是期望值,不是已收值:收到 seq=1000 的 SYN,回 1001。
  3. SYN 和 FIN 都占序号,纯 ACK 不占。
  4. TIME_WAIT 只出现在主动关闭方,被动关闭方是 CLOSE_WAIT。
  5. CLOSE_WAIT 堆积是应用没关连接,不是内核参数问题。
  6. 2MSL 有两个作用,只答"等 ACK 重传"要扣分。
  7. rwnd 和 cwnd 不是一回事,实际发送窗口取两者较小值。
  8. 慢启动是翻倍,拥塞避免是 +1,别把名字当速度。
  9. 超时回 cwnd=1,快恢复回 ssthresh+3,两种罚法不能混。
  10. ssthresh 减半向下取整,17/2 是 8。

5. 演示:三道手算

演示 1:握手报文的序号与确认号

题目:客户端 ISN = 1000,服务端 ISN = 2000,握手三报文的 seq 与 ack 各是多少?

第一步,写第一条:客户端发 SYN,seq = 1000(自己的 ISN)。这个 SYN 占 1 个序号。

第二步,写第二条:服务端回 SYN+ACK,seq = 2000(自己的 ISN),ack = 1000 + 1 = 1001(期待客户端下一个字节)。

第三步,写第三条:客户端回 ACK,seq = 1000 + 1 = 1001(自己 SYN 之后的下一个),ack = 2000 + 1 = 2001。

答案:①(seq=1000)②(seq=2000, ack=1001)③(seq=1001, ack=2001)。

自检:三条报文的 ack 都是"对方 ISN + 1",seq 是"自己 ISN + 已占用序号数"。只要记住 SYN 占 1,就不会写成 ack=1000。

演示 2:一次超时之后 cwnd 怎么走

题目:初始 cwnd = 1 MSS,ssthresh = 16 MSS。第 4 轮 cwnd 到 16,之后线性增长,到第 12 轮 cwnd = 24 时发生超时。问第 12 轮之后 ssthresh 与 cwnd 各是多少,第 17 轮 cwnd 是多少?

第一步,算减半:ssthresh = cwnd/2 = 24/2 = 12 MSS。

第二步,算归零:cwnd = 1 MSS(超时重罚,回慢启动)。

第三步,往后推:

轮次1314151617
cwnd124812

第 16 轮 cwnd=8,下一轮翻倍应为 16,但 ssthresh=12,取 12 不越界。

答案:ssthresh = 12 MSS,cwnd = 1 MSS,第 17 轮 cwnd = 12 MSS。

易错点:有人算第 17 轮为 16(直接翻倍),错在忘了门限截断。慢启动涨到门限就停,不能越过。

演示 3:快恢复的 +3 从哪来

题目:第 22 轮 cwnd = 17 MSS,此时收到 3 个重复 ACK。问 ssthresh、cwnd 各是多少,第 23、24 轮 cwnd 是多少?

第一步,减半:ssthresh = 17/2 = 8.5 → 8 MSS(向下取整)。

第二步,加 3:cwnd = 8 + 3 = 11 MSS。

第三步,定下一轮:快恢复当下 cwnd 就是 11,所以第 23 轮 cwnd = 11 MSS;随后进拥塞避免,每轮 +1,第 24 轮 = 12 MSS,第 25 轮 = 13 MSS。

为什么不是回 1:3 个重复 ACK 说明后面还有包在流动,网络没有全面堵死,只丢了一个包。轻罚即可,所以走快恢复而不是慢启动。

对比记忆:

触发ssthreshcwnd下一步
超时cwnd/21慢启动
3 个重复 ACKcwnd/2ssthresh+3拥塞避免

6. 真题演练(3 题)

第 1 题(自编,题型对照综合题)

TCP 建立连接时,客户端发送 SYN 报文(seq=2000),服务端回复 SYN+ACK 报文(seq=5000)。客户端随后发送的第三个报文段中,序号和确认号分别是( )。

A. seq=2000, ack=5000
B. seq=2001, ack=5001
C. seq=5000, ack=2001
D. seq=2001, ack=5000

答案:B

解析:客户端的 SYN 占 1 个序号,故第三个报文 seq = 2000 + 1 = 2001;服务端的 SYN 也占 1 个序号,故 ack = 5000 + 1 = 5001。A 忘了加 1,C 把两端序号写反,D 只给客户端加了 1、忘了给服务端加。

回指:4.3 三次握手。

第 2 题(自编,题型对照综合题)

关于 TCP 的 TIME_WAIT 状态,下列说法错误的是( )。

A. 只有主动关闭连接的一方才会进入 TIME_WAIT
B. TIME_WAIT 持续 2MSL 后自动进入 CLOSED
C. TIME_WAIT 的作用之一是让旧连接的残余报文在网络中消失
D. TIME_WAIT 是为了等待服务端把剩余数据发完

答案:D

解析:等剩余数据发完是 CLOSE_WAIT 阶段的事——服务端收到 FIN、回 ACK 后进 CLOSE_WAIT,此时它还在发数据,发完才补 FIN。TIME_WAIT 的两个作用是:保证最后一个 ACK 有重传机会、让旧连接的残包消散。A、B、C 都正确。

回指:4.6 TIME_WAIT 与 2MSL。

第 3 题(自编,题型对照综合题)

某 TCP 连接当前 cwnd = 20 MSS,ssthresh = 20 MSS。若此时发生超时重传,则 ssthresh 和 cwnd 分别变为( )。

A. 10 MSS、1 MSS
B. 10 MSS、10 MSS
C. 20 MSS、1 MSS
D. 10 MSS、13 MSS

答案:A

解析:超时是重罚:ssthresh = cwnd/2 = 10,cwnd = 1,回到慢启动。B 是"减半但窗口不变",混淆了快恢复;C 忘了减半;D 的 +3 是快恢复的算法(ssthresh+3 = 13),不是超时。

回指:4.9 拥塞控制四阶段、演示 2。

7. 图解

三张图:先看连接怎么建、怎么拆;再看窗口怎么涨、怎么掉;最后一张把节点逐个标在 cwnd 折线上,把两种罚法的账当面算清——超时归 1、三个重复 ACK 减半再 +3。

图 1 三次握手与四次挥手的状态变化

图 1:左侧三次握手(SYN / SYN+ACK / ACK,客户端 SYN_SENT→ESTABLISHED,服务端 SYN_RCVD→ESTABLISHED),右侧四次挥手(FIN / ACK / FIN / ACK,主动方 FIN_WAIT_1→FIN_WAIT_2→TIME_WAIT,被动方 CLOSE_WAIT→LAST_ACK→CLOSED)——先竖着看一条生命线,再横着看每一对箭头改了什么状态。

读法:先只看左边客户端那条竖线,从上往下走——出发时是 CLOSED(图里没画,从 SYN_SENT 起),发完 ① 变 SYN_SENT,收到 ② 变 ESTABLISHED。再横着看 ② 这条反向箭头,它同时干了两件事:给客户端确认、把自己的 ISN 报过去,所以服务端在收到 ③ 之前一直是 SYN_RCVD。右边同理,但要盯住 TIME_WAIT 在 ③ 之后就出现了,不是等 ④ 发完才出现——这是状态题最爱设的陷阱。金框里两条考点分别是半连接队列和 2MSL,都属于"知道就能答、不知道就蒙"的题。

再看窗口怎么涨、怎么掉。这张图是一条 cwnd 随 RTT 变化的曲线,五个标注对应四个阶段加一次重罚,三条浅灰横线是三次 ssthresh 的取值。

图 2 拥塞控制的 cwnd 曲线与四阶段

图 2:横轴 RTT 轮次、纵轴 cwnd(MSS),蓝线慢启动(每轮翻倍)、绿线拥塞避免(每轮 +1)、红线超时(cwnd 归 1)、金线快恢复(减半后 +3);三条浅线标出 ssthresh 从 16 到 12 再到 8 的三次变化。

读法:只盯曲线的斜率,别看具体数值。斜率陡(近乎竖直)的是慢启动,斜率平(约 45 度以下)的是拥塞避免;出现竖直下跌就是出事——跌到底(cwnd=1)是超时,跌一半(并抬高 3 格)是快恢复。再对照三条灰线:每次下跌,ssthresh 就跟着降一档,而且新的 ssthresh 总是落在下跌前的 cwnd 一半处。做题时先在草稿上画这三条横线,再把轮次一格一格填,比纯背公式稳得多。

cwnd 折线上的四阶段与两种罚法

图 3:一条 25 轮的 cwnd 折线,绿段慢启动(每轮翻倍)、蓝段拥塞避免(每轮 +1)、红竖线超时(ssthresh 取半、cwnd 归 1)、金段快恢复(ssthresh=8、cwnd=8+3=11 之后继续 +1);右侧四张卡写清四阶段的触发条件与动作。

读法:把这张图当草稿纸用。先认颜色,再认竖线:绿段陡、蓝段平、红竖线跌到底、金竖线只跌一半。然后按轮次报数——第 4 轮到门限 16 转拥塞避免,一路加到第 12 轮的 24 触发超时,门限砍到 12、cwnd 回 1;再到第 22 轮的 17 收到三个重复 ACK,门限砍到 8、cwnd 直接给 11,从 11 继续 +1。最容易错的两处:门限减半要向下取整;慢启动那一轮不越界(8 翻倍会超 12,就截在 12)。

8. 自测五题

  1. 握手第三条报文是纯 ACK,它占不占序号?
  2. 被动关闭方在收到 FIN 之后、发出自己的 FIN 之前,处于什么状态?
  3. rwnd = 0 时发送方会怎么办?
  4. 慢启动阶段 cwnd 从 4 涨到 8 用了几个 RTT?
  5. 快恢复时 cwnd = ssthresh + 3,这个 3 代表什么?

参考答案:

  1. 不占。纯 ACK 不消耗序号空间,所以客户端下一个数据字节仍是 x+1。
  2. CLOSE_WAIT。此时它已回 ACK,但可能还有数据要发,发完才发 FIN 进 LAST_ACK。
  3. 停发,但定期发零窗口探测报文,防止窗口更新通知丢失导致双方僵死。
  4. 一个 RTT。慢启动每轮翻倍,4→8 只需一轮。
  5. 代表已收到 3 个重复 ACK,即有 3 个报文已到达接收方、离开了网络,所以在途数据少了 3 个,窗口可以补回 3。

9. 题库(7 题,由易到难)

第 1 题(自编) TCP 首部中,用于表示"期望收到对方下一个字节编号"的字段是( )。

A. 序号
B. 确认号
C. 窗口
D. 校验和

答案:B
解析:序号表示本报文第一个数据字节的编号;确认号表示期望收到的下一个字节编号;窗口表示接收能力;校验和用于检错。
回指:4.2 首部字段。

第 2 题(自编) 三次握手中,服务端收到第一个 SYN 后进入的状态是( )。

A. LISTEN
B. SYN_SENT
C. SYN_RCVD
D. ESTABLISHED

答案:C
解析:LISTEN 是收到 SYN 之前的状态;SYN_SENT 是客户端发出 SYN 后的状态;收到 SYN 并回复 SYN+ACK 后进入 SYN_RCVD,等第三个 ACK 才 ESTABLISHED。
回指:4.3 三次握手。

第 3 题(自编) 某服务器 netstat 显示大量连接处于 CLOSE_WAIT,最可能的原因是( )。

A. 内核参数 somaxconn 太小
B. 遭遇 SYN Flood 攻击
C. 应用层收到对端 FIN 后未调用 close()
D. 网络中存在大量丢包导致重传

答案:C
解析:CLOSE_WAIT 是"已收到 FIN、回了 ACK,但本端还没发 FIN",只有应用不关连接才会停在这里。A 影响 ACCEPT 队列(表现为连接建立慢),B 打的是 SYN 队列(表现为 SYN_RCVD 堆积),D 表现为重传率升高。
回指:4.5 四次挥手与状态机。

第 4 题(自编) 关于 TCP 流量控制与拥塞控制,说法正确的是( )。

A. 流量控制保护的是网络,拥塞控制保护的是接收方
B. 实际发送窗口取 rwnd 与 cwnd 的较大值
C. rwnd 由接收方通告,cwnd 由发送方维护
D. 拥塞控制只在超时的时候起作用

答案:C
解析:A 说反了;B 应取较小值(两者都是上限,谁小按谁);D 错,拥塞控制在慢启动、拥塞避免、快重传快恢复四个阶段持续起作用。
回指:4.8 流量控制、4.9 拥塞控制。

第 5 题(自编) 初始 cwnd = 1 MSS,ssthresh = 8 MSS。第 4 轮 cwnd 是多少?

A. 4 MSS
B. 8 MSS
C. 9 MSS
D. 16 MSS

答案:B
解析:慢启动每轮翻倍:第 1 轮 2、第 2 轮 4、第 3 轮 8,第 4 轮本应 16,但 ssthresh = 8,涨到门限即停并转拥塞避免,故第 4 轮为 8 MSS(第 5 轮为 9)。
回指:4.10 数值推演。

第 6 题(自编) 某连接第 10 轮末 cwnd = 16 MSS 时收到 3 个重复 ACK。若之后不再丢包,第 12 轮末 cwnd 是多少?

A. 11 MSS
B. 12 MSS
C. 13 MSS
D. 16 MSS

答案:B
解析:3 个重复 ACK 触发快恢复:ssthresh = 16/2 = 8,cwnd = 8 + 3 = 11,随后直接进拥塞避免(不回慢启动)。第 11 轮 = 11,第 12 轮 = 12。A 是快恢复当下的瞬时值、漏了第 12 轮的增长;C 多算一轮;D 是没做任何处理的静态值。

回指:4.9、4.10。

第 7 题(自编) 综合题。

一条 TCP 连接:客户端 ISN = 3000,服务端 ISN = 7000。三次握手完成后,客户端发送了 500 字节数据,服务端全部正确接收。此时服务端回复的 ACK 报文中,确认号是多少?若服务端随后发送 200 字节数据,客户端回复的 ACK 确认号是多少?

答案:服务端 ACK 的确认号为 3501;客户端 ACK 的确认号为 7201。

解析:

  1. 握手完成后,客户端下一个可发字节 = ISN + 1 = 3001。
  2. 客户端发 500 字节,占用 3001~3500,故服务端下一个期望是 3501。
  3. 服务端握手后下一个可发字节 = 7001;发 200 字节占 7001~7200,客户端下一个期望 7201。

易错点:忘记握手时 SYN 已占 1 个序号,直接从 3000 算成 3500;或者把"已收 500 字节"算成 ack=3500。

回指:4.2、4.3、4.7。

10. 技术英语(10 词)

英文中文一句话记忆
three-way handshake三次握手three 是重点,不是 two
sequence number序号每个字节的身份证号
acknowledgment确认ack = 我下一个想要几号
window窗口我还能收多少
congestion拥塞网络堵了
slow start慢启动名字叫慢,涨得最快
congestion avoidance拥塞避免线性爬坡
fast retransmit快重传3 个重复 ACK 就重发
fast recovery快恢复减半加三,不回起点
half-open connection半开连接一方已关一方不知

11. 必背 12 条

  1. 握手三条:SYN(seq=x) → SYN+ACK(seq=y, ack=x+1) → ACK(seq=x+1, ack=y+1)。
  2. SYN 与 FIN 各占 1 个序号,纯 ACK 不占。
  3. 服务端状态:LISTEN → SYN_RCVD → ESTABLISHED;客户端:SYN_SENT → ESTABLISHED。
  4. 半连接队列收 SYN,全连接队列等 accept,SYN Flood 打前者。
  5. 挥手四条:FIN(m) → ACK(m+1) → FIN(n) → ACK(n+1)。
  6. 主动方:FIN_WAIT_1 → FIN_WAIT_2 → TIME_WAIT → CLOSED;被动方:CLOSE_WAIT → LAST_ACK → CLOSED。
  7. TIME_WAIT = 2MSL,两个作用:保 ACK 重传、让残包消散。
  8. TIME_WAIT 只在主动关闭方,被动方是 CLOSE_WAIT。
  9. 可靠传输四件套:序号、累计确认、超时重传、校验和。
  10. 实际发送窗口 = min(rwnd, cwnd)。
  11. 慢启动翻倍、拥塞避免 +1、超时归 1(ssthresh 减半)、快恢复减半 +3。
  12. 慢启动涨到 ssthresh 就停,不能越界。

12. 速记表 + 复习计划 + 下篇预告 + 互动提问

速记表

考点一句话口诀
握手三条SYN、SYN+ACK、ACK
首条报文序号客户端 ISN,占 1
第二条 ack客户端 ISN + 1
第三条 seq客户端 ISN + 1
第三条 ack服务端 ISN + 1
握手为何三次双向确认,两次不够
挥手为何四次FIN 与 ACK 不能合并
主动方状态1→2→TIME_WAIT→CLOSED
被动方状态CLOSE_WAIT→LAST_ACK→CLOSED
2MSL 两作用保 ACK、散残包
CLOSE_WAIT 多应用没关连接
TIME_WAIT 多短连接太密
可靠四件套序号、确认、重传、校验
窗口取值取 rwnd 与 cwnd 较小
慢启动每轮翻倍
拥塞避免每轮 +1
超时cwnd=1,ssthresh 减半
快恢复cwnd=ssthresh+3
ssthresh 取整向下取整

⚠️ 使用说明:这张表里最容易错的是"第三条 ack"和"快恢复 +3"。前者是因为服务端的 SYN 也占一个序号,后者是因为 3 个重复 ACK 表示 3 个包已离开网络。两个都要能讲出"为什么",光背结论在案例题里没用。

复习计划(高频篇,2 天)

  • 第 1 天:4.1~4.6(补的能力、首部、握手、挥手、TIME_WAIT)+ 图 1 + 必背 1~9
  • 第 1 天晚:4.7~4.8(可靠传输、流量控制)+ 演示 1 + 题库 1~4
  • 第 2 天:4.9~4.11(拥塞控制、TCP/UDP)+ 图 2 + 演示 2、3 + 题库 5~7
  • 第 7 天回看:拿一张纸默画两条状态链和一条 cwnd 曲线,画不出就回 4.5 和 4.9

下篇预告:篇 22 讲 路由与交换——静态路由与动态路由(RIP、OSPF、BGP)怎么选、VLAN 怎么划分广播域、三层交换为什么比路由器快。它和本篇的衔接点在"路径":TCP 关心的是端到端这根管子通不通,路由关心的是这根管子在网络里走哪条路。管子的行为(重传、乱序、时延)很大程度上由路径决定,所以网络排障的顺序永远是"先看路由通不通,再看 TCP 稳不稳"。

互动提问:你负责的系统里,netstat -an 里哪种状态最多?如果 TIME_WAIT 上万,你第一反应是改内核参数还是改成长连接?如果 CLOSE_WAIT 上万,你会先去查代码还是先调 somaxconn?把你的判断和理由写在评论区,我挑几个典型配置帮你看看方向对不对。


系列导航|回目录|上一篇:20 嵌入式系统架构|下一篇:22 路由与交换

转载自 CSDN-专业IT技术社区

原文链接:https://blog.csdn.net/weixin_46316564/article/details/170373462

文章来源转载

评论

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--