网络 & HTTP
TCP 握手与挥手
TCP 是面向连接的协议,数据传输前必须先建立连接,结束后必须释放连接。建立连接用三次握手,释放连接用四次挥手。这两个过程决定了每一次网络请求的起止延迟。
一、为什么需要握手
TCP 需要保证双方都具备发送和接收的能力,三次握手是能确认这一点的最少次数:
- 一次握手:只有客户端确认了服务器能收,服务器不知道客户端能收
- 两次握手:服务器确认了客户端能收,但客户端不知道服务器能收
- 三次握手:双方都确认了对方的收发能力,连接可以可靠建立
二、三次握手(建立连接)
客户端 服务器
| |
|------- SYN (seq=x) -------->| 第一次:请求连接
| |
|<-- SYN+ACK (seq=y, ack=x+1)-| 第二次:同意连接
| |
|------- ACK (ack=y+1) ------>| 第三次:确认连接
| |
|======== 连接建立,可以传输数据 ========|第一次握手:客户端 → 服务器(SYN)
客户端发送 SYN 报文,请求建立连接:
SYN = 1:表示这是一个连接请求seq = x:客户端的初始序列号(随机生成,防止历史报文干扰)
此时客户端进入 SYN_SENT 状态。
第二次握手:服务器 → 客户端(SYN + ACK)
服务器收到请求后,回复一个 SYN + ACK 报文:
SYN = 1:服务器也请求建立连接ACK = 1:确认收到客户端的 SYNack = x + 1:期望收到客户端下一个序号为 x+1 的报文seq = y:服务器的初始序列号
此时服务器进入 SYN_RCVD 状态。
第三次握手:客户端 → 服务器(ACK)
客户端收到服务器的 SYN + ACK 后,发送最后一个 ACK:
ACK = 1:确认收到服务器的 SYNack = y + 1:期望收到服务器下一个序号为 y+1 的报文
双方进入 ESTABLISHED 状态,连接正式建立,HTTP 请求可以在此连接上发送。
三、四次挥手(释放连接)
连接的释放需要四步,因为 TCP 是全双工的——客户端和服务器各自的数据通道需要独立关闭。
客户端 服务器
| |
|------- FIN (seq=u) -------->| 第一次:客户端请求关闭
| |
|<------ ACK (ack=u+1) -------| 第二次:服务器确认(可能还有数据要发)
| |
|<------ FIN (seq=v) ---------| 第三次:服务器数据发完,请求关闭
| |
|------- ACK (ack=v+1) ------>| 第四次:客户端确认
| |
|== 客户端等待 2MSL 后彻底关闭 ==|第一次挥手:客户端 → 服务器(FIN)
客户端发送 FIN 报文,表示"我的数据发完了,请求关闭"。客户端进入 FIN_WAIT_1 状态。
第二次挥手:服务器 → 客户端(ACK)
服务器确认收到 FIN,但此时服务器可能还有数据未发完,连接进入半关闭状态。客户端进入 FIN_WAIT_2 状态,继续接收服务器的数据。
第三次挥手:服务器 → 客户端(FIN)
服务器数据全部发完后,主动发送 FIN,表示"我也可以关闭了"。服务器进入 LAST_ACK 状态。
第四次挥手:客户端 → 服务器(ACK)
客户端发送最后的 ACK,进入 TIME_WAIT 状态,等待 2MSL(两倍报文最大生存时间,约 2 分钟)后关闭,确保服务器收到了最后的 ACK。
四、TIME_WAIT 的作用
客户端关闭前等待 2MSL 有两个目的:
- 确保服务器收到最后的 ACK:如果 ACK 丢失,服务器会重发 FIN,客户端在 2MSL 内还能再次响应。
- 让旧连接的报文消散:防止本次连接残留的延迟报文被下一个新连接误认。
服务端大量 TIME_WAIT 通常说明短连接太多,可以通过开启 Keep-Alive 复用连接来缓解。
五、前端角度的影响
每次建立新的 TCP 连接都要经历三次握手,这意味着额外的延迟(通常 1.5× RTT)。
减少握手次数的常用手段:
| 手段 | 原理 |
|---|---|
Connection: keep-alive | HTTP/1.1 默认开启,复用 TCP 连接,避免重复握手 |
| HTTP/2 | 单个连接上多路复用,多个请求共享一次握手 |
| HTTP/3(QUIC) | 基于 UDP,首次连接 1-RTT,恢复连接 0-RTT |
<link rel="preconnect"> | 提前与目标域名完成 TCP 握手和 TLS 协商 |
| CDN | 用户就近连接节点,缩短 RTT,握手延迟更低 |
<!-- 提前与第三方域名完成握手,页面请求资源时不再等待 -->
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="dns-prefetch" href="https://cdn.example.com" />