长连接、隧道与 NAT 内网穿透 (Keep-Alive, Tunneling and NAT Traversal)
把一堆容易混的网络概念串成一条线:TCP 握手挥手、HTTP 短/长连接、upstream 长连接、HTTP/2、WebSocket、SSH/SS/FRP 隧道、NAT 与内网穿透。
一句话框架: 所有网络通信的本质都是「一条连接 + 在连接上跑的数据」。区别只在四件事——连接怎么建、建了之后复用还是销毁、数据是单向还是双向、谁先发起。下文所有概念都是这四件事的不同组合。
1. TCP 连接的开销:三次握手、四次挥手、TIME_WAIT
TCP 是可靠、全双工的连接。建立和销毁都有固定成本。
- 三次握手 (three-way handshake):
SYN → SYN-ACK → ACK。目的不是"互相认识",而是同步双方的初始序号,确认双向都能收发。要 1 个 RTT。 - 四次挥手 (four-way close):
FIN → ACK → FIN → ACK。因为 TCP 是全双工的,两个方向各自独立关闭,所以挥手比握手多一步。主动关闭的那一方进入 TIME_WAIT。
TIME_WAIT 为什么存在: 主动关闭方要等 2 × MSL(最长报文寿命,Linux 上约 60 秒),为了 (1) 确保最后一个 ACK 送达、丢了能重传;(2) 让旧连接的迟到报文在网络上死干净,不污染下一次复用同一「四元组」的新连接。
高并发 + 短连接的代价: 如果每次请求都新建一条 TCP 连接、用完就关,那么每次请求都要付一次「三次握手 + 四次挥手」的成本。结果:
- CPU 开销大(每次都要处理握手挥手、分配销毁内核结构);
- 大量连接堆积在 TIME_WAIT(主动关闭方尤其明显);
- 客户端临时端口耗尽(四元组 = 源IP + 源端口 + 目的IP + 目的端口,源端口一共 65535 个,短时间反复建连就用光了)。
所以需要「连接复用」——也就是长连接。
2. HTTP 短连接 vs 长连接 (keep-alive)
- HTTP/1.0:默认短连接。每个请求新建一条 TCP,响应完就关闭。想复用得显式带
Connection: keep-alive。 - HTTP/1.1:默认长连接 (keep-alive)。多个请求复用一个 TCP 连接,串行地发。想关闭才需要
Connection: close。
长连接的本质 = 连接复用,不是隧道。 请求完成后连接不销毁,留给下一个请求用。它依然是「请求-响应」模式,服务端不会主动推送。
关键点:空闲太久会被服务端主动关掉。 Nginx 的 keepalive_timeout(默认 65s 左右)到点就把空闲连接断了。所以"长连接"不是"永久连接",只是"别那么快销毁,先复用一会儿"。它纯粹是性能优化,实践里几乎总是开着,但它不是一种新的通信形态。
3. 长连接 vs 隧道:关键区分(容易混的地方)
这是最容易混的一对,它们根本不在一个层次:
| 长连接 (keep-alive) | 隧道 (SSH / SS / FRP) | |
|---|---|---|
| 本质 | 连接复用:连接不销毁,留给下次 | 一条持续存活的双向数据流通道 |
| 数据方向 | 仍是请求-响应,单向发起 | 双向自由收发(全双工) |
| 生命周期 | 空闲超时会被服务端主动关 | 一旦断开就失效,要重新建立 |
| 目的 | 省掉重复握手的性能优化 | 承载/封装另一层流量,做转发或代理 |
| 类比 | 服务员顺手把杯子留着,下次还倒同一杯 | 挖了一条专用的、两边都能传东西的地下管道 |
一句话:长连接是"复用",隧道是"转发通道"。 隧道里跑的那条 TCP 连接,本身往往也是"长连接"(持续存活),但它的意义不是省握手,而是提供一条双向、加密、持续的管道。
4. 反向代理与 upstream 长连接
Nginx 作为反向代理,前面连客户端,后面连后端(upstream)。后端这段也有短/长连接的问题。
- 默认:Nginx 对 upstream 用的是 HTTP/1.0 短连接,每来一个请求就新连一次后端,转发完就断。这在高并发下同样有"反复握手挥手"的开销。
开 upstream 长连接要两步:
upstream backend { server 127.0.0.1:3000; keepalive 16; }—— 保持 16 条空闲连接备用;location里proxy_http_version 1.1;+proxy_set_header Connection "";—— 因为默认是 1.0 短连接,得显式升到 1.1 并清掉 Connection 头。
为什么上游可能"不给你开"长连接?
keep-alive 是双方协商的,不是单方面授予的。 客户端(或 Nginx)可以请求 Connection: keep-alive,但服务器有权拒绝:
- 在响应里回
Connection: close,表示"这条连接我用完就关,不复用"; - 或配置
keepalive_timeout 0,对所有连接都不复用。
很多 API 网关、CDN、限流/计费场景故意这么做:不想让单个客户端占着连接。所以确实存在这种情况——上游给你账号开了高并发(允许你同时开很多条连接),但没给你"长连接资格"(每条连接用完就关)。 并发配额和长连接是两个独立的东西:
- 并发 = 允许你同时建立多少条连接;
- 长连接 = 允许你一条连接复不复用、用多久。
你可能两条都拿到,也可能只拿到第一条。
5. HTTP/2
HTTP/1.1 长连接解决了一部分问题,但还有个缺陷:连接内是串行的,一个请求慢,后面同一条连接上的请求都得排队(队头阻塞,Head-of-Line Blocking)。浏览器只能靠"开多条并行连接"来缓解。
HTTP/2 的核心是多路复用 (multiplexing):在一条 TCP 连接上,把多个请求/响应拆成带编号的二进制帧交错传输,彼此不阻塞。还带来:
- 二进制分帧、头部压缩(HPACK);
- 一个域名通常只建一条连接,复用率更高。
但注意: HTTP/2 仍在 TCP 之上。应用层的队头阻塞解决了,传输层的队头阻塞还在——一旦丢包,整个 TCP 连接都被拖住(因为 TCP 要求按序送达)。这是 HTTP/3(基于 QUIC/UDP) 才解决的问题:用 UDP + 独立流,丢一个包不影响其他流。所以别把 HTTP/2 当成"彻底解决长连接一切问题"的银弹。
6. WebSocket
HTTP 长连接(keep-alive)仍是"请求-响应",服务端不能主动推数据。WebSocket 则把一条 HTTP 连接升级(Upgrade)成一条全双工、双向的持久连接:
- 客户端发一个带
Upgrade: websocket的 HTTP 请求; - 服务器回
101 Switching Protocols; - 之后这条连接上,双方可以随时、自由地互相发消息,不再是"你问我才答"。
区分: WebSocket 是"长连接"里的另一类——它同时满足"复用(持久)"+ "双向自由收发",所以它其实更接近"隧道"那一类(见第 3 节),而不是 keep-alive。keep-alive 只是复用连接、仍是请求-响应;WebSocket 是双向数据流。
7. NAT:谁是内网、谁是外网
背景:IPv4 地址不够用。 IPv4 一共约 43 亿个地址,分完了。所以家庭/校园/公司用私有地址给内部设备,大家共享一个公网 IP 出去。这个"把内网地址翻译成公网地址"的机制叫 NAT(Network Address Translation,网络地址转换)。
判断内网/外网只有一条标准: 有全球唯一、能被任何人直接路由到的地址,才是公网;用私有地址段的,都是内网。私有地址段:
10.0.0.0/8(10.x.x.x)172.16.0.0/12(172.16.x.x ~ 172.31.x.x)192.168.0.0/16
所以:10.201.21.177(手机)和你的电脑,都是 10 段私有地址 → 都在内网。真正的公网 IP 在校园网出口,甚至可能运营商级 NAT 之后。
NAT 怎么工作: 内网设备往外发包时,路由器把源地址从「内网IP:端口」改成「公网IP:新端口」,并在NAT 映射表里记一条。回包到了,路由器查表,把「公网IP:端口」改回「内网IP:端口」转发进去。
为什么外网无法主动向内网发起 TCP 连接?
因为 NAT 表是"出方向流量"才建立的。没人先发出去,表里就没有条目,外网来的 SYN 到路由器后不知道该转发给内网哪台机器,直接丢弃。打个比方:内网能打电话出去,但外网打不进来——除非内网先打出去、建立了一条能双向通话的线路。
8. 为什么需要一台有公网 IP 的 FRP 服务器
既然外网进不来,怎么让外网访问内网服务?答案是:让内网机器主动伸一条连接出去。
- 内网机器(frpc)主动向有公网 IP 的服务器(frps)发起一条 TCP 连接。这是出方向,天然能建(NAT 允许)。
- 这条连接建好后,就是一条反向隧道:外网请求先到 frps(公网,人人连得到),frps 再通过这条已经建立的、内网主动伸出来的连接,把流量捎带回内网 frpc。
- 所以 frp 的"转发"也需要一条持续连接,只不过这条连接是内网主动建立的,绕开了"NAT 不许外网主动进"的限制。
这就是为什么必须有公网 IP 的那台服务器: 只有公网 IP 才能被外网直接连到;内网连公网服务器是出方向、天然可行;服务器再借这条已建立的反向连接把流量送回内网。
IPv6: 地址空间近乎无限,理论上每个设备都有全球唯一地址,不需要 NAT,外网可以直接连进来。但现实里很多网络没 IPv6,或有 IPv6 但防火墙挡着,所以 IPv4 时代的内网穿透方案依然大量存在。
9. 一条主线串起来:SSH、SS、FRP 都是"隧道"
| 工具 | 隧道里封装了什么 | 典型用途 |
|---|---|---|
| SSH | 加密的交互 shell / 文件 / 端口转发 | 远程管理 |
| SS (Shadowsocks) | 加密混淆的代理流量 | 翻墙/代理 |
| FRP | 反向转发的 TCP 流量 | 内网穿透 |
它们的共同点是:由"没被暴露的一方"主动建立一条持续存活的连接,然后在这条连接上双向流动数据;一旦断开就失效,要重新建立。 这正是第 3 节说的"隧道"。
而 keep-alive、upstream 长连接、HTTP/2 多路复用,都是在已经建立的连接上做"复用"优化,省掉重复握手。
10. 一次理清(面试式速记)
- 短连接高并发为什么慢? 每次请求都重复三次握手 + 四次挥手,CPU 开销大、TIME_WAIT 堆积、端口耗尽。
- 长连接(keep-alive)是什么? 连接复用,不销毁留给下次,是性能优化,空闲超时会被关。HTTP/1.1 默认开。
- 隧道是什么? 一条持续存活的双向数据流,断开失效。SSH/SS/FRP 都是隧道。
- upstream 长连接为什么可能开不起来? keep-alive 是协商的,服务器可以回
Connection: close拒绝复用;并发配额 ≠ 长连接资格。 - HTTP/2 解决什么? 一条连接多路复用,解决 HTTP/1.1 的应用层队头阻塞;传输层队头阻塞留给 HTTP/3。
- WebSocket 和 keep-alive 区别? keep-alive 复用连接但仍请求-响应;WebSocket 升级成双向自由收发的持久连接。
- 为什么外网进不了内网? NAT 表由出方向流量建立,外网主动的包路由器不知道转给谁。
- 为什么 FRP 要有公网服务器? 内网主动连公网服务器建反向隧道,公网流量借这条连接捎回内网。
相关
- SSH 协议
- SSH 连接到底费不费流量 (SSH Connection Traffic Myth)——SSH 长连接为何空闲时几乎零流量
- 用 SSH + Termux 玩旧手机——SSH 隧道管理旧安卓手机的实际场景
- 镜像站与反向代理 (Mirror Site and Reverse Proxy)——反代/Nginx 属于"数据面转发"的对照
- HTTPS 协议——TLS 在 TCP 之上又加一层握手,短连接开销更大
本文归档于个人知识库 · 领域:CS & AI · 模块:04 Project_CS_AI/Network