TCP与UDP辨析
TCP 和 UDP:别再因为选错协议半夜爬起来改 Bug 了
你肯定碰到过这种魔幻时刻:自己电脑上跑得六亲不认的网络模块,一扔上服务器就变得脆弱无比——消息莫名其妙短一截,端口用着用着就没了,延迟时好时坏像在蹦迪。多数时候,元凶就藏在传输层的两个“老伙计”身上:TCP 和 UDP。它们一个像偏执狂一样确保每粒字节送达,另一个洒脱到丢包都懒得通知你。搞懂它们的脾气,很多诡异问题会自行消失。
一、TCP:事无巨细的“可靠强迫症”
如果把网络传输比作寄快递,TCP 就是那种寄一封挂号信,先打电话确认地址,途中隔三岔五问“到了吗”,最后还让你签字画押的快递员。它提供的服务叫可靠的字节流,可靠到让你觉得它有点烦,但账本、订单这类东西又离不开它。
1.1 你好、我好、连接好:三次握手与四次挥手
TCP 建立连接要走“三次握手”,释放连接要走“四次挥手”。流程如下:
sequenceDiagram
participant C as 客户端
participant S as 服务端
Note over C,S: 三次握手
C->>S: SYN (seq=x)
S->>C: SYN+ACK (seq=y, ack=x+1)
C->>S: ACK (seq=x+1, ack=y+1)
Note over C,S: 连接建立完成
Note over C,S: 四次挥手
C->>S: FIN (seq=u)
S->>C: ACK (seq=v, ack=u+1)
Note right of S: 半关闭状态
S->>C: FIN (seq=v, ack=u+1)
C->>S: ACK (seq=u+1, ack=v+1)
Note left of C: TIME_WAIT (等2MSL)问题往往出在挥手之后。主动关闭的一方会进入 TIME_WAIT 状态,时长 2 倍 MSL(通常 60 秒)。这段时间里,那个连接占用的本地端口无法复用。短连接一多,netstat 里铺天盖地的 TIME_WAIT 能把端口号吃干抹净,新的连接只能干瞪眼。想缓解的话,要么启用 tcp_tw_reuse(还得同时打开 timestamps),要么直接用长连接或连接池,别让连接动不动就挥来挥去。
1.2 确认、重传、窗口、拥塞——四大金刚
TCP 为了“可靠”二字,祭出了一套组合拳:
- 确认与重传:收方乖乖回 ACK,发方超时没收到就重传。如果发方一连收到三个重复 ACK(说明后面的包到了但中间缺了),它会立刻重传,不等超时,这招叫“快速重传”。
- 滑动窗口:收方告诉发方自己还能吃下多少数据(窗口大小),发方据此控制发送速率,避免把收方撑死。
- 拥塞控制:网络堵车时,TCP 会主动缩小自己的“拥塞窗口”(cwnd),算法包含慢启动、拥塞避免、快速恢复等。常见事故:丢包一多,cwnd 断崖式下跌,吞吐量跌成狗,查半天才发现是底层拥塞算法在“好心地”帮你降速。
1.3 字节流的坑:粘包这件小事
TCP 眼里没有“消息”的概念,只有连绵不断的字节流。你调用两次 send,它可能把这两次的数据拼成一个 TCP 段发出去;也可能拆成三次,全看心情。上层应用如果不做消息切分,接收方就会把两个消息读成一个,或者只读到半截——这就是臭名昭著的“粘包”。
解决方法不外乎三种:
| 策略 | 做法 | 适合场景 |
|---|---|---|
| 固定长度 | 每消息固定 N 字节,不足补零 | 消息长度极端一致,如传感器数值 |
| 分隔符 | 用 \r\n 等特殊符号作为消息边界 | 文本协议,比如 SMTP |
| 长度前缀 | 消息前加 4 字节声明本次消息体多长 | 二进制协议,最为通用 |
比如这串代码:
1 | import struct |
这段代码没有半点“伪”的,直接拷贝到你的项目里,配合 send 时也先发送 struct.pack('!I', len(body)) 就行。从此粘包问题跟你形同陌路。
1.4 队头阻塞——TCP 的死穴
TCP 是严格有序的,一个报文段丢了,后面所有到达的包都得在内核缓冲区里排队,等那个丢掉的家伙重传到位,应用才能接着读。这就是队头阻塞(Head-of-Line Blocking)。它对于实时通信简直是噩梦:一丢包,延迟就暴增。这也是为什么 HTTP/3 干脆抛弃 TCP,在 UDP 之上搞了 QUIC。
二、UDP:没心没肺,快得纯粹
UDP 是传输层的“极简主义者”,只做了两件事:给数据加上端口号,然后尽力把它扔向目标。不保证送达、不保证顺序、不保证不重复,甚至连连接的概念都没有。
sequenceDiagram
participant A as 发送方
participant B as 接收方
A->>B: 数据报 1 (直接发送)
A->>B: 数据报 2
B-->>A: 应用层确认(需要自己实现)
Note over A,B: UDP 本身不提供任何保证如果把 TCP 比作查岗式的微信电话,UDP 就是往群里扔了一条语音,对方听没听见、听见几条,完全随缘。正因如此,它几乎没有状态维护开销,一台服务器扛几十万个 UDP 端点轻松愉快。
2.1 快有快的代价:丢包、乱序你得自己扛
游戏里一个角色位置更新包丢了,无所谓,因为 50ms 后又来一包新的。但对可靠性有要求的场景,就必须在应用层自己补课。常见手段:
- 序列号 + 滑动窗口:每个包带递增序号,接收方只确认连续收到的最大序号,发送方根据确认情况选择性重传。
- 前向纠错(FEC):发送时附上冗余数据,丢了可以通过算数恢复,避免重传带来的延迟,直播领域很常见。
- 用现成轮子:KCP、ENet、QUIC 等协议都在 UDP 之上封装了可靠传输,比你自己造轮子靠谱多了。
2.2 包大小:别让 IP 分片出来捣乱
一个 UDP 数据报能塞多少数据?理论上最大 65535 字节,但实际上链路层的 MTU(一般是 1500 字节)才是硬杠杠:
1 | 安全大小 = MTU - IP头(20) - UDP头(8) = 1472 字节 |
超过这个数字,IP 层就会将数据报拆成多个碎片。只要其中一片丢了,整个数据报就废了;而且很多老旧防火墙看到分片就直接扔掉,丢包率急剧升高。实际编码时,最好控制在 1400 字节以内,或者自己在应用层拆包。
一个简单的安全发送示例:
1 | import socket |
2.3 安全问题:UDP 是反射放大攻击的温床
UDP 不验证源地址,伪造起来跟玩似的。攻击者用受害者 IP 发送一个小请求,服务器回一大坨响应,打出一记漂亮的反射放大攻击。所以任何基于 UDP 的应用,都必须在应用层做来源校验,或者套上 DTLS 加密。
三、一图一表,彻览两者差异
抛开抽象的概念,用一张流程图速判选型方向,再用一张表格量化核心区别。
选型速断流程图
graph TD
A[需要可靠传输?] -->|是| B[数据顺序是否必须严格?]
A -->|否| C[延迟是否极度敏感?]
B -->|是| TCP
B -->|否| D[能否接受队头阻塞?]
D -->|能| TCP
D -->|不能| QUIC
C -->|是| UDP
C -->|否| E[是否需要连接状态管理?]
E -->|是| TCP
E -->|否| UDPTCP vs UDP 关键差异速查表
| 维度 | TCP | UDP |
|---|---|---|
| 连接模型 | 面向连接 (三次握手) | 无连接,直接发送 |
| 可靠性 | 确认+重传,保证交付 | 不保证,尽力而为 |
| 数据边界 | 字节流,无边界,易粘包 | 数据报,天然保留边界 |
| 顺序保证 | 严格有序 | 无序,应用层自行处理 |
| 传输开销 | 较高(ACK、重传、拥塞控制) | 极低(仅有 8 字节首部) |
| 头部大小 | 20~60 字节 | 8 字节 |
| 流控/拥塞控制 | 内建滑动窗口、慢启动等 | 无,由应用自行实现 |
| 队头阻塞 | 存在(单个丢包拖慢后续所有数据) | 无(各数据报独立) |
| 适用场景 | 网页、文件、交易、RPC | 音视频、游戏、IoT、DNS |
| 常见故障 | TIME_WAIT 堆积、重传风暴、cwnd 骤降 | 分片丢包、源地址伪造、乱序 |
四、什么时候该翻谁的牌子?
选协议这事儿,本质是在“可靠性”和“低延迟/低开销”之间做权衡。下面这张表能帮你快速决断:
| 应用场景 | 推荐 | 理由 |
|---|---|---|
| 网页、文件下载、交易系统 | TCP | 字节一个都不能少,顺序也不能乱 |
| 微服务间的 RPC 调用 | TCP (gRPC) | 可靠、成熟、生态齐全 |
| 服务发现、健康检查 | UDP 组播/单播 | 小报文、周期性,扛得住丢包 |
| 视频会议、直播 | UDP | 延迟第一,丢几帧不影响体验 |
| 多人在线游戏(状态同步) | UDP | 每秒数十次更新,丢了就等下一帧 |
| 百万级 IoT 设备心跳 | UDP | 没那么多端口和内存维护连接 |
| DNS 查询 | UDP 为主 | 一次请求一个包搞定,超 512 字节再切 TCP |
| 想同时拥有 TCP 的可靠和 UDP 的速度 | QUIC (基于 UDP) | HTTP/3 的标准传输层,解决队头阻塞,连接迁移丝滑 |
QUIC 的出现模糊了二者的边界:它在 UDP 之上实现了可靠传输、安全加密和多路复用,还没了队头阻塞。新项目如果追求极致性能,可以直接把目光投向它。
五、写在最后
TCP 和 UDP 没有高低贵贱之分,它们只是网络工具箱里型号不同的两把扳手。理解它们,不是背熟面试八股,而是你能在线上出故障时,看一眼 ss -s 和 tcpdump 抓的包,就大致猜到是握手风暴、重传飓风还是数据报被撕碎——然后手起刀落,药到病除。
下一次你的代码要去跟网络打交道时,不妨对着上面的流程图和表格,花两分钟想清楚:这次数据是宁可晚到也不能丢,还是宁可丢几个也不能卡?答案一出,协议自然就选出来了。
- 标题: TCP与UDP辨析
- 作者: Star Dust
- 创建于 : 2026-05-17 21:53:27
- 更新于 : 2026-05-19 00:01:38
- 链接: https://starblog.qzz.io/posts/68295e25.html
- 版权声明: 版权所有 © Star Dust,禁止转载。