TCP 与 UDP 同属传输层协议,但设计哲学截然不同:TCP 是"面向连接的可靠字节流 ",UDP 是"无连接的不可靠数据报"。
一、在协议栈中的位置
┌─────────────┐
│ 应用层 │ HTTP / FTP / DNS / QUIC
├─────────────┤
│ 传输层 │ TCP │ UDP
├─────────────┤─────────────┤
│ 网络层 │ IP(负责寻址和路由)
├─────────────┤
│ 链路层 │ Ethernet / Wi-Fi
└─────────────┘
- 网络层 (IP)只负责把数据包送到目标主机,不保证送达、不保证顺序、不保证不重复。
- 传输层在此基础上做增强:TCP 通过复杂机制实现"可靠传输",UDP 则几乎原样暴露 IP 的能力。
二、报文结构原理
1. UDP 数据报(Datagram)
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
├─────────────────────┴─────────────────────┤
│ Source Port │ Destination Port │ ← 各 16 bit
├─────────────────────┴─────────────────────┤
│ Length │ Checksum │ ← 各 16 bit
├─────────────────────┴─────────────────────┤
│ Data (Payload) │ ← 应用层数据
└───────────────────────────────────────────┘
头部固定 8 字节,极其简单:
- Source Port / Destination Port:标识发送/接收进程(端口号)
- Length:整个 UDP 报文(头部+数据)的长度
- Checksum:校验和(IPv4 中可选为 0,IPv6 中强制要求)
核心特点 :UDP 把应用层数据包直接封装进 IP 包,保留报文边界------发送方调用一次 sendto() 发送 100 字节,接收方一次 recvfrom() 必定收到 100 字节(或丢包),不会出现"半个包"或"两个包粘在一起"的情况。
2. TCP 报文段(Segment)
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
├─────────┴─────────┴───────────────────────────────────────────┤
│ Source Port │ Destination Port │ ← 32 bit
├───────────────────┴───────────────────────────────────────────┤
│ Sequence Number │ ← 32 bit
├───────────────────────────────────────────────────────────────┤
│ Acknowledgment Number │ ← 32 bit
├─────────┴─┬─┴─────┴─────────┬─────────────────────────────────┤
│ Data │ Resv │U|A|P|R|S│ Window Size │ ← 32 bit
│ Offset │ 6bit │R|C|S|S|Y|I│ (16 bit) │
│ (4 bit) │ │G|K|H|T|N|N│ │
├───────────┴───────┴───────────┴─────────────────────────────────┤
│ Checksum │ Urgent Pointer │ ← 32 bit
├───────────────────────────────────────────────────────────────┤
│ Options (0-40 bytes) │
├───────────────────────────────────────────────────────────────┤
│ Data │
└───────────────────────────────────────────────────────────────┘
头部至少 20 字节,包含大量控制字段:
- Sequence Number(序列号):标识本报文段发送数据的第一个字节序号,解决乱序和重复问题。
- Acknowledgment Number(确认号):期望收到的下一个字节的序号,用于可靠传输。
- Flags(标志位):SYN、ACK、FIN、RST、PSH、URG,控制连接状态。
- Window Size(窗口大小):接收方缓冲区剩余空间,用于流量控制。
- Checksum:覆盖头部、数据和伪头部(IP 地址等),强制启用。
三、TCP 核心机制原理
1. 连接管理:三次握手与四次挥手
三次握手(建立连接)的本质是交换初始序列号(ISN):
客户端 服务器
| |
| ① SYN=1, seq=x |
| ---------------------> |
| |
| ② SYN=1, ACK=1, |
| seq=y, ack=x+1 |
| <--------------------- |
| |
| ③ ACK=1, seq=x+1, |
| ack=y+1 |
| ---------------------> |
| |
[连接建立,开始传输数据]
每一步的含义:
| 步骤 | 名称 | 含义 |
|---|---|---|
| ① | SYN | 客户端:"我想连接你,我的初始序列号是 x" |
| ② | SYN-ACK | 服务器:"收到,我同意连接,我的初始序列号是 y,期待你下一个序列号是 x+1" |
| ③ | ACK | 客户端:"收到,期待你下一个序列号是 y+1" |
为什么需要序列号?
- 防止历史连接请求干扰(延迟到达的旧 SYN 会被拒绝)。
- 为后续可靠传输、按序重组、去重提供基准。
为什么是三次,不是两次?
两次不够------如果只用两次握手:
- 客户端发了一个 SYN,但因为网络延迟,这个包很久之后才到达服务器。
- 服务器以为是新的连接请求,回复 SYN-ACK 并分配资源。
- 但客户端早已放弃,不会回复 ACK,也不会发送数据。
- 服务器就会一直等待,白白占用资源(这就是"半开连接"攻击的原理)。
三次握手 确保双方都能确认:
客户端确认:服务器收到了我的请求,且能发数据给我。
服务器确认:客户端收到了我的同意,且能发数据给我。
四次挥手(断开连接) :
TCP 是全双工,双方各有一个发送通道,需要分别关闭。客户端发送 FIN 只关闭自己的发送通道,但还能接收服务器数据;服务器数据发完后也发 FIN,双方彻底关闭。
客户端 服务器
| |
| ① FIN=1, seq=u |
| ---------------------> |
| |
| ② ACK=1, seq=v, |
| ack=u+1 |
| <--------------------- |
| |
| [服务器可能还有数据要发] |
| |
| ③ FIN=1, ACK=1, |
| seq=w, ack=u+1 |
| <--------------------- |
| |
| ④ ACK=1, seq=u+1, |
| ack=w+1 |
| ---------------------> |
| |
[等待 2MSL 后彻底关闭] [收到 ACK 后立即关闭]
每一步的含义
| 步骤 | 名称 | 含义 |
|---|---|---|
| ① | FIN | 客户端:"我没数据要发了,我想关闭发送通道" |
| ② | ACK | 服务器:"收到你的关闭请求" |
| ③ | FIN | 服务器:"我也没数据要发了,我也关闭发送通道" |
| ④ | ACK | 客户端:"收到,我也关闭" |
为什么是四次,不是三次?
因为 TCP 连接是全双工的------双方各有一条发送通道和一条接收通道。
- 客户端说"我发完了"(①),但服务器可能还有数据要发给客户端。
- 所以服务器先确认收到关闭请求(②),等自己数据发完后再说"我也发完了"(③)。
- 最后客户端确认(④),双方才彻底关闭。
如果是三次挥手:服务器把 ACK 和 FIN 合并成一次发送,前提是"服务器也没有数据要发"。但现实中,服务器往往还有数据要回传,所以 ACK 和 FIN 通常不能合并,必须是四次。
TIME_WAIT(2MSL)
客户端在发送最后一个 ACK 后,不会立即关闭,而是进入 TIME_WAIT 状态,等待 2MSL(Maximum Segment Lifetime,报文最大生存时间,通常 2-4 分钟)。
为什么?
- 防止最后一个 ACK 丢失:如果 ACK 丢了,服务器会重发 FIN,客户端还在等待就能重新回复 ACK。
- 防止旧连接的数据包干扰新连接:确保网络中所有旧连接的报文都消失,避免下一个使用相同端口的新连接收到"幽灵数据"。
2. 可靠传输原理
TCP 在不可靠的 IP 网络上实现了四个保证:
(1)按序交付与去重
- 每个字节都有序列号。
- 接收方通过序列号重组数据,丢弃重复报文(超时重传可能导致重复)。
(2)确认应答(ACK)与超时重传
- 接收方累积确认:收到 1-1000 字节后,回复 ACK=1001(表示期待下一个字节是 1001)。
- 发送方启动重传定时器(RTO),超时未收到 ACK 则重传。
- RTO 不是固定的:基于 RTT(往返时间)动态计算(Jacobson/Karels 算法),网络波动时自适应调整。
(3)滑动窗口:批量发送提高效率
- 发送方不必等每个 ACK 才发下一个包,可以连续发送多个。
- 窗口大小 = min(接收方窗口 rwnd, 拥塞窗口 cwnd)。
- 通过 ACK 的确认号"滑动"窗口,确认过的数据移出窗口。
(4)选择确认(SACK,可选)
- 传统 ACK 只能确认"连续收到的最大序号"。
- SACK 允许接收方告知"我收到了 1-1000 和 2001-3000,但 1001-2000 丢了",发送方只重传丢失部分,提高效率。
3. 流量控制原理
目的:防止发送方过快把接收方缓冲区撑爆。
机制:接收方在 ACK 中携带 Window Size(当前接收缓冲区剩余空间)。发送方发送的数据量不能超过这个值。
零窗口问题 :当接收方缓冲区满,会发送 Window=0 的 ACK。发送方停止发送,启动持续定时器(Persist Timer),定期探测窗口是否恢复(发送 1 字节窗口探测报文),避免死锁。
4. 拥塞控制原理
目的 :防止发送方过快把网络路由器/链路撑爆。
核心变量 :拥塞窗口 cwnd,发送方实际发送窗口 = min(rwnd, cwnd)。
四个阶段:
| 阶段 | 行为 | 触发条件 |
|---|---|---|
| 慢启动(Slow Start) | cwnd 从 1 MSS 开始,每收到一个 ACK 增加 1 MSS,指数增长 | 连接刚建立或超时重传后 |
| 拥塞避免(Congestion Avoidance) | cwnd 每 RTT 增加 1 MSS,线性增长 | cwnd 达到慢启动阈值 ssthresh |
| 快重传(Fast Retransmit) | 收到 3 个重复 ACK,立即重传丢失报文,不等 RTO 超时 | 中间报文丢失,后续报文触发重复 ACK |
| 快恢复(Fast Recovery) | ssthresh = cwnd/2,cwnd = ssthresh + 3 MSS,然后线性增长 |
快重传后 |
超时重传 vs 快重传:
- 超时:网络严重拥塞,cwnd 直接降到 1,重新慢启动。
- 快重传:只是个别丢包,cwnd 减半,不回到 1,更快恢复。
四、UDP 的"无原理"原理
UDP 没有连接、没有重传、没有窗口、没有拥塞控制,它的原理就是"不做任何事":
应用层数据 ──> UDP 加 8 字节头部 ──> IP 层封装 ──> 网络发送
IP 层提供什么,UDP 就原样暴露给应用层:
- IP 可能丢包 → UDP 也丢包
- IP 可能乱序 → UDP 也乱序
- IP 可能重复 → UDP 也重复
UDP 唯一的增强:
- 端口号:让 IP 能把数据交给正确的进程。
- 校验和:检测数据在传输中是否被损坏(但只是丢弃,不重传)。
五、两者数据流向对比
TCP 数据流
应用层 write()
│
▼
┌─────────────────────────────────────┐
│ 发送缓冲区(Send Buffer) │
│ [已发送已确认][已发送未确认][待发送] │
└─────────────────────────────────────┘
│
▼ 滑动窗口控制
TCP 分段(加头部:Seq, ACK, Window)
│
▼
IP 封装 → 路由 → 网络传输
│
▼
接收方 IP 解封 → 接收缓冲区 → 按 Seq 重组 → 应用层 read()
UDP 数据流
应用层 sendto()
│
▼
UDP 加头部(Port, Length, Checksum)
│
▼
IP 封装 → 路由 → 网络传输
│
▼
接收方 IP 解封 → 根据目的端口找进程 → 应用层 recvfrom()
(无缓冲区、无重组、无确认)
六、核心对比
| 特性 | TCP | UDP |
|---|---|---|
| 连接方式 | 面向连接(三次握手建立,四次挥手断开) | 无连接(直接发送,不管对方在不在) |
| 可靠性 | 可靠:丢包重传、按序到达、去重 | 不可靠:发出去就不管了,不保证到达 |
| 传输顺序 | 保证数据按发送顺序到达 | 不保证顺序,可能乱序 |
| 流量控制 | 有(滑动窗口机制) | 无 |
| 拥塞控制 | 有(慢启动、拥塞避免、快重传等) | 无 |
| 头部开销 | 大(20 字节固定头部 + 可选选项) | 极小(8 字节头部) |
| 传输效率 | 相对较低(建立连接、确认应答耗时) | 极高(无额外握手和确认) |
| 数据边界 | 字节流,无消息边界(可能出现粘包) | 保留报文边界,每个包独立 |
七、典型应用场景
| 场景 | 协议 | 原因 |
|---|---|---|
| 网页浏览(HTTP/HTTPS) | TCP | 页面完整性必须保证,一个字节都不能错 |
| 文件传输(FTP) | TCP | 文件损坏不可接受 |
| 邮件(SMTP/POP3/IMAP) | TCP | 邮件内容必须完整送达 |
| 数据库访问 | TCP | 数据一致性要求极高 |
| 在线视频直播 | UDP | 实时性优先,偶尔花屏比重播更能接受 |
| 语音通话(VoIP) | UDP | 延迟敏感,重传旧语音包毫无意义 |
| 在线游戏 | UDP | 玩家位置信息需要毫秒级同步,丢一帧不影响大局 |
| DNS 查询 | UDP | 请求小、响应小,一次往返搞定,TCP 握手反而拖沓 |
八、常见问题
1. TCP 和 UDP 的区别?
| 维度 | TCP | UDP |
|---|---|---|
| 连接 | 面向连接(三次握手) | 无连接 |
| 可靠性 | 可靠(确认、重传、去重) | 不可靠 |
| 有序性 | 保证顺序 | 不保证 |
| 头部开销 | 20+ 字节 | 8 字节 |
| 流量/拥塞控制 | 有 | 无 |
| 适用场景 | 文件传输、网页、邮件 | 直播、游戏、DNS、VoIP |
2. 三次握手的过程和目的?
SYN → SYN-ACK → ACK。
目的: 确认双方的发送和接收能力都正常。两次握手无法防止历史连接请求导致的资源浪费(半开连接)。
3. 四次挥手的过程?为什么不是三次?
FIN → ACK → FIN → ACK。
原因: TCP 是全双工,双方的发送通道需要分别独立关闭。服务器收到客户端的 FIN 后,可能还有数据要发送,所以 ACK 和 FIN 不能合并,必须分两次。
4. 为什么客户端最后要等待 2MSL?
- 防止最后一个 ACK 丢失,服务器重发 FIN 时客户端还能响应。
- 确保网络中旧连接的报文全部消失,避免干扰新连接。
5. TCP 如何保证可靠性?
四大机制:
- 序列号与确认应答(ACK):丢包后重传。
- 超时重传:RTT 动态计算,超时未收到 ACK 则重发。
- 滑动窗口:批量发送,提高效率。
- 流量控制(接收窗口)+ 拥塞控制(慢启动、拥塞避免、快重传、快恢复)。
6. 什么是 TCP 粘包?怎么解决?
TCP 是字节流协议,没有消息边界,多个小数据包可能被合并成一个大的 TCP 报文发送。
解决方法:
- 固定长度消息
- 特殊分隔符(如 \n)
- 消息头中携带长度字段(最常用,如 HTTP Content-Length)
7. 流量控制和拥塞控制的区别?
- 流量控制:端到端,防止发送方把接收方的缓冲区打满(通过接收窗口 rwnd)。
- 拥塞控制:全局网络,防止发送方把网络拥塞(通过拥塞窗口 cwnd,慢启动、AIMD 等算法)。
8. TCP 的拥塞控制算法有哪些?
- 慢启动(Slow Start):cwnd 指数增长。
- 拥塞避免(Congestion Avoidance):cwnd 线性增长。
- 快重传(Fast Retransmit):收到 3 个重复 ACK 立即重传,不等超时。
- 快恢复(Fast Recovery):快重传后 cwnd 减半,而不是降到 1。
9. UDP 如何实现可靠传输?
在应用层自己实现:
- 自定义序列号 + 确认应答机制
- 应用层超时重传
- 引入滑动窗口做流量控制
- 典型例子:QUIC(基于 UDP,在应用层实现可靠性)、KCP。
10. 什么是 SYN Flood 攻击?怎么防御?
攻击者伪造大量 SYN 请求但不回复 ACK,服务器半连接队列被占满,无法服务正常用户。
防御:
- SYN Cookie:不分配资源,用加密 Cookie 验证合法性。
- 缩短 SYN Timeout。
- 限制半连接队列长度。
11. TCP 和 UDP 的头部结构是怎样的?
- TCP 头部:源端口(2) + 目的端口(2) + 序列号(4) + 确认号(4) + 数据偏移(1) + 标志位(1) + 窗口(2) + 校验和(2) + 紧急指针(2) + 选项 = 至少 20 字节。
- UDP 头部:源端口(2) + 目的端口(2) + 长度(2) + 校验和(2) = 固定 8 字节。
12. 为什么 DNS 使用 UDP?什么情况下用 TCP?
- 默认用 UDP:查询和响应通常很小(< 512 字节),一次往返即可,UDP 开销小、速度快。
- 用 TCP 的情况:响应超过 512 字节(DNS 截断标志 TC=1)、区域传输(Zone Transfer,主从 DNS 同步)、DNSSEC 等。
九、总结
- TCP 可靠面向连接,UDP 快速无连接
- 要完整、有序、不丢包------选 TCP
- 要快、低延迟、能承受少量丢包------选 UDP。