【网络精讲1】TCP 三次握手:为什么是三次,第三次丢了怎么办
写在前面
这篇文章从最基础的那个疑问出发 ------ 为什么 TCP 需要三次握手才能建立连接,然后一路追问下去:滞留的旧连接请求到底是怎么被挡住的、第三次握手丢失之后重传间隔怎么算、服务端发出第二个报文后处于哪个状态。
这几个问题看起来零散,其实串成了一条完整的线索:
握手的目的 → 旧连接请求的拦截 → 重传的退避算法 → 服务端的状态与队列
按这条线索逐层展开,一次讲透。
一、三次握手到底在办哪三件事
1.1 同步双方的初始序列号(ISN)
TCP 的可靠传输建立在序号之上 ------ 排序、去重、确认、重传,全部依赖它。一条连接刚建立时,双方各自随机生成一个 32 位初始序列号(ISN),它必须让对方知道,并且得到对方的确认。
信息是双向的:客户端要告诉服务端「本端起点的序号是 x」,服务端也要告诉客户端「本端起点的序号是 y」。每个方向都需要一次「本端声明 + 对方确认」,合起来就是四条信息。
1.2 确认双方的收发能力
一次握手时,客户端不知道服务端收没收到;两次握手时,服务端永远不知道对方有没有收到自己发出去的东西。只有收到第三个 ACK,服务端才终于确认「自己发出的包,对方能收到」。
把每个时刻双方的「认知状态」列出来,会看得非常清楚:
| 时刻 | 客户端已经确认 | 服务端已经确认 |
|---|---|---|
| 发出 ① 之后 | 什么都还没确认 | 什么都还没确认 |
| 收到 ② 之后 | 能发、能收、对端序号 y 已知 | 只知道「本端能发」,从未收到过任何确认 |
| 收到 ③ 之后 | 没有变化(第 ② 步就已经全知道) | 能发、能收、对端序号 x 已知 |
这张表藏着一个容易被忽略的洞察:第三个 ACK 纯粹是为服务端服务的。客户端在第 ② 步信息就已经齐全,第 ③ 步不改变客户端的任何认知;而服务端在第 ② 步结束时还是「盲发」状态 ------ 它已经在准备发数据、分配资源,却从未收到过对方任何一句「收到了」。
1.3 挡住滞留在网络里的旧连接请求
这是教材里「已失效的连接请求报文段」想表达的东西,也是两次握手真正致命的地方 ------ 问题不在于「能力确认不全」那么温和,而在于服务端会在没有任何确认的情况下就分配连接资源。
完整的三次握手时序如下:
服务端 客户端 服务端 客户端 #mermaid-svg-ghv99alxqrxfqSMD{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-ghv99alxqrxfqSMD .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-ghv99alxqrxfqSMD .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-ghv99alxqrxfqSMD .error-icon{fill:#552222;}#mermaid-svg-ghv99alxqrxfqSMD .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-ghv99alxqrxfqSMD .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-ghv99alxqrxfqSMD .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-ghv99alxqrxfqSMD .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-ghv99alxqrxfqSMD .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-ghv99alxqrxfqSMD .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-ghv99alxqrxfqSMD .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-ghv99alxqrxfqSMD .marker{fill:#333333;stroke:#333333;}#mermaid-svg-ghv99alxqrxfqSMD .marker.cross{stroke:#333333;}#mermaid-svg-ghv99alxqrxfqSMD svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-ghv99alxqrxfqSMD p{margin:0;}#mermaid-svg-ghv99alxqrxfqSMD .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-ghv99alxqrxfqSMD text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-ghv99alxqrxfqSMD .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-ghv99alxqrxfqSMD .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-ghv99alxqrxfqSMD .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-ghv99alxqrxfqSMD .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-ghv99alxqrxfqSMD #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-ghv99alxqrxfqSMD .sequenceNumber{fill:white;}#mermaid-svg-ghv99alxqrxfqSMD #sequencenumber{fill:#333;}#mermaid-svg-ghv99alxqrxfqSMD #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-ghv99alxqrxfqSMD .messageText{fill:#333;stroke:none;}#mermaid-svg-ghv99alxqrxfqSMD .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-ghv99alxqrxfqSMD .labelText,#mermaid-svg-ghv99alxqrxfqSMD .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-ghv99alxqrxfqSMD .loopText,#mermaid-svg-ghv99alxqrxfqSMD .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-ghv99alxqrxfqSMD .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-ghv99alxqrxfqSMD .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-ghv99alxqrxfqSMD .noteText,#mermaid-svg-ghv99alxqrxfqSMD .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-ghv99alxqrxfqSMD .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-ghv99alxqrxfqSMD .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-ghv99alxqrxfqSMD .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-ghv99alxqrxfqSMD .actorPopupMenu{position:absolute;}#mermaid-svg-ghv99alxqrxfqSMD .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-ghv99alxqrxfqSMD .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-ghv99alxqrxfqSMD .actor-man circle,#mermaid-svg-ghv99alxqrxfqSMD line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-ghv99alxqrxfqSMD :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 客户端 CLOSED | 服务端 LISTEN 收到 SYN状态 LISTEN → SYN_RCVD连接放入半连接队列 收到 SYN+ACK状态 SYN_SENT → ESTABLISHED 收到 ACK状态 SYN_RCVD → ESTABLISHED连接移入全连接队列 ① SYN, seq = x② SYN + ACK, seq = y, ack = x+1③ ACK, ack = y+1
二、为什么不是四次
两个方向各需要两条信息(「本端的 ISN」+「确认对方的 ISN」),一共四条。但 ACK 本身不占序号、也不需要携带数据,可以直接搭在 SYN 上一起发。
于是服务端的「确认对方的 x」和「这是自己的 y」合并成一个 SYN+ACK 报文,四条信息压成三个包。三是能完成这件事的物理下限,不是随便定的数字。
#mermaid-svg-PmOBLbyBTsUKJAwF{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-PmOBLbyBTsUKJAwF .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-PmOBLbyBTsUKJAwF .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-PmOBLbyBTsUKJAwF .error-icon{fill:#552222;}#mermaid-svg-PmOBLbyBTsUKJAwF .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-PmOBLbyBTsUKJAwF .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-PmOBLbyBTsUKJAwF .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-PmOBLbyBTsUKJAwF .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-PmOBLbyBTsUKJAwF .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-PmOBLbyBTsUKJAwF .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-PmOBLbyBTsUKJAwF .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-PmOBLbyBTsUKJAwF .marker{fill:#333333;stroke:#333333;}#mermaid-svg-PmOBLbyBTsUKJAwF .marker.cross{stroke:#333333;}#mermaid-svg-PmOBLbyBTsUKJAwF svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-PmOBLbyBTsUKJAwF p{margin:0;}#mermaid-svg-PmOBLbyBTsUKJAwF .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-PmOBLbyBTsUKJAwF .cluster-label text{fill:#333;}#mermaid-svg-PmOBLbyBTsUKJAwF .cluster-label span{color:#333;}#mermaid-svg-PmOBLbyBTsUKJAwF .cluster-label span p{background-color:transparent;}#mermaid-svg-PmOBLbyBTsUKJAwF .label text,#mermaid-svg-PmOBLbyBTsUKJAwF span{fill:#333;color:#333;}#mermaid-svg-PmOBLbyBTsUKJAwF .node rect,#mermaid-svg-PmOBLbyBTsUKJAwF .node circle,#mermaid-svg-PmOBLbyBTsUKJAwF .node ellipse,#mermaid-svg-PmOBLbyBTsUKJAwF .node polygon,#mermaid-svg-PmOBLbyBTsUKJAwF .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-PmOBLbyBTsUKJAwF .rough-node .label text,#mermaid-svg-PmOBLbyBTsUKJAwF .node .label text,#mermaid-svg-PmOBLbyBTsUKJAwF .image-shape .label,#mermaid-svg-PmOBLbyBTsUKJAwF .icon-shape .label{text-anchor:middle;}#mermaid-svg-PmOBLbyBTsUKJAwF .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-PmOBLbyBTsUKJAwF .rough-node .label,#mermaid-svg-PmOBLbyBTsUKJAwF .node .label,#mermaid-svg-PmOBLbyBTsUKJAwF .image-shape .label,#mermaid-svg-PmOBLbyBTsUKJAwF .icon-shape .label{text-align:center;}#mermaid-svg-PmOBLbyBTsUKJAwF .node.clickable{cursor:pointer;}#mermaid-svg-PmOBLbyBTsUKJAwF .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-PmOBLbyBTsUKJAwF .arrowheadPath{fill:#333333;}#mermaid-svg-PmOBLbyBTsUKJAwF .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-PmOBLbyBTsUKJAwF .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-PmOBLbyBTsUKJAwF .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-PmOBLbyBTsUKJAwF .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-PmOBLbyBTsUKJAwF .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-PmOBLbyBTsUKJAwF .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-PmOBLbyBTsUKJAwF .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-PmOBLbyBTsUKJAwF .cluster text{fill:#333;}#mermaid-svg-PmOBLbyBTsUKJAwF .cluster span{color:#333;}#mermaid-svg-PmOBLbyBTsUKJAwF div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-PmOBLbyBTsUKJAwF .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-PmOBLbyBTsUKJAwF rect.text{fill:none;stroke-width:0;}#mermaid-svg-PmOBLbyBTsUKJAwF .icon-shape,#mermaid-svg-PmOBLbyBTsUKJAwF .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-PmOBLbyBTsUKJAwF .icon-shape p,#mermaid-svg-PmOBLbyBTsUKJAwF .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-PmOBLbyBTsUKJAwF .icon-shape .label rect,#mermaid-svg-PmOBLbyBTsUKJAwF .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-PmOBLbyBTsUKJAwF .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-PmOBLbyBTsUKJAwF .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-PmOBLbyBTsUKJAwF :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 实际只需三个报文
需要传递的四条信息
① 客户端声明本端 ISN = x
② 服务端确认 x
③ 服务端声明本端 ISN = y
④ 客户端确认 y
SYN:承载 ①
SYN+ACK:合并承载 ② + ③
ACK:承载 ④
三、深入:滞留在网络里的旧连接请求
「挡住旧连接请求」这句话之所以难懂,是因为它依赖一个前提:一个 TCP 报文可以在网络里「活」很久,久到它到达时,它所属的那次连接早就结束了。
3.1 一个 SYN 可以「活」很久
t0 客户端发出 SYN₁(seq = x)
这个包在网络里被卡住了,迟迟送不到服务端
t1 客户端等不到回应,超时重传
它并不知道 SYN₁ 还活着,于是又发了一个 SYN₂
t2 SYN₂ 正常送达,三次握手完成
建连 → 传数据 → 四次挥手 → 连接彻底关闭
服务端上关于它的状态已经全部清空
t3 滞留的 SYN₁ 终于到达服务端
在服务端眼里,它就是一个全新的「请求建立连接」报文
这个「迟到的包」并不是理论上的极端情况:
- 超时重传本身的副作用 。SYN 的重传超时按 1s → 2s → 4s 递增。如果第一次的 SYN 只是被某个拥塞的路由器压了几十秒而不是丢了,它就会在重传之后姗姗来迟。
- 客户端进程崩溃后立刻重启,又恰好分到同一个源端口。新连接和旧连接的四元组一模一样,旧连接的残留报文会直接串进新连接。
- 中间的 NAT、防火墙、四层负载均衡各自都有自己的重传与超时机制,同样会制造延迟包。
3.2 服务端为什么分辨不出新旧
到 t3 这一刻,问题变成:服务端凭什么判断这个 SYN 是「迟到三分钟的旧包」还是「刚发来的新请求」?
答案是 ------ 凭不了。把服务端能看到的全部信息逐条列出来:
| 服务端能看到的信息 | 这次的值 | 能说明它是旧包吗 |
|---|---|---|
| 源 IP / 源端口 | 和一个新请求完全一样 | 不能 ------ 同一个客户端当然一样 |
| 目的 IP / 目的端口 | 指向服务端自己 | 不能 ------ 任何连接都指向它 |
| SYN 标志位 | = 1 | 不能 ------ 新请求的 SYN 也是 1 |
| 序号 seq | 重传沿用同一个 x | 不能 ------ 服务端手里没有它的历史 |
| 时间戳选项 | 两个包都带 | 不能 ------ 它只用来算 RTT,不代表包的年龄 |
| 「这个包的出生时间」 | 报文头里不存在这个字段 | --- |
TCP 报文头里没有表示「这个包已经存活多久」的字段。 服务端唯一能依靠的判据是「当前有没有这条连接」------ 而到 t3 这一刻,那条连接早就在 t2 结束时被彻底清掉,连 TIME_WAIT 都过期了。所以它只能把旧 SYN 当成一次全新的连接请求来对待。
顺带一个加强论据:重传的 SYN 用的是同一个 ISN(seq 仍然是 x)。所以服务端拿到那个迟到的 SYN₁,跟当初的 SYN₂ 几乎字节级相同,连序号都一样 ------ 它连「这两个包不一样」都看不出来。
3.3 两种握手方案的分叉
服务端只能硬着头皮回一个 SYN+ACK。这时候两种握手方案的差别就体现出来了 ------ 差别不在「服务端做了什么」,而在于服务端敢不敢当场认定连接成立:
| 只有两次握手 | 有第三次握手 | |
|---|---|---|
| 发出 SYN+ACK 后 | 就当连接已建立,立刻进入 ESTABLISHED |
只停在 SYN_RCVD,挂在半连接队列 |
| 资源分配 | 马上分配 socket、缓冲区、fd | 等不到 ACK 就绝不认为连上了,不分配正式连接资源 |
| 对方反应 | 客户端毫不知情,永远不会回 ACK | 客户端已关闭、不在 SYN_SENT,收到莫名的 SYN+ACK 就回 RST |
| 结局 | 幽灵连接长期占着服务端资源 | 服务端收到 RST,立刻清掉半连接,零损失 |
第三个 ACK 的真正作用,是把「连接算不算成立」的最终决定权交到客户端手里。 客户端可以用沉默或 RST 来否决;而在两次握手里,服务端是单方面宣布连接成立的。
3.4 延伸:SYN Flood
理解了这个机制,SYN Flood 就顺理成章 ------ 攻击者连重传都不用,直接伪造海量源 IP 发 SYN。服务端每收到一个就回 SYN+ACK、占一个半连接队列槽位、傻等 ACK。
这正是「没过第三次握手就不分配正式连接资源」这条设计的价值:它把损失压在半连接队列里(轻量),而不是完整的 socket 资源(沉重)。
防御手段有三个方向:调大 backlog、缩短 SYN_RCVD 超时、开启 net.ipv4.tcp_syncookies=1(打开之后即使半连接队列满了也能完成握手)。
四、握手报文丢了:RTO 与指数退避
4.1 RTO 是一个变量,不是常量
常见的误解是把 RTO 当成一个固定值。它有两种身份:
- SYN / SYN+ACK :还没有任何 RTT 采样,RTO 被规定固定初值 1 秒,只有翻倍、没有测量。
- 数据段 :RTO =
SRTT + 4 × RTTVAR(RFC 6298),Linux 下限 200ms。而且重传过的报文不能用它的 ACK 来更新 RTT(Karn 算法)------ 因为分不清这个 ACK 在确认哪一次,所以退避期间 RTO 只会涨、不会收敛。
4.2 指数退避:翻倍的是「相邻间隔」
每一跳都是上一跳的 2 倍,而不是「间隔固定等于 2 × RTO」。准确表述是:
第 n 次重传与第 n+1 次重传之间的等待时长 = 当前 RTO,而每次重传之后 RTO 都会翻倍。
所以相邻两跳才是 2 倍关系:RTO₀, 2·RTO₀, 4·RTO₀, 8·RTO₀ ...
对 SYN / SYN+ACK 这种没有 RTT 采样的场景,RTO₀ 被规定死为 1 秒,于是间隔序列特别干净:
发出 SYN 之后等 1 秒
第 1 次重传后等 2 秒
第 2 次重传后等 4 秒
第 3 次重传后等 8 秒
第 4 次重传后等 16 秒
第 5 次重传后等 32 秒
第 6 次重传后等 64 秒
─────
127 秒后彻底放弃
翻倍还有上限:TCP_RTO_MAX = 120 秒,涨到这儿就封顶,不会无限增长。
还有一个容易踩的点:只有「超时重传」才退避,快速重传不退避。 数据段如果收到 3 个重复 ACK,是立刻重发的,不走 RTO 定时器。握手阶段没有重复 ACK 可用,只能走超时路径 ------ 所以握手重传一定是退避的。
4.3 第三次 ACK 丢了,谁在重传
这里有个关键前提需要掰正:退避重传的不是客户端,是服务端。
原因是那个 ACK 是「纯 ACK」------ 它不携带数据,客户端发完就什么都不管了,它根本没有为这个 ACK 设置任何重传定时器 。真正在数秒、在翻倍、在超时重发的,是卡在 SYN_RCVD 等对方确认的服务端 ,它在重传 SYN+ACK:
服务端 客户端 服务端 客户端 #mermaid-svg-tXWtyp3S8UN5LgFM{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-tXWtyp3S8UN5LgFM .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-tXWtyp3S8UN5LgFM .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-tXWtyp3S8UN5LgFM .error-icon{fill:#552222;}#mermaid-svg-tXWtyp3S8UN5LgFM .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-tXWtyp3S8UN5LgFM .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-tXWtyp3S8UN5LgFM .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-tXWtyp3S8UN5LgFM .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-tXWtyp3S8UN5LgFM .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-tXWtyp3S8UN5LgFM .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-tXWtyp3S8UN5LgFM .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-tXWtyp3S8UN5LgFM .marker{fill:#333333;stroke:#333333;}#mermaid-svg-tXWtyp3S8UN5LgFM .marker.cross{stroke:#333333;}#mermaid-svg-tXWtyp3S8UN5LgFM svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-tXWtyp3S8UN5LgFM p{margin:0;}#mermaid-svg-tXWtyp3S8UN5LgFM .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-tXWtyp3S8UN5LgFM text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-tXWtyp3S8UN5LgFM .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-tXWtyp3S8UN5LgFM .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-tXWtyp3S8UN5LgFM .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-tXWtyp3S8UN5LgFM .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-tXWtyp3S8UN5LgFM #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-tXWtyp3S8UN5LgFM .sequenceNumber{fill:white;}#mermaid-svg-tXWtyp3S8UN5LgFM #sequencenumber{fill:#333;}#mermaid-svg-tXWtyp3S8UN5LgFM #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-tXWtyp3S8UN5LgFM .messageText{fill:#333;stroke:none;}#mermaid-svg-tXWtyp3S8UN5LgFM .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-tXWtyp3S8UN5LgFM .labelText,#mermaid-svg-tXWtyp3S8UN5LgFM .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-tXWtyp3S8UN5LgFM .loopText,#mermaid-svg-tXWtyp3S8UN5LgFM .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-tXWtyp3S8UN5LgFM .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-tXWtyp3S8UN5LgFM .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-tXWtyp3S8UN5LgFM .noteText,#mermaid-svg-tXWtyp3S8UN5LgFM .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-tXWtyp3S8UN5LgFM .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-tXWtyp3S8UN5LgFM .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-tXWtyp3S8UN5LgFM .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-tXWtyp3S8UN5LgFM .actorPopupMenu{position:absolute;}#mermaid-svg-tXWtyp3S8UN5LgFM .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-tXWtyp3S8UN5LgFM .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-tXWtyp3S8UN5LgFM .actor-man circle,#mermaid-svg-tXWtyp3S8UN5LgFM line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-tXWtyp3S8UN5LgFM :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} ③ 客户端的 ACK 在网络里丢了 进入 ESTABLISHED可以立刻发数据没有重传定时器 留在 SYN_RCVD启动重传定时器 终于进入 ESTABLISHED ① SYN② SYN + ACK③ ACK ✗(丢包)④ 超时后按退避时长重传 SYN+ACK⑤ 客户端收到重复 SYN+ACK,回一个重复 ACK
客户端收到重复的 SYN+ACK 之后会回一个重复 ACK(Linux 实现里走的是 RFC 5961 的 challenge ACK 路径),服务端这才被救出来。
4.4 两侧的参数
| 方向 | 参数 | 默认值 | 退避序列(秒) | 放弃时间 |
|---|---|---|---|---|
| 主动方重传 SYN | tcp_syn_retries |
6 | 1, 2, 4, 8, 16, 32, 64 | 约 127 秒 |
| 被动方重传 SYN+ACK | tcp_synack_retries |
5 | 1, 2, 4, 8, 16 | 约 31 秒 |
4.5 线上的真实表现
这个 SYN+ACK 重传在真实场景里多数根本用不上 。客户端一进入 ESTABLISHED 通常立刻就把请求发出去了,而这个数据段的 ACK 字段就是 y+1 ------ 它顺路就把服务端从 SYN_RCVD 拎进 ESTABLISHED 了。
所以「第三次握手丢失」在线上通常表现为首个请求多等一个 RTT,而不是连接失败。
真正会失败的是那条反向的时间差:服务端约 31 秒后放弃,把半连接扔掉;客户端却还傻傻地 ESTABLISHED 着 ------ 它之后发的数据会收到一个 RST,于是报 Connection reset。
这就是「客户端以为连上了、服务端其实早就放弃了」的经典不一致状态。
五、握手期间的状态流转
5.1 双方的状态机
服务端在收到那个 SYN 时就进入了 SYN_RCVD,然后(同一步里)把 SYN+ACK 发出去。所以「发出第二个报文之后」,它确实站在 SYN_RCVD 上。
#mermaid-svg-A4UK813F7PC3Luh6{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-A4UK813F7PC3Luh6 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-A4UK813F7PC3Luh6 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-A4UK813F7PC3Luh6 .error-icon{fill:#552222;}#mermaid-svg-A4UK813F7PC3Luh6 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-A4UK813F7PC3Luh6 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-A4UK813F7PC3Luh6 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-A4UK813F7PC3Luh6 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-A4UK813F7PC3Luh6 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-A4UK813F7PC3Luh6 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-A4UK813F7PC3Luh6 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-A4UK813F7PC3Luh6 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-A4UK813F7PC3Luh6 .marker.cross{stroke:#333333;}#mermaid-svg-A4UK813F7PC3Luh6 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-A4UK813F7PC3Luh6 p{margin:0;}#mermaid-svg-A4UK813F7PC3Luh6 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-A4UK813F7PC3Luh6 .cluster-label text{fill:#333;}#mermaid-svg-A4UK813F7PC3Luh6 .cluster-label span{color:#333;}#mermaid-svg-A4UK813F7PC3Luh6 .cluster-label span p{background-color:transparent;}#mermaid-svg-A4UK813F7PC3Luh6 .label text,#mermaid-svg-A4UK813F7PC3Luh6 span{fill:#333;color:#333;}#mermaid-svg-A4UK813F7PC3Luh6 .node rect,#mermaid-svg-A4UK813F7PC3Luh6 .node circle,#mermaid-svg-A4UK813F7PC3Luh6 .node ellipse,#mermaid-svg-A4UK813F7PC3Luh6 .node polygon,#mermaid-svg-A4UK813F7PC3Luh6 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-A4UK813F7PC3Luh6 .rough-node .label text,#mermaid-svg-A4UK813F7PC3Luh6 .node .label text,#mermaid-svg-A4UK813F7PC3Luh6 .image-shape .label,#mermaid-svg-A4UK813F7PC3Luh6 .icon-shape .label{text-anchor:middle;}#mermaid-svg-A4UK813F7PC3Luh6 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-A4UK813F7PC3Luh6 .rough-node .label,#mermaid-svg-A4UK813F7PC3Luh6 .node .label,#mermaid-svg-A4UK813F7PC3Luh6 .image-shape .label,#mermaid-svg-A4UK813F7PC3Luh6 .icon-shape .label{text-align:center;}#mermaid-svg-A4UK813F7PC3Luh6 .node.clickable{cursor:pointer;}#mermaid-svg-A4UK813F7PC3Luh6 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-A4UK813F7PC3Luh6 .arrowheadPath{fill:#333333;}#mermaid-svg-A4UK813F7PC3Luh6 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-A4UK813F7PC3Luh6 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-A4UK813F7PC3Luh6 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-A4UK813F7PC3Luh6 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-A4UK813F7PC3Luh6 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-A4UK813F7PC3Luh6 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-A4UK813F7PC3Luh6 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-A4UK813F7PC3Luh6 .cluster text{fill:#333;}#mermaid-svg-A4UK813F7PC3Luh6 .cluster span{color:#333;}#mermaid-svg-A4UK813F7PC3Luh6 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-A4UK813F7PC3Luh6 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-A4UK813F7PC3Luh6 rect.text{fill:none;stroke-width:0;}#mermaid-svg-A4UK813F7PC3Luh6 .icon-shape,#mermaid-svg-A4UK813F7PC3Luh6 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-A4UK813F7PC3Luh6 .icon-shape p,#mermaid-svg-A4UK813F7PC3Luh6 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-A4UK813F7PC3Luh6 .icon-shape .label rect,#mermaid-svg-A4UK813F7PC3Luh6 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-A4UK813F7PC3Luh6 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-A4UK813F7PC3Luh6 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-A4UK813F7PC3Luh6 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 客户端(主动方)
发出 SYN
收到 SYN+ACK,回 ACK
CLOSED
尚未发起任何连接
SYN_SENT
已发 SYN,等对方的 SYN+ACK
ESTABLISHED
回完 ACK 就立刻进入
服务端(被动方)
收到 SYN,回 SYN+ACK
收到第三个 ACK
LISTEN
监听中,还没为谁分配连接
SYN_RCVD
已回 SYN+ACK,在等对方确认
ESTABLISHED
连接正式建立
注意这个状态只存在于服务端 ------ 同一时刻客户端在 SYN_SENT,两边是不同步的,各行其是。
5.2 状态名对照
同一个状态有三种叫法,别被搞混:
| RFC 793 标准名 | Linux 内核常量 | ss / netstat 显示 |
|---|---|---|
SYN-RECEIVED |
TCP_SYN_RECV |
SYN-RECV / SYN_RECV |
如果第三个 ACK 一直不来,服务端就卡在 SYN_RCVD 里,按指数退避重传 SYN+ACK(1、2、4、8、16 秒,共约 31 秒),耗完 tcp_synack_retries 就把这条半连接从队列里删掉 ------ 连接像没发生过一样。这期间客户端其实已经 ESTABLISHED 了,就是前面说的那个认知不一致。
5.3 半连接队列与全连接队列
SYN_RCVD 这个状态不是抽象的,它对应内核里一个具体的位置 ------ 连接被挂进了半连接队列。
#mermaid-svg-z4jJhDogrdi5pSCW{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-z4jJhDogrdi5pSCW .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-z4jJhDogrdi5pSCW .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-z4jJhDogrdi5pSCW .error-icon{fill:#552222;}#mermaid-svg-z4jJhDogrdi5pSCW .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-z4jJhDogrdi5pSCW .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-z4jJhDogrdi5pSCW .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-z4jJhDogrdi5pSCW .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-z4jJhDogrdi5pSCW .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-z4jJhDogrdi5pSCW .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-z4jJhDogrdi5pSCW .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-z4jJhDogrdi5pSCW .marker{fill:#333333;stroke:#333333;}#mermaid-svg-z4jJhDogrdi5pSCW .marker.cross{stroke:#333333;}#mermaid-svg-z4jJhDogrdi5pSCW svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-z4jJhDogrdi5pSCW p{margin:0;}#mermaid-svg-z4jJhDogrdi5pSCW .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-z4jJhDogrdi5pSCW .cluster-label text{fill:#333;}#mermaid-svg-z4jJhDogrdi5pSCW .cluster-label span{color:#333;}#mermaid-svg-z4jJhDogrdi5pSCW .cluster-label span p{background-color:transparent;}#mermaid-svg-z4jJhDogrdi5pSCW .label text,#mermaid-svg-z4jJhDogrdi5pSCW span{fill:#333;color:#333;}#mermaid-svg-z4jJhDogrdi5pSCW .node rect,#mermaid-svg-z4jJhDogrdi5pSCW .node circle,#mermaid-svg-z4jJhDogrdi5pSCW .node ellipse,#mermaid-svg-z4jJhDogrdi5pSCW .node polygon,#mermaid-svg-z4jJhDogrdi5pSCW .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-z4jJhDogrdi5pSCW .rough-node .label text,#mermaid-svg-z4jJhDogrdi5pSCW .node .label text,#mermaid-svg-z4jJhDogrdi5pSCW .image-shape .label,#mermaid-svg-z4jJhDogrdi5pSCW .icon-shape .label{text-anchor:middle;}#mermaid-svg-z4jJhDogrdi5pSCW .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-z4jJhDogrdi5pSCW .rough-node .label,#mermaid-svg-z4jJhDogrdi5pSCW .node .label,#mermaid-svg-z4jJhDogrdi5pSCW .image-shape .label,#mermaid-svg-z4jJhDogrdi5pSCW .icon-shape .label{text-align:center;}#mermaid-svg-z4jJhDogrdi5pSCW .node.clickable{cursor:pointer;}#mermaid-svg-z4jJhDogrdi5pSCW .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-z4jJhDogrdi5pSCW .arrowheadPath{fill:#333333;}#mermaid-svg-z4jJhDogrdi5pSCW .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-z4jJhDogrdi5pSCW .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-z4jJhDogrdi5pSCW .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-z4jJhDogrdi5pSCW .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-z4jJhDogrdi5pSCW .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-z4jJhDogrdi5pSCW .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-z4jJhDogrdi5pSCW .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-z4jJhDogrdi5pSCW .cluster text{fill:#333;}#mermaid-svg-z4jJhDogrdi5pSCW .cluster span{color:#333;}#mermaid-svg-z4jJhDogrdi5pSCW div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-z4jJhDogrdi5pSCW .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-z4jJhDogrdi5pSCW rect.text{fill:none;stroke-width:0;}#mermaid-svg-z4jJhDogrdi5pSCW .icon-shape,#mermaid-svg-z4jJhDogrdi5pSCW .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-z4jJhDogrdi5pSCW .icon-shape p,#mermaid-svg-z4jJhDogrdi5pSCW .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-z4jJhDogrdi5pSCW .icon-shape .label rect,#mermaid-svg-z4jJhDogrdi5pSCW .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-z4jJhDogrdi5pSCW .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-z4jJhDogrdi5pSCW .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-z4jJhDogrdi5pSCW :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 服务端内核:一个 LISTEN socket 下面挂着两个队列
收到第三个 ACK,内核把它挪过来
accept 取走后交付给应用
① 半连接队列(SYN queue)
处于 SYN_RCVD 的连接待在这里
已回过 SYN+ACK,还没收到对方确认
② 全连接队列(accept queue)
处于 ESTABLISHED 的连接待在这里
等应用层 accept() 把它取走
应用手里那个已连接的 fd
完整的答案链是:
服务端
LISTEN→(收到 SYN)→ 进入SYN_RCVD,同时创建一条「半连接」记录放进半连接队列,并把SYN+ACK发出去 →(收到第三个 ACK)→ 进入ESTABLISHED,记录移入全连接队列,等accept()取走。
两个队列的长度上限(面试常问):
- 半连接队列:
net.ipv4.tcp_max_syn_backlog - 全连接队列:
min(backlog, somaxconn)
SYN Flood 打的就是前者。
六、速查与常见追问
一句话回答「为什么需要三次握手」
三次握手 = 双向序号同步 + 双向收发能力确认,顺带把不可靠网络里那些过期的连接请求筛掉。
为什么不是两次
两次握手在第 ② 步就结束了,此时服务端是「盲发」状态 ------ 它已经在准备发数据、分配资源,却从未收到过对方发来的任何确认。而且一旦把「认定连接成立」提前到收到 SYN 的那一刻,滞留的旧 SYN 就会在服务端留下一条条没人认领的幽灵连接。
为什么不是四次
两个方向各要两条信息(本端 ISN + 确认对方的 ISN),共四条;而 ACK 不占序号、可以搭 SYN 的便车,于是四条信息合并成三个包。三是物理下限。
第三个 ACK 丢了会怎样
- 客户端已经
ESTABLISHED,且没有为这个 ACK 设置重传定时器 ;如果它立刻发了数据,数据段里的ACK = y+1会顺路把服务端拎进ESTABLISHED,通常不用等重传。 - 服务端停在
SYN_RCVD,按指数退避重传SYN+ACK(tcp_synack_retries = 5,约 31 秒后放弃)。 - 客户端收到重传的
SYN+ACK会回一个重复 ACK,握手完成。 - 若服务端先放弃,客户端仍以为自己连上了 ------ 之后发数据收到 RST,报
Connection reset。
重传间隔到底怎么算
RTO 是变量。每次超时之后 RTO 翻倍 ,所以「相邻两次重传的间隔」才是 2 倍关系,而不是「间隔 = 2 × RTO」。SYN 类报文的 RTO 初值固定 1 秒,序列为 1、2、4、8、16、32、64 秒,上限 TCP_RTO_MAX = 120 秒。
服务端发出第二个报文后是什么状态
SYN_RCVD(Linux 内核 TCP_SYN_RECV,ss 显示 SYN-RECV)。连接此时待在半连接队列里,收到第三个 ACK 才转入 ESTABLISHED 并移入全连接队列。
附:本文涉及的内核参数与标准
| 项目 | 值 / 出处 |
|---|---|
| SYN 重传的 RTO 初值 | TCP_TIMEOUT_INIT,1 秒 |
| RTO 翻倍上限 | TCP_RTO_MAX,120 秒 |
| 数据段 RTO 下限 | TCP_RTO_MIN,200ms |
| 主动方 SYN 重传次数 | net.ipv4.tcp_syn_retries,默认 6 |
| 被动方 SYN+ACK 重传次数 | net.ipv4.tcp_synack_retries,默认 5 |
| 半连接队列上限 | net.ipv4.tcp_max_syn_backlog |
| 全连接队列上限 | min(backlog, somaxconn) |
| SYN Flood 防护 | net.ipv4.tcp_syncookies = 1 |
| RTO 算法 | RFC 6298(含 Karn 算法) |
| 状态机 | RFC 793 |
| ESTABLISHED 中的意外 SYN | RFC 5961(challenge ACK) |