TCP-UDP与前端工程师需要掌握的网络基础
TCP-UDP与前端工程师需要掌握的网络基础
一句话概括
TCP(传输控制协议)和 UDP(用户数据报协议)是互联网传输层的两大基石:TCP 提供可靠、有序、拥塞控制的字节流服务(用于 HTTP、WebSocket),UDP 提供无连接、无可靠保证的数据报服务(用于 DNS 视频流/语音),理解它们的差异和适用场景是网络性能优化的前置知识。
背景与意义
为什么需要两种协议?
互联网的应用场景差异巨大:
1
2
3
4
5
6
7
8
9
HTTP 网页浏览:
"我需要每一个字节都正确到达,少一个都不行"
→ TCP 保证了数据的完整性和顺序性
→ 代价:连接建立慢(三次握手)、拥塞控制
视频直播 / 游戏:
"晚 500ms 到不如丢了重传,我要实时性!"
→ UDP 放弃可靠性,优先保证速度和实时性
→ 代价:数据包可能丢失或乱序
没有 UDP,实时应用就无法实现;没有 TCP,网页浏览和文件传输就无法保证正确性。
面试高频信号
TCP/UDP 在前端面试中出现频率相对较低,但在网络性能优化和安全方向面试中是常考题:
- “TCP 的三次握手和四次挥手是什么?”
- “TCP 和 UDP 的区别是什么?”
- “HTTP 用的是 TCP 还是 UDP?WebSocket 呢?”
概念与定义
TCP 的核心特性
1
2
3
4
5
6
7
8
9
10
11
12
13
可靠传输(Reliable Transmission)
├─ 确认机制(ACK)
├─ 超时重传
└─ 序列号(Sequence Number)
│
有序传输(Ordered Delivery)
└─ 按序号重组,丢弃重复包
│
流量控制(Flow Control)
└─ 滑动窗口(Sliding Window)
│
拥塞控制(Congestion Control)
└─ 慢启动 → 拥塞避免 → 快速恢复
UDP 的核心特性
1
2
3
4
5
6
7
8
9
10
11
12
13
无连接(Connectionless)
└─ 无需握手,直接发送
不可靠传输(Unreliable)
├─ 无 ACK
├─ 无重传
└─ 无序号(可能乱序)
无流量控制
└─ 发送方无限制,接收方缓冲区满则丢包
低开销
└─ 仅 8 字节头部(vs TCP 最小 20 字节)
最小示例
1
2
3
4
5
6
7
8
9
// WebSocket 基于 TCP(浏览器自动处理连接)
const ws = new WebSocket('wss://echo.websocket.org');
ws.onopen = () => console.log('TCP 连接已建立');
ws.onmessage = (e) => console.log(e.data);
ws.send('hello');
// WebRTC 基于 UDP(需要 ICE/STUN/TURN 处理NAT穿透)
// 语音/视频通话不走 TCP(延迟太大)
// 直播平台推流:RTMP over TCP → HLS/DASH over HTTP → QUIC/UDP
核心知识点拆解
知识点 1:TCP 三次握手(连接建立)
1
2
3
4
5
6
7
8
9
10
11
12
13
客户端 服务器
│ │
│────── SYN=1, seq=x ────────────────────→│ 第一次握手
│ "我想建立连接,我的起始序号是 x" │
│ │
│←──── SYN=1, ACK=1, seq=y, ack=x+1 ─────│ 第二次握手
│ "收到!我也准备好了,我的序号是 y,"
│ "确认收到 x+1 之前的字节"
│ │
│────── ACK=1, seq=x+1, ack=y+1 ─────────→│ 第三次握手
│ "收到确认,建立完成!"
│ │
│ 🟢 TCP 连接建立成功 │
为什么需要三次?
1
2
3
4
5
6
7
8
9
如果是两次握手:
客户端 ──── SYN ────────────────────────→ 服务器
(SYN 丢了,客户端不知道,服务器以为连接建立了)
(服务器开始分配资源,客户端永远不会发数据 → 浪费)
三次握手的本质:
客户端确认:自己能发 + 服务器能收 ✅
服务器确认:自己能发 + 客户端能收 ✅
必须三次,两次不够,四次浪费
💡 对前端的影响:每次 HTTP/1.1 的新连接都需要三次握手 ≈ 增加 1-3 个 RTT(30-200ms)。HTTP/2 和 WebSocket 可以复用连接避免重复握手。
知识点 2:TCP 四次挥手(连接关闭)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
客户端 服务器
│ │
│────── FIN=1, seq=u ────────────────────→│ 第一次挥手
│ "我发完了,想关闭我的写通道" │
│ │
│←──── ACK=1, ack=u+1 ────────────────────│ 第二次挥手
│ "收到,但我可能还有数据要发" │
│ (服务器继续发送剩余数据) │
│ │
│←──── FIN=1, seq=w ────────────────────│ 第三次挥手
│ "我也发完了,关闭我的写通道" │
│ │
│────── ACK=1, ack=w+1 ──────────────────→│ 第四次挥手
│ "收到,连接正式关闭" │
│
│ 客户端等待 2MSL 后释放资源 │
│ (防止最后的 ACK 丢失) │
为什么挥手需要四次,但握手只需要三次?
1
2
3
4
握手:双方同时开始建立连接(SYN + SYN-ACK + ACK)
挥手:双方分别关闭自己的写通道(FIN + ACK + FIN + ACK)
服务器收到第一次 FIN 后,可能还有数据要发 → 先回 ACK
发完数据后才发 FIN → 多了这步导致四次
💡 对前端的影响:关闭 WebSocket 连接时,如果服务器不主动 close,客户端的 FIN 会等很久才收到响应。
知识点 3:TCP 拥塞控制
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
慢启动(Slow Start):
cwnd(拥塞窗口)从 1 个 MSS 开始
每收到一个 ACK,cwnd += 1 MSS
→ 指数增长,直到达到慢启动阈值(ssthresh)
拥塞避免(Congestion Avoidance):
cwnd 达到阈值后,改为线性增长
每 RTT,cwnd += 1 MSS
快速重传(Fast Retransmit):
收到 3 个重复 ACK → 立即重传丢失的包
→ cwnd 减半,进入快速恢复
快速恢复(Fast Recovery):
重传完成后,回到拥塞避免阶段
知识点 4:HTTP/WebSocket/QUIC 各用什么协议?
| 应用 | 传输层 | 理由 |
|---|---|---|
| HTTP/1.1 | TCP | 需要可靠的请求-响应 |
| HTTP/2 | TCP | 多路复用需要有序帧传输 |
| HTTP/3 | QUIC (UDP) | 解决 TCP 队头阻塞,基于 UDP 自建可靠传输 |
| WebSocket | TCP | 双向流需要可靠传输 |
| WebRTC | UDP | 实时音视频不需要等待重传 |
| DNS (通常) | UDP | 速度快,不需要严格可靠(也有 TCP fallback) |
| QUIC | UDP | HTTP/3 的底层协议 |
实战案例
案例一:WebSocket 连接的生命周期
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
class WebSocketConnection {
constructor(url) {
this.ws = new WebSocket(url);
this.ws.onopen = this.onOpen.bind(this);
this.ws.onclose = this.onClose.bind(this);
this.ws.onerror = this.onError.bind(this);
this.ws.onmessage = this.onMessage.bind(this);
}
onOpen() {
console.log('🟢 WebSocket 连接建立(底层:TCP 三次握手已完成)');
// 发送心跳
this.heartbeat = setInterval(() => {
if (this.ws.readyState === WebSocket.OPEN) {
this.ws.send(JSON.stringify({ type: 'ping' }));
}
}, 30000);
}
onClose(event) {
console.log(`🔴 连接关闭(code: ${event.code})`);
// WebSocket Close Code:
// 1000 = 正常关闭
// 1001 = 服务器关闭
// 1006 = 连接异常断开(网络问题)
clearInterval(this.heartbeat);
// 自动重连策略
setTimeout(() => this.reconnect(), 1000);
}
onError(error) {
console.error('⚠️ WebSocket 错误:', error);
// 底层可能是 TCP 连接失败或 TLS 握手失败
}
reconnect() {
console.log('尝试重新连接...');
// 实现指数退避重连
}
}
案例二:理解 QUIC/UDP 在现代 Web 中的角色
1
2
3
4
5
6
7
8
HTTP/1.1 → TCP (3次握手 + TLS 握手 ≈ 2-3 RTT)
HTTP/2 → TCP (TLS 1.3 合并握手 ≈ 1 RTT)
HTTP/3 → QUIC/UDP (0-RTT 建立连接!)
HTTP/3 的 0-RTT 优势:
用户首次访问:需要 1 RTT(TLS 认证)
用户再次访问:0 RTT!直接发送加密数据
(基于之前会话的密钥恢复)
底层原理
TCP 滑动窗口(Flow Control)
1
2
3
4
5
6
7
8
9
10
11
发送方窗口(已发送未确认 | 未发送可发 | 不能发)
↓ ↓ ↓
[ 已发送-未确认 ][ 已发送-未确认 ][ 未发送 ]
←────────── 接收方窗口大小 ──────────→
ACK 收到后,窗口向右滑动
如果接收方缓冲区满了:
→ 窗口大小变为 0
→ 发送方停止发送(Zero Window)
→ 接收方腾出空间后发送 Window Update
→ 发送方恢复发送
高频面试题解析
Q1:TCP 的三次握手和四次挥手是什么?
参考答案: 三次握手:SYN → SYN-ACK → ACK。目的是验证双方都能正常收发。 四次挥手:FIN → ACK → FIN → ACK。目的是确保双方都发完了数据。
Q2:TCP 和 UDP 的核心区别?
参考答案:
| 特性 | TCP | UDP |
|---|---|---|
| 连接性 | 面向连接 | 无连接 |
| 可靠性 | 可靠传输 | 不可靠 |
| 有序性 | 有序 | 无序 |
| 流量控制 | 有滑动窗口 | 无 |
| 拥塞控制 | 有 | 无 |
| 头部开销 | 20-60 字节 | 8 字节 |
| 速度 | 较慢(三次握手+重传) | 快速(无握手) |
| 使用场景 | HTTP, WebSocket, 文件传输 | DNS, 直播, 游戏 |
Q3:HTTP/3 为什么改用 UDP(QUIC)?
参考答案: TCP 存在传输层队头阻塞:如果一个 TCP 包丢失,所有 HTTP 流都要等这个包重传完成才能继续。QUIC 基于 UDP 自己实现可靠传输,但将拥塞控制放在流级别而非连接级别——所以一个流丢包只影响那个流,其他流不受影响。
Q4:DNS 用 TCP 还是 UDP?
参考答案: 主要是 UDP(端口 53),速度快。但有三种情况会切换到 TCP:
- 响应数据超过 512 字节(DNS 响应截断)
- DNS 区域传输(AXFR)
- 客户端发起 TCP 探测失败后的 fallback
总结与扩展
核心要点
- TCP = 可靠 + 有序 + 流量控制 + 拥塞控制(用于 HTTP/WebSocket)
- UDP = 快速 + 无连接(用于 DNS/直播/QUIC)
- 三次握手 = SYN + SYN-ACK + ACK,验证双方收发能力
- 四次挥手 = FIN + ACK + FIN + ACK,确保数据发完再关
- HTTP/3 基于 QUIC/UDP,解决了 TCP 队头阻塞
延伸学习方向
- QUIC 协议详解:0-RTT、重传机制、连接迁移
- WebRTC 架构:ICE + STUN + TURN + DTLS + SRTP
- HTTP/3:QUIC 的完整实现
- TCP BBR 拥塞控制算法:Google 的新拥塞控制算法
相关主题
本文由作者按照 CC BY 4.0 进行授权