TCP VS UDP

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。
相关推荐
SendTomo6 小时前
SendTomo稳定传输三大核心策略
javascript·网络协议·webrtc·html5·p2p
CANWeb冗余现场总线6 小时前
2毫秒扫描周期以太网Modbus TCP UDP高速通信:西门子SMART PLC+CANWeb现场总线远程扩展IO模块
网络协议·tcp/ip·udp
阿pin6 小时前
HTTP 与 HTTPS 详解
网络协议·http·https
阿pin7 小时前
HTTP 协议的演进
网络·网络协议·http
bksczm7 小时前
Linux之网络层协议(IP协议)
linux·网络·tcp/ip
跨境技工小黎7 小时前
IPFoxy动态住宅IP实测:做数据采集和爬虫可行吗?
爬虫·网络协议·tcp/ip
秋田君7 小时前
QT_一个UDP通信程序(单播+广播)
开发语言·qt·udp
Mortalbreeze7 小时前
深入理解TCP协议(一):TCP报文格式详解
linux·服务器·网络·tcp/ip
SendTomo8 小时前
send.wang(私传网)P2P直连加速文件传输
网络·网络协议·webrtc·html5·p2p