TCP 与 UDP
TCP 和 UDP 怎么选:三次握手和 TLS 各占几个 RTT、丢包重传为什么会让延迟跳到秒级、TIME_WAIT 和临时端口耗尽、队头阻塞,以及 QUIC / HTTP/3 如何在 UDP 上重做可靠传输。带跨洲 RTT 算例、翻车表和面试答法。
选传输层协议要回答三个问题:丢了的数据还要不要、晚到的数据还有没有用、建连和重传的往返能不能承受。 TCP 把可靠、有序做进内核,代价是握手和重传的等待;UDP 什么都不保证,可靠性要应用自己做,QUIC 就是在 UDP 上把这件事重新做了一遍。
Trade-off 快览
- TCP:可靠、有序、重传,适合对完整性要求高的场景
- UDP:更快、低开销,但可能丢包,适合实时与低 latency 场景
示例:
- email / 文件传输 → TCP
- 视频直播 / online gaming → UDP
有约束的设计问题
一个在线教育 App,服务端在美西,大量用户在悉尼。要传的东西有四类:
- 课程页面和 API:每个字节都要到,顺序不能错。
- 课后作业上传:几十 MB,可以慢一点,但不能坏。
- 直播课的音视频:晚到 300 ms 的帧已经没用了,宁可丢掉。
- 客户端查域名的 DNS 请求:一问一答,很小。
四类流量的答案不同,下文每一节都回到它们。
TCP
Transmission Control Protocol (TCP) 是 connection-oriented 的协议,一旦 connection 建立,数据就可以双向传输。TCP 自带 error-checking 和顺序保证,能保证数据按发送顺序到达,适合传输静态图片、data files、web pages 等内容。

TCP 很可靠,但它的反馈机制会带来更大的 overhead,占用更多 network 带宽。这份 overhead 在延迟上具体体现为下面几处。
建连:三次握手
客户端发 SYN,服务端回 SYN-ACK,客户端再发 ACK(RFC 9293)。第三个 ACK 可以和第一段数据一起发,所以握手本身花 1 个 RTT。HTTPS 还要在其上做 TLS:TLS 1.3 完整握手 1 个 RTT(RFC 8446),TLS 1.2 是 2 个 RTT。
算一下场景里的悉尼用户。以下 RTT 是假设值:悉尼到美西按 150 ms 算。
| 协议组合 | 首字节前的往返 | 约耗时 |
|---|---|---|
| TCP + TLS 1.2 + 请求 | 1 + 2 + 1 = 4 RTT | 600 ms |
| TCP + TLS 1.3 + 请求 | 1 + 1 + 1 = 3 RTT | 450 ms |
| QUIC(新连接)+ 请求 | 1 + 1 = 2 RTT | 300 ms |
| QUIC 0-RTT 恢复 + 请求 | 1 RTT | 150 ms |
这就是为什么连接复用(HTTP keep-alive、连接池)比任何参数调优都重要:复用的连接没有握手,只剩请求本身的 1 个 RTT。TCP Fast Open(RFC 7413)允许在 SYN 里带数据,但需要此前连过同一服务端拿到 cookie,RFC 7413 也讨论了中间设备丢弃这类 SYN 的情况,客户端要能回退到普通握手。
丢包:重传超时是秒级的
TCP 靠 ACK 发现丢包。快速重传在收到重复 ACK 时就补发,代价约 1 个 RTT;如果后面没有足够的包触发重复 ACK(比如丢的是 SYN,或者是一次响应的最后几个包),就只能等重传超时(RTO):
- RFC 6298 规定初始 RTO 为 1 秒,计算出的 RTO 小于 1 秒时 SHOULD 取 1 秒。
- Linux 没照这个下限,内核源码
include/net/tcp.h里TCP_RTO_MIN是HZ/5,即 200 ms;TCP_RTO_MAX是 120 秒。 - SYN 丢了走的是初始 RTO。Linux 文档里
tcp_syn_retries默认 6 次,当前内核在最后一次 SYN 重传后大约 131 秒才判定连接失败。
所以「建连偶尔要 1 秒多」通常就是一个 SYN 或 SYN-ACK 丢了。应用层的 connect timeout 如果设成 1 秒,正好会在第一次重传前放弃;设成 3 秒则能容忍一次丢包。
慢启动:新连接一开始跑不满带宽
新连接的初始拥塞窗口按 RFC 6928 是 10 个 segment(约 14.6 KB),之后每个 RTT 大致翻倍。理想情况下(无丢包、MSS 1460)传一个 100 KB 的响应:第 1 个 RTT 发 14.6 KB,第 2 个 29.2 KB,第 3 个 58.4 KB,累计 102.2 KB,要 3 个 RTT。在 150 ms RTT 下这就是 450 ms,和带宽有多大无关。Linux 的 tcp_slow_start_after_idle 默认开启,空闲一个 RTO 后窗口会被重置,长连接闲置后的第一个大响应又要重新爬坡。
关连接:TIME_WAIT 和临时端口
主动关闭的一方进入 TIME_WAIT,等 2 倍 MSL 才释放四元组,防止旧连接的迟到包混进新连接。RFC 9293 把 MSL 取为 2 分钟;Linux 把 TIME_WAIT 时长写死为 60 秒(TCP_TIMEWAIT_LEN)。
这对「服务 A 不停新建短连接调服务 B」很要命。Linux 默认临时端口范围 ip_local_port_range 是 32768–60999,共 28,232 个。同一个源 IP 连同一个目标 IP:port,每条连接关闭后端口要占 60 秒:
28,232 个端口 / 60 秒 ≈ 470 条新连接/秒
超过这个速率,connect() 开始报 EADDRNOTAVAIL(Cannot assign requested address)。修法按优先级:用连接池复用长连接;扩大端口范围;增加目标 IP(多个后端地址);tcp_tw_reuse 当前内核默认是 2(只对 loopback 开启),文档注明没有专家建议不应修改。
# 看 TIME_WAIT 数量和端口范围
ss -s
ss -tan state time-wait | wc -l
sysctl net.ipv4.ip_local_port_range
队头阻塞
TCP 只交付一条有序字节流。HTTP/2 在一条 TCP 连接上复用多个请求,但只要丢一个包,后面所有请求的数据都得等这个包重传完才能交给应用,即使它们属于别的 stream。从机制上推,丢包率越高,HTTP/2 单连接越可能比 HTTP/1.1 的多条并行连接更慢。
UDP
User Datagram Protocol (UDP) 是更简单的 connectionless 协议,不做重传和排序。UDP 不需要建立/维护/终止 connection 的 overhead,数据会持续发送给接收方,不管对方是否真的收到。UDP 头只有 8 字节(RFC 768),带一个校验和,校验失败的包直接丢弃,不会重发。

UDP 常用于 real-time 通信,也支持 broadcast / multicast。当你更在意 最低 latency,且「晚到的数据比丢数据更糟」时,应优先用 UDP。
用 UDP 意味着这些事要应用自己决定:
- 丢包:音视频用前向纠错或直接跳过;需要可靠的消息自己加序号和重传。
- 乱序:用序号或时间戳丢弃过时的包,比如游戏只保留最新的位置快照。
- 拥塞控制:UDP 不会自己降速,发得太猛会把链路和别人的流量一起挤垮。
- NAT 与防火墙:没有连接状态,NAT 映射靠超时维护,长时间不发包映射会被回收,所以实时应用会定期发 keepalive。
DNS 是 UDP 的经典用法:一问一答,丢了就重发整个查询。RFC 1035 把 UDP 上的 DNS 消息限制在 512 字节,超长响应设置截断位(TC),客户端改用 TCP 重查;EDNS(0)(RFC 6891)允许协商更大的 UDP 报文。RFC 7766 要求 DNS 实现必须支持 TCP,所以防火墙只放行 53/UDP 是错误配置。
QUIC 与 HTTP/3:在 UDP 上重做可靠传输
QUIC(RFC 9000)跑在 UDP 上,在用户态实现了可靠传输、拥塞控制和多路复用,并把 TLS 1.3 嵌进握手(RFC 9001)。和 TCP + TLS 相比有三处实质区别:
- 握手合并:传输和加密握手一起完成,新连接 1 个 RTT;恢复连接可用 0-RTT 直接发请求。0-RTT 数据可能被重放,只应放幂等请求。
- Stream 相互独立:HTTP/3(RFC 9114)每个请求一个 QUIC stream,RFC 原文是一个 stream 阻塞或丢包不会妨碍其他 stream 前进,TCP 层的队头阻塞没有了。
- 连接迁移:连接用 connection ID 标识,不绑定四元组。手机从 Wi-Fi 切到 4G,IP 变了连接还在;TCP 连接在这种情况下会断掉重连。
代价:UDP 在一些企业网络里被限速或屏蔽,所以浏览器一般先走 TCP,通过 Alt-Svc 发现服务端支持 HTTP/3 后再切换;QUIC 多在用户态实现,同样流量的 CPU 开销一般高于内核 TCP(这是部署经验,不是 RFC 结论,各实现差异很大,要自己压测)。
TCP vs UDP
TCP 是 connection-oriented;UDP 是 connectionless。TCP 首包更慢(握手)、丢包时更慢(等重传),这些等待换来的是可靠有序。UDP 更简单、开销更低,但丢包重传只在 TCP 里由协议提供,UDP 上要应用自己做。
TCP 提供 ordered delivery,而 UDP 不保证 end-to-end 顺序,也不会检查 receiver readiness。
| Feature | TCP | UDP |
|---|---|---|
| Connection | 需要先建立连接(1 RTT 握手) | Connectionless |
| Guaranteed delivery | 保证送达或报错 | 不保证 |
| Re-transmission | 协议自动重传 | 无,需应用自己实现 |
| Ordering | 有序字节流 | 每个 datagram 独立,可能乱序 |
| Header | 20–60 字节 | 8 字节 |
| Congestion control | 内核内置 | 无,需应用自己实现 |
| Broadcasting | 不支持 | 支持(IPv4 broadcast / multicast) |
| Use cases | HTTP/1.1、HTTP/2、SMTP、数据库连接 | DNS、VoIP、直播、游戏、QUIC / HTTP/3 |
回到场景
| 流量 | 选择 | 原因 |
|---|---|---|
| 页面与 API | TCP + TLS 1.3,长连接复用;可加 HTTP/3 | 必须完整有序;跨洲 RTT 大,握手次数决定首屏 |
| 作业上传 | TCP,分片上传 + 断点续传 | 完整性第一;单连接丢包时会被 RTO 卡住,分片可以只重传失败的片 |
| 直播音视频 | UDP(WebRTC 媒体通常走 UDP 上的 SRTP) | 晚到的帧没用,重传只会增加延迟 |
| DNS | UDP,截断时回退 TCP | 一问一答,重发整个查询比建连便宜 |
常见翻车
| 翻车 | 现象 | 修法 |
|---|---|---|
| 服务间每次调用新建连接 | 高峰时 EADDRNOTAVAIL,大量 TIME_WAIT | 连接池 / keep-alive;按 470 条/秒/目标估算上限 |
| connect timeout 设为 1 秒 | 偶发建连失败,恰好丢一个 SYN 就失败 | 超时覆盖一次 SYN 重传;并在上层做有退避的重试 |
| 防火墙只放行 53/UDP | 大响应(DNSSEC、多 A 记录)解析失败 | 同时放行 53/TCP |
| UDP 应用没有拥塞控制 | 带宽跑满时丢包率飙升,同链路其他业务受影响 | 用现成协议(QUIC、WebRTC 的拥塞控制),不要裸发 |
| 长连接闲置后首个大响应慢 | p99 偶发多出几个 RTT | 了解 tcp_slow_start_after_idle 的影响,按压测结果决定 |
| 企业网屏蔽 UDP 443 | HTTP/3 连不上 | 保留 TCP 回退,不要只提供 HTTP/3 |
面试时这样回答
- 复述约束:「这类数据丢了能不能接受、晚到还有没有用、RTT 大概多少。」
- 给出选择:需要完整有序就 TCP;实时媒体、游戏状态用 UDP,并说清谁来处理丢包和拥塞。
- 用 RTT 说延迟:「TCP 握手 1 RTT,TLS 1.3 再 1 RTT,跨洲 150 ms 的话首字节至少 450 ms,所以要连接复用或者 HTTP/3。」
- 说一个故障:短连接打满临时端口(28,232 个端口 / 60 秒 TIME_WAIT ≈ 470 条/秒),修法是连接池。
- 加分项:HTTP/2 的 TCP 队头阻塞,HTTP/3 用 QUIC 的独立 stream 解决;QUIC 0-RTT 只能放幂等请求。
相关章节:OSI Model、IP、DNS、SSL / TLS / mTLS、Long Polling / WebSockets / SSE、Networking Essentials、CDN。
一手证据
- RFC 9293:Transmission Control Protocol
- RFC 768:User Datagram Protocol
- RFC 6298:Computing TCP's Retransmission Timer
- RFC 6928:Increasing TCP's Initial Window
- RFC 7413:TCP Fast Open
- RFC 8446:TLS 1.3
- RFC 9000:QUIC
- RFC 9001:Using TLS to Secure QUIC
- RFC 9114:HTTP/3
- RFC 1035:Domain Names、RFC 6891:EDNS(0)、RFC 7766:DNS over TCP
- Linux kernel:IP sysctl(
tcp_syn_retries、ip_local_port_range、tcp_tw_reuse、tcp_slow_start_after_idle) - Linux 源码 include/net/tcp.h(
TCP_TIMEWAIT_LEN、TCP_RTO_MIN)