1. 为什么前端面试总爱问三次握手和四次挥手1.1 一道题背后的考察逻辑前端面试里TCP/IP 的三次握手和四次挥手几乎是网络部分的“保留曲目”。很多人第一次被问到的时候会懵我写页面、调接口、做性能优化跟 TCP 有什么关系但面试官问这道题从来不是想听你把课本上的状态机背一遍而是想通过它判断你对“一次网络请求到底经历了什么”有没有真实的感知。我面过不少人也被人面过很多次。这道题真正拉开差距的地方在于有人只能说出“SYN、SYNACK、ACK”三个词有人却能顺着这条线讲到浏览器并发连接数、HTTP/1.1 的队头阻塞、为什么接口慢有时候不是后端的问题、为什么某些场景下会出现连接被重置。前者是背答案后者是理解系统。面试官要的是后者。所以这篇内容我会把三次握手和四次挥手拆开讲透同时补上前端视角下真正有用的那些延伸点。适合准备面试的同学系统梳理也适合已经工作但一直没搞明白这块的开发者补课。核心关键词就是三次握手、四次挥手、TCP 连接、前端面试、网络请求这些会贯穿全文。1.2 前端到底需不需要懂这么深先回答一个很多人心里的疑问前端天天写业务真的需要懂 TCP 吗我的答案是日常写增删改查确实用不上但一旦你碰到下面这些场景不懂就会很被动。页面加载慢你怀疑是接口问题但抓包发现是连接建立阶段耗时过长高并发场景下浏览器对同一域名的连接数有限制导致请求排队某些接口偶发失败报错是连接被重置你完全不知道从哪查做性能优化时想通过域名分片、连接复用来提升加载速度这些问题的根子都在传输层。你不需要会写 TCP 协议栈但你需要知道一条连接是怎么建立、怎么断开、在什么情况下会出问题。这就是前端面试考这道题的底层逻辑——它是一块试金石测的是你有没有往下挖一层的能力。2. 三次握手一条连接是怎么被“谈”出来的2.1 用生活场景理解握手过程先别急着看状态机。我用一个打电话的场景帮你建立直觉。你给朋友打电话朋友接起来说“喂”你说“是我能听到吗”朋友说“能听到你说”。这三句话完成了一次“确认双方都能正常收发”的过程。TCP 的三次握手本质上一模一样只不过双方是两台机器说的是 SYN 和 ACK。具体过程是这样的客户端先发一个 SYN 包里面带一个随机序号比如 seqx意思是“我想跟你建立连接我的起始序号是 x”服务端收到后回一个 SYNACK 包seqyackx1意思是“我收到了我的起始序号是 y我期待你下一个发 x1”客户端再回一个 ACK 包acky1意思是“我收到你的确认了我期待你下一个发 y1”到这里双方都确认了“我能发、你能收你能发、我也能收”连接建立完成进入 ESTABLISHED 状态。2.2 为什么是三次而不是两次或四次这是面试里最爱追问的点也是最能看出理解深度的地方。为什么不是两次假设只有两次客户端发 SYN服务端回 SYNACK 就认为连接建立了。问题在于服务端无法确认客户端是否收到了自己的 SYNACK。如果这个包在路上丢了客户端根本没建立连接但服务端已经分配了资源、进入了连接状态这就造成了资源浪费。更麻烦的是如果客户端之前发过一个延迟很久的旧 SYN 包服务端会误以为这是新请求直接建立连接而客户端根本不认这就是所谓的“历史连接”问题。三次握手让客户端有机会通过第三个 ACK 来确认服务端只有在收到这个 ACK 后才真正认为连接可用。为什么不是四次因为服务端的 SYN 和 ACK 可以合并成一个包发送。服务端收到 SYN 后既要确认客户端的序号ACK又要发送自己的序号SYN这两个动作没有先后依赖完全可以放在一个包里。所以四次被优化成了三次。这里有个细节值得注意三次握手并不是“必须三次”而是“最少三次”。在某些特殊情况下比如双方同时发起连接同时打开流程会变成四次但这是极少数场景面试里一般不深究。2.3 序号和确认号到底在干什么很多人背了 seq 和 ack 但不知道它们的作用。我用一句话说清楚序号解决的是“数据按什么顺序拼”的问题确认号解决的是“我收到了哪些”的问题。TCP 是面向字节流的它不关心你发的是一个完整的消息还是半个它只认字节。每个字节都有一个编号seq 表示“我这个包的第一个字节的编号是多少”ack 表示“我期待你下一个字节的编号是多少”。接收方通过 ack 告诉发送方“你发的第 x 个字节之前的内容我都收到了”。这个机制保证了可靠性如果发送方发出去的数据迟迟收不到对应的 ack它就会重传。前端同学平时感知不到这一层是因为它被操作系统和网络库封装好了但一旦出现丢包、乱序这套机制就是保证数据完整的核心。2.4 初始序号为什么是随机的面试里偶尔会问为什么初始序号ISN不固定从 0 或 1 开始而要随机生成原因有两个。第一是安全如果序号可预测攻击者就能伪造包插入到连接里随机化让伪造变得困难。第二是避免历史连接的干扰如果两次连接的序号范围重叠旧连接的延迟包可能被新连接误认为是有效数据。随机化的 ISN 让不同连接的序号空间几乎不可能重叠。现代实现里ISN 不是纯随机而是基于一个时钟计数器加上每次连接的偏移量算出来的这样既保证了不可预测性又避免了序号回绕过快的问题。这个细节面试里说出来会很加分。3. 四次挥手断开为什么比建立更麻烦3.1 挥手过程的完整拆解连接建立是三次断开却是四次这个不对称经常让人困惑。还是用打电话类比你说“我说完了挂了啊”朋友说“好的我知道了”但朋友可能还有话没说完等他讲完他再说“我也说完了挂吧”你回“好的”。这就是四次。对应到 TCP客户端发 FINsequ意思是“我没数据要发了但还能收”服务端回 ACKacku1意思是“我知道你要关了”服务端处理完剩余数据后发 FINseqw意思是“我也没数据要发了”客户端回 ACKackw1意思是“我知道你要关了”然后等待一段时间后彻底关闭关键在于第二步和第三步之间服务端收到 FIN 后可能还有数据没发完所以 ACK 和 FIN 不能合并必须分开发。这就是四次挥手的根本原因。3.2 为什么客户端最后要等 2MSL这是四次挥手里最经典的追问。客户端发完最后一个 ACK 后不会立刻关闭而是进入 TIME_WAIT 状态等待 2 个 MSL报文最大生存时间通常是 1 到 4 分钟。为什么要等两个原因。第一保证最后一个 ACK 能到达服务端。如果这个 ACK 丢了服务端会以为客户端没收到自己的 FIN于是重传 FIN。如果客户端已经关闭了就没人回这个重传的 FIN服务端会一直卡在 LAST_ACK 状态。等待 2MSL 就是为了在这个时间内能响应服务端的重传。第二让本次连接的所有旧包在网络中消散。如果不等立刻用相同的四元组建立新连接旧连接里延迟的包可能被新连接收到造成数据错乱。2MSL 足够让这些包自然消亡。面试里如果能把这两点说清楚基本就稳了。再补一句“MSL 是报文在网络中的最大存活时间Linux 上通常固定为 60 秒所以 TIME_WAIT 一般是 60 秒”会显得你确实了解过实际实现。3.3 服务端为什么会有大量 TIME_WAIT 和 CLOSE_WAIT实际工作中排查网络问题时经常看到这两个状态它们对应的原因完全不同。TIME_WAIT 堆积通常出现在主动关闭连接的一方。如果是服务端主动关闭大量短连接会导致 TIME_WAIT 堆积占用端口资源。解决办法一般是让客户端主动关闭或者调整内核参数复用 TIME_WAIT 状态的连接。前端同学如果做 Node 服务端这个问题会经常遇到。CLOSE_WAIT 堆积则说明被动关闭的一方收到了 FIN但没有调用 close 关闭连接。这通常是代码 bug比如连接用完了忘记释放或者某个异常分支没有走到关闭逻辑。CLOSE_WAIT 不会自己消失堆积多了会耗尽文件描述符。看到 CLOSE_WAIT 居高不下第一反应应该是去查代码里的连接释放逻辑。这两个状态的区别面试里能讲清楚的人不多但讲出来就是亮点。4. 前端视角下真正有用的延伸知识4.1 浏览器并发连接数限制浏览器对同一个域名准确说是同一个源的并发 TCP 连接数是有限制的HTTP/1.1 时代通常是 6 个。这意味着如果你同时发 10 个请求到同一个接口域名有 4 个会排队等待。这个限制直接影响了前端性能优化策略。早期的域名分片技术就是把静态资源分散到多个子域名下绕过单域名连接数限制从而并行加载更多资源。但域名分片也有代价每个新域名都要重新做 DNS 解析、重新握手连接建立的开销反而可能拖慢速度。HTTP/2 出现后多路复用让一个连接就能并行传输多个请求域名分片基本被淘汰了。面试里如果被问到“为什么 HTTP/2 不需要域名分片”你就可以从三次握手的开销角度回答每个新域名都要重新握手而 HTTP/2 在一个连接上就能复用省掉了这些开销。4.2 连接复用与 Keep-AliveHTTP/1.1 默认开启 Keep-Alive一个 TCP 连接可以承载多个 HTTP 请求避免每个请求都重新握手。这个机制的价值用握手成本一算就清楚一次握手至少需要 1.5 个 RTT往返时间如果每个请求都新建连接在延迟高的网络下光握手就占了大半时间。但 Keep-Alive 也有坑。如果连接长时间空闲服务端可能会主动关闭而客户端不知道下次复用这个连接时就会失败。这就是为什么有时候会看到“连接被重置”的错误。解决办法是客户端在复用前做健康检查或者设置合理的空闲超时。4.3 握手耗时对首屏的影响一个完整的 HTTPS 请求在 TCP 三次握手之后还要做 TLS 握手TLS 1.2 需要额外的 2 个 RTTTLS 1.3 优化到了 1 个 RTT。加上 DNS 解析的时间一个请求在真正传输数据前可能已经花掉了好几个 RTT。这就是为什么首屏优化里减少请求数、提前建立连接preconnect、使用 CDN 就近接入这些手段有效。它们本质上都是在减少握手次数或缩短握手距离。理解了三次握手你就能明白这些优化手段背后的原理而不是机械地照搬。5. 面试答题的常见问题与排查技巧5.1 常见问题速查表问题常见错误回答正确思路为什么三次握手只答“为了确认双方收发能力”补充历史连接问题和资源浪费的角度为什么四次挥手答“因为要分别关闭两个方向”补充服务端可能还有数据要发ACK 和 FIN 不能合并为什么等 2MSL只答“保证 ACK 到达”补充让旧包消散避免影响新连接TIME_WAIT 太多怎么办答“直接杀掉”分析是主动关闭方的问题考虑连接复用或调整参数CLOSE_WAIT 太多怎么办不知道指向代码里连接未释放的 bug5.2 我踩过的几个坑第一个坑是死记硬背状态机。我早期准备面试时把 CLOSED、SYN_SENT、ESTABLISHED 这些状态背得滚瓜烂熟但被问到“客户端发完最后一个 ACK 后处于什么状态”时还是卡壳。后来我改成用场景去记把每个状态对应到“谁在等谁”就再也没忘过。第二个坑是忽略实际抓包。光看文字描述你永远不知道一个真实的握手包长什么样。我建议你用抓包工具实际抓一次访问网页的过程看看 SYN、SYNACK、ACK 三个包的时间间隔看看 FIN 包出现的时机。亲眼看过一次比背十遍都管用。第三个坑是把三次握手和 HTTPS 握手混为一谈。TCP 握手是传输层的TLS 握手是应用层之上的两者是先后关系。面试时如果被问到 HTTPS 的连接建立要分清楚这两个阶段别答串了。5.3 自己梳理全文背诵的正确姿势标题里说“建议自己梳理全文背诵”我特别认同但背诵不是死记。我的方法是画一张图把客户端和服务端两条时间线并排画出来每个箭头标注 seq、ack 和状态变化。画完之后合上资料自己默画一遍画不出来的地方就是没理解的地方。然后再用一句话概括每个阶段的核心目的三次握手是“确认双方收发能力并同步序号”四次挥手是“双方各自确认没有数据要发”。抓住这个主线细节自然能串起来。6. 从握手挥手看网络问题的排查思路6.1 连接建立慢怎么查如果发现接口响应慢第一步要区分是连接建立慢还是数据传输慢。用抓包工具看时间线如果 SYN 发出后很久才收到 SYNACK说明是网络延迟或服务端处理慢如果握手很快但数据传输慢那是带宽或服务端逻辑的问题。握手慢的常见原因包括DNS 解析慢、网络链路质量差、服务端 accept 队列满。前端能做的优化是提前解析 DNS、使用 preconnect 预热连接。6.2 连接被重置怎么定位“连接被重置”这个错误在前端很常见原因可能有很多。从 TCP 角度看通常是收到了 RST 包。RST 可能来自服务端主动拒绝也可能来自中间设备。排查时要看是偶发还是必现偶发可能是连接复用时服务端已关闭必现可能是服务端配置或防火墙问题。理解了四次挥手你就知道连接复用的风险点在哪客户端以为连接还在实际服务端已经关了这时发数据就会收到 RST。解决办法是客户端做连接健康检查或者缩短 Keep-Alive 空闲时间。6.3 高并发下的连接管理做 Node 服务端时连接管理是个大问题。每个连接都占用文件描述符和内存连接数上不去吞吐量就上不去。这时候要关注几个点TIME_WAIT 是否堆积、CLOSE_WAIT 是否异常、连接池是否合理复用。我的经验是短连接场景下尽量让客户端主动关闭避免服务端堆积 TIME_WAIT长连接场景下要设置合理的心跳和超时及时清理死连接。这些调整都需要你对握手挥手的过程有清晰的认识否则就是瞎调参数。7. 最后分享几个实操建议如果你正在准备面试我的建议是别只背这一道题而是把它当成一个入口顺着往下挖。三次握手可以延伸到 HTTP 连接管理、性能优化四次挥手可以延伸到服务端连接排查、高并发处理。把这些串起来你回答的就不只是一道题而是一整套知识体系。如果你已经工作了我建议你找个机会实际抓一次包看看自己负责的项目里一次请求到底经历了什么。很多时候性能瓶颈就藏在你从来没关注过的握手阶段。我自己就是这么发现某个接口慢的根因是 DNS 解析而不是后端逻辑的。最后再分享一个小技巧面试时如果被问到不会的细节别硬编。你可以说“这块我理解的是……但具体实现细节我需要在实践中再确认”。诚实加上清晰的思路比背错答案强得多。面试官看重的从来不是你记住了多少而是你能不能把知识用起来。