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. 核心对比表

维度TCPUDP
连接面向连接(先三次握手)无连接(直接发)
可靠性可靠:确认重传、保证到达不可靠:丢了就丢了
顺序保证按发送顺序交付不保证顺序
数据形态字节流(无消息边界,会粘包)数据报(一发一收,有边界)
流量控制有(滑动窗口)
拥塞控制有(网络堵了会自动降速)无(该发还发,可能加剧拥塞)
头部开销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 节