TCP / UDP(传输层协议)
1. 定义
TCP 和 UDP 是传输层 (Transport Layer) 的两个核心协议,负责把数据从”一台机器上的某个程序”送到”另一台机器上的某个程序”(靠端口区分程序)。它们都跑在网络层 IP 之上,socket 编程时你选的就是它俩之一。
- TCP (Transmission Control Protocol):面向连接、可靠、有序的字节流协议。传前先握手建连,保证数据不丢、不乱、不重。
- UDP (User Datagram Protocol):无连接、不可靠的数据报协议。写上地址直接发,不管对方收没收到。
一句话概括:TCP 追求”送到且送对”,UDP 追求”送得快”。可靠性和速度的取舍,是理解这两个协议的钥匙。
类比:TCP 像打电话——先拨通(握手),全程确认对方在听、听清了、按顺序说,讲完礼貌挂断。UDP 像寄明信片——写上地址扔进邮筒就走,不保证送到、不保证先寄的先到,但省事又快。
2. IP、TCP/UDP、端口的分工(先理清层次)
应用层 你的数据(HTTP请求、一段语音…)
│
传输层 TCP / UDP ── 加上端口,负责"送到哪个程序" + (TCP)可靠性
│
网络层 IP ── 加上IP地址,负责"送到哪台机器" + 路由
│
链路层 网卡 ── 变成电信号/无线信号发出去
- IP 负责”机器到机器”:只管把包尽力送到目标 IP,不保证到、不保证顺序(IP 本身也是”不可靠”的)。
- TCP/UDP 负责”程序到程序”:靠端口找到具体程序。TCP 额外在 IP 的”不可靠”之上,用一套机制造出了可靠;UDP 则基本原样用 IP 的”尽力而为”。
关键认知:可靠性不是 IP 给的,是 TCP 自己在上层实现的。 所以同样跑在会丢包的 IP 上,TCP 能保证送达,UDP 不能——区别全在传输层这一层做了多少事。
3. 核心对比表
| 维度 | TCP | UDP |
|---|---|---|
| 连接 | 面向连接(先三次握手) | 无连接(直接发) |
| 可靠性 | 可靠:确认重传、保证到达 | 不可靠:丢了就丢了 |
| 顺序 | 保证按发送顺序交付 | 不保证顺序 |
| 数据形态 | 字节流(无消息边界,会粘包) | 数据报(一发一收,有边界) |
| 流量控制 | 有(滑动窗口) | 无 |
| 拥塞控制 | 有(网络堵了会自动降速) | 无(该发还发,可能加剧拥塞) |
| 头部开销 | 20 字节起,较大 | 8 字节,很小 |
| 速度 | 较慢(握手、确认、重传的代价) | 快 |
| 一对多 | 不支持(点对点连接) | 支持(可广播/多播) |
| 典型应用 | HTTP、HTTPS、SSH、数据库、文件传输 | DNS、视频直播、在线游戏、语音、DHCP |
选型口诀:数据不能错 → TCP;实时性 > 完整性 → UDP。 网页、转账、传文件用 TCP(错一个字节都不行);直播、游戏、语音用 UDP(丢一两帧无所谓,卡顿才要命)。
4. TCP 怎么做到”可靠”
IP 会丢包、乱序、重复,TCP 靠这几套机制把它变可靠:
| 机制 | 解决什么 | 原理 |
|---|---|---|
| 序列号 (Seq) | 乱序、重复 | 每个字节都编号,接收方按号重排、去重 |
| 确认应答 (ACK) | 丢包检测 | 收到数据就回一个 ACK 告诉对方”收到到哪了” |
| 超时重传 | 丢包恢复 | 发出去一段时间没收到 ACK,就重发 |
| 滑动窗口 | 流量控制 | 接收方告诉发送方”我还能收多少”,避免发太快撑爆对方 |
| 拥塞控制 | 网络拥堵 | 慢启动+拥塞避免,网络堵了主动降速,通畅了再加速 |
流量控制 vs 拥塞控制别混:流量控制是照顾”接收方”(别把对方缓冲区塞爆),拥塞控制是照顾”整个网络”(别把中间的路由器/链路堵死)。
5. 三次握手 & 四次挥手
建连和断连的过程(socket 的 connect/close 底下就是它):
三次握手(建立连接):
客户端 ──── SYN ────▶ 服务端 "我想连,我的序号 x"
客户端 ◀── SYN+ACK ── 服务端 "行,我序号 y,确认收到你的 x+1"
客户端 ──── ACK ────▶ 服务端 "确认收到你的 y+1,开始传"
连接建立 ✓
为什么三次?要双方都确认”自己能发能收、对方也能发能收”。两次不够(服务端无法确认客户端能收到自己的包),三次刚好,四次多余。
四次挥手(关闭连接):
主动方 ──── FIN ────▶ 被动方 "我没数据要发了"
主动方 ◀─── ACK ───── 被动方 "知道了"(但我可能还有数据要发)
主动方 ◀─── FIN ───── 被动方 "我也发完了"
主动方 ──── ACK ────▶ 被动方 "好,拜拜" → 进入 TIME_WAIT 等一会
为什么四次?连接是双向的,一方说”我发完了”,另一方可能还有数据要发,所以中间的 ACK 和 FIN 不能合并,要两个方向各关一次。TIME_WAIT 的由来见
socket.md第 6 节。
6. TCP 报文头关键字段(抓包时会看到)
| 源端口 | 目标端口 | ← 定位程序
| 序列号 Seq | ← 本段数据第一个字节的编号
| 确认号 Ack | ← 期望对方下次发的序号
| 标志位 SYN/ACK/FIN/RST/PSH | ← 控制连接状态
| 窗口大小 | ← 流量控制,"我还能收多少"
常见标志位:SYN(建连) / ACK(确认) / FIN(正常关闭) / RST(强制重置连接) / PSH(尽快交给应用)。
RST值得记:它是”粗暴挂断”,比如你连一个没程序监听的端口,对方会回RST——这也是端口扫描判断端口开没开的依据之一(见网络安全相关笔记)。
7. UDP:简单到什么程度
UDP 头只有 8 字节(源端口、目标端口、长度、校验和),没有握手、没有序号、没有确认、没有重传。发出去就不管了。
正因为”简单”,UDP 反而灵活——需要可靠性的场景可以在应用层自己实现轻量的可靠机制,只保留需要的部分,不像 TCP 全都强塞给你。典型代表:
- QUIC(HTTP/3 的底座):跑在 UDP 上,自己实现了可靠、有序、加密、多路复用,避开了 TCP 的队头阻塞问题。
- 实时音视频 (WebRTC):用 UDP,丢包就丢,不重传(重传来的旧画面没意义)。
这解释了一个看似矛盾的现象:最新的 HTTP/3 反而从 TCP 换回了 UDP。 不是 UDP 更好,而是 TCP 的某些”包办”机制(如严格有序导致的队头阻塞)在现代场景成了负担,干脆用 UDP 打底、自己在上面按需造轮子。
8. 常见坑 / 误区
- ❌ “UDP 不可靠所以没人用” → 错,DNS、视频、游戏、DHCP 全靠它;可靠性可在应用层按需补
- ❌ “TCP 保证数据一定送达” → 只在连接正常时保证;网线拔了、对方宕机,TCP 也无能为力,只是会告诉你连接断了
- ❌ “一次 send 对应一次 recv” → 只有 UDP 成立;TCP 是字节流,会粘包/拆包,要自己划分消息边界(见
socket.md第 10 节) - ❌ “三次握手是传数据” → 握手只建连接不传业务数据,数据在握手完成后才发
- ❌ “端口是 TCP/UDP 独有的东西” → TCP 和 UDP 各有自己独立的 65536 个端口,TCP 的 80 和 UDP 的 80 是两个不同的东西
- ❌ “TCP 更先进所以永远比 UDP 好” → 场景决定,实时场景 UDP 更合适;HTTP/3 就换回了 UDP
- ❌ “拥塞控制 = 流量控制” → 前者照顾整个网络,后者照顾接收方,两码事(见第 4 节)
9. 延伸阅读 / 关联概念
- Socket — 应用程序通过 socket 选择用 TCP 还是 UDP,代码级对照;见
socket.md - 服务器原理 — 服务器的请求-响应大多建立在 TCP 之上;见
server.md - HTTP / HTTPS — 最典型的 TCP 应用层协议;见
http.md - DNS 解析绑定 — 典型的 UDP 应用;见
dns-binding.md - ping / traceroute — 基于 ICMP(既非 TCP 也非 UDP,另一种网络层协议);见
ping.md - QUIC / HTTP/3 — 跑在 UDP 上、自己实现可靠性的新一代协议
- 三次握手 / TIME_WAIT — 更多细节见
socket.md第 6 节