IN · 网络面试题
网络是前端唯一绕不开的"后端领域"——页面加载慢、接口 502、跨域报错、Cookie 丢了, 桩桩件件最后都要落到协议层。面试官问网络,真正想听的不是"三次握手的报文叫什么", 而是 "你能不能把一次请求的代价拆开、知道瓶颈在哪、知道怎么改"。
所以本页的组织方式是:先给分层模型(骨架)→ 再逐层给高频题 → 最后给排障方法论。 与浏览器直接相关的(HTTP 缓存、CORS、Core Web Vitals)链回 A 域,本页只留要点。
📌 结构:零、一次请求的代价分层 · 一、TCP · 二、HTTP 演进 · 三、HTTPS/TLS · 四、DNS · 五、状态码与方法语义 · 六、同源与跨域 · 七、Cookie 与鉴权 · 八、缓存 · 九、长连接选型 · 十、CDN · 十一、排障方法论 · 十二、移动端对照。
高频 Top 10(不会的话优先补这些)
| # | 题目 | 一句答案 | 去哪补 |
|---|---|---|---|
| 1 | 从输入 URL 到页面展示发生了什么 | 不要背流程,要能拆出每一段的耗时和优化点 | 本章 |
| 2 | 为什么是三次握手,两次行不行 | 两次无法确认客户端的接收能力,且历史重复 SYN 会让服务端建错连接 | 1.1 |
| 3 | TIME_WAIT 出现在哪一端、为什么要等 | 主动关闭方;① 保证最后 ACK 可达 ② 让旧报文消散 | 1.3 |
| 4 | HTTP/2 解决了什么、又留下了什么 | 解决应用层队头阻塞,没解决 TCP 层队头阻塞 → 才有 HTTP/3 | 2.2 |
| 5 | HTTPS 握手多了几个 RTT | TLS 1.2 2 个,TLS 1.3 1 个,QUIC 复用 0 个 | 3.2 |
| 6 | 跨域是被谁拦的 | 浏览器拦的——请求发出去了、服务端也回了 200 | 6.2 |
| 7 | Cookie 的 SameSite 三档有什么区别 | Lax 是现代默认:跨站 GET 导航带、跨站 POST/iframe/XHR 不带 | 7.1 |
| 8 | 强缓存与协商缓存怎么选 | 带 hash 的静态资源 → max-age=31536000, immutable;HTML → no-cache | A04 |
| 9 | 502 / 504 / 499 分别说明什么 | 502 上游无效响应、504 上游超时、499 客户端先断了 | 5.2 |
| 10 | WebSocket 还是 SSE | 服务端单向推 → SSE(简单得多);双向/高频/二进制 → WS | 9.1 |
零、一条主线:一次请求的代价分层
这一节是全页的骨架。 能把它讲清楚,上面 10 题基本都能顺出来。 面试官问"输入 URL 之后发生了什么"时,按这个答,比背八股高一个段位。
HTTPS + HTTP/1.1 首次访问一个域名:
| 阶段 | 动作 | 代价 | 能不能省 |
|---|---|---|---|
| ① URL 解析 | 补全协议、编码、HSTS 判断(是否在 preload 列表里 → 直接跳 HTTPS) | ~0 | — |
| ② 缓存检查 | 强缓存命中 → 直接结束,一次网络请求都没有 | 0 RTT | ✅ 最优解 |
| ③ DNS | 浏览器缓存 → OS 缓存/hosts → 本地 DNS 递归 → 根/顶级域/权威 | 0~1 RTT | dns-prefetch、HTTPDNS、减少域名数 |
| ④ TCP 握手 | SYN → SYN+ACK → ACK | 1 RTT | 长连接复用 |
| ⑤ TLS 握手 | 协商套件、验证书、交换密钥 | 2 RTT(1.2)/ 1 RTT(1.3) | 升级 1.3、会话复用、QUIC 0-RTT |
| ⑥ 发送请求 | 请求行 + 头 + body | — | 头部压缩(HPACK/QPACK)、减小 Cookie |
| ⑦ 等响应(TTFB) | 网络往返 + 服务端处理 + 可能的回源 | ≥1 RTT | CDN、缓存、后端优化 |
| ⑧ 收响应 | 受慢启动与带宽限制 | 视包体 | 压缩、分片、减少体积 |
首屏关键路径的 RTT 账:
HTTPS/1.1 + TLS1.2 首次: DNS 1 + TCP 1 + TLS 2 + 请求 1 ≈ 5 RTT
HTTPS/2 + TLS1.3 首次: DNS 1 + TCP 1 + TLS 1 + 请求 1 ≈ 4 RTT
HTTPS/2 + TLS1.3 复用: 0 + 0 + 0 + 请求 1 = 1 RTT
HTTP/3 + QUIC 复用: 0 + 0 + 0 + 请求 1 = 1 RTT(且无队头阻塞)结论(也是优化口诀):能不请求就不请求(缓存)→ 能复用连接就复用(keep-alive/HTTP2)→ 能就近就就近(CDN)→ 最后才是压缩体积。
🇨🇳 跨地域访问一个美国源站,单程 ~100ms,1 RTT = 200ms。上面从 5 RTT 降到 1 RTT,就是 800ms 的差距—— 这比你在 JS 里抠 100ms 有意义得多。
→ 浏览器侧的性能度量与诊断见 A25,优化清单见 A10。
一、TCP
1.1 三次握手:为什么不多不少三次
C → S: SYN seq=x (我在,我的初始序号是 x)
S → C: SYN+ACK seq=y, ack=x+1 (我在,我收到你的 x 了)
C → S: ACK ack=y+1 (我收到你的 y 了,可以发了)为什么两次不行——两个理由,缺一不可:
- 双向确认收发能力:TCP 是全双工,双方都要确认"对方的发送能力 + 自己的接收能力"都对得上。 两次握手只确认了 C→S 方向,服务端不知道客户端能不能收。
- 防止历史重复连接:网络里可能有个滞留的旧 SYN 迟到。两次握手下,服务端收到旧 SYN 就直接建连并等数据, 客户端根本不认这个连接 → 白白浪费一个连接资源,还可能让服务端误以为有数据要收。
为什么四次没必要:服务端的 SYN 和 ACK 可以在同一个报文里合并发出(捎带),所以三次就够。
追问:初始序号(ISN)为什么是随机的? → 防止攻击者伪造合法序号注入数据,也避免新旧连接的报文序号重叠。SYN Flood 攻击就是利用半连接队列: 服务端发完 SYN+ACK 后连接处于 SYN_RECV,占着队列等 ACK;伪造源 IP 狂发 SYN 就能把队列打满。 防御:tcp_syncookies(不分配资源,用 cookie 校验真客户端)、加大 somaxconn、云厂商的 SYN 代理/清洗。
1.2 四次挥手:为什么是四次
TCP 是全双工,每个方向要单独关闭:
主动方 → 被动方: FIN (我这边没数据发了)
被动方 → 主动方: ACK (知道了)
…… 被动方可能还有数据要发 ……
被动方 → 主动方: FIN (我也发完了)
主动方 → 被动方: ACK (知道了)→ 进入 TIME_WAIT为什么不能像握手一样合并成三次:被动方收到 FIN 时可能还有数据没发完, 所以 ACK 必须立刻回(否则主动方会重传 FIN),而 FIN 要等数据发完才能发。这就是四次的根本原因。
1.3 TIME_WAIT 与 CLOSE_WAIT(面试与排障双高频)
TIME_WAIT 只出现在主动关闭方,持续 2MSL(RFC 793 建议 MSL=2 分钟,Linux 实际按 60s 计时)。
两个存在的理由:
| 理由 | 说明 |
|---|---|
| 可靠终止 | 最后一个 ACK 可能丢,被动方会重发 FIN。没有 TIME_WAIT 的话,主动方已经关闭,只能回 RST,被动方就会以错误方式结束 |
| 旧报文消散 | 等 2MSL 让这次连接的残留报文在网络中消失,否则四元组相同的新连接可能收到旧报文,数据串台 |
面试追问:服务器上 TIME_WAIT 几万个,是不是出事了?
先区分两种状态,结论完全相反:
| 状态 | 谁的问题 | 判断 |
|---|---|---|
| CLOSE_WAIT 大量堆积 | 应用 bug | 对方已经发 FIN 了,你的程序没调 close()。去查代码(连接池没归还、异常分支没关流) |
| TIME_WAIT 大量堆积 | 通常无害 | 只占一点内存和端口表项。是协议设计的一部分,不是泄漏 |
TIME_WAIT 真正会出问题的场景:客户端/压测机端口耗尽(ip_local_port_range 用光 → Cannot assign requested address)、 或短连接高频打同一个 LB(四元组复用压力大)。
处理手段与代价(按推荐度排序):
| 手段 | 说明 | 代价 / 坑 |
|---|---|---|
| 让客户端主动关闭 | 服务端不主动断,把 TIME_WAIT 甩给客户端 | 最干净,首选 |
| 长连接 / 连接池 | 复用连接,根本上减少关闭次数 | 需要调 idle 超时(见 10.4) |
net.ipv4.tcp_tw_reuse=1 | 复用处于 TIME_WAIT 的出向连接(依赖 TCP 时间戳 PAWS) | 对服务端入向连接作用有限 |
tcp_tw_recycle | 不要提它当方案 | NAT 后时间戳乱序会大面积丢连接,Linux 4.12 已移除 |
SO_LINGER=0 | 直接发 RST 不等 | 可能丢数据、绕过优雅关闭,只在明确可接受时用 |
1.4 拥塞控制:为什么新连接总是"先慢后快"
- 慢启动:新连接的拥塞窗口
cwnd从一个很小的值(现代 Linuxinitcwnd= 10 个 MSS)开始, 每收到一轮 ACK 就翻倍 → 前几个 RTT 很慢。 - 丢包 → 快速重传 / 快速恢复(CUBIC 等);BBR 则是基于带宽与 RTT 建模,不去等丢包,弱网长肥管道收益明显。
对前端的意义: ① 复用连接(HTTP/2 多路复用、keep-alive)不只是省握手,还跳过慢启动; ② 首屏关键资源尽量小且早——慢启动阶段窗口小,大文件在开头几 RTT 就是传不动。
二、HTTP 演进
2.1 一条脉络
| 版本 | 核心改进 | 遗留问题 |
|---|---|---|
| HTTP/1.0 | 短连接,每请求一次握手 | 建连成本极高 |
| HTTP/1.1 | 长连接 keep-alive、管线化、Host 头、缓存字段、Range | 应用层队头阻塞;头部无压缩、重复传 |
| HTTP/2 | 二进制分帧、多路复用、HPACK 头部压缩、流优先级、(服务端推送,已废弃) | TCP 层队头阻塞 |
| HTTP/3(QUIC) | 基于 UDP,流之间独立重传、0/1-RTT 建连、连接迁移、加密内置 | UDP 被中间设备限流/阻断;运维可观测性弱 |
2.2 HTTP/2 解决了什么,代价是什么
解决:应用层队头阻塞(HOL)。HTTP/1.1 下一个 TCP 连接上请求-响应必须串行, 浏览器只能靠同域 6 个并发连接缓解(所以以前有"域名分片"这种优化)。
代价(这是加分点):
- HTTP/2 通常只用 1 个 TCP 连接。一旦丢包,TCP 的重传会让所有流一起等—— 在高丢包弱网下,HTTP/2 可能比 HTTP/1.1 的 6 个连接更慢。
- 流优先级在浏览器/服务器实现上普遍不理想(Chrome 多次调整策略,服务端推送已被主流浏览器移除)。
- 中间件(老代理、WAF)对 h2 支持不一致 → 上线要灰度、看错误率。
前端实践结论:
| 优化手段 | HTTP/1.1 时代 | HTTP/2 时代 |
|---|---|---|
| 域名分片(多域名) | ✅ 提升并发 | ❌ 负优化(多 DNS + 多 TLS + 多慢启动) |
| 资源合并(雪碧图、大 bundle) | ✅ 减少请求 | ⚠️ 要重新权衡(细粒度更利于缓存与按需) |
| 内联小资源 | ✅ 省请求 | ⚠️ 不能再被单独缓存 |
| 压缩 / 内容 hash / CDN | ✅ | ✅ 依然有效 |
2.3 HTTP/3 与 QUIC 的关键点
- 独立流:每个 stream 单独重传,丢一个包不阻塞其他流 → 弱网收益最大(移动端、跨境)。
- 0-RTT:复用之前连接的参数(PSK)直接发数据。⚠️ 0-RTT 数据可被重放—— 只应承载幂等请求,或做应用层抗重放(一次性 token)。
- 连接迁移:用 Connection ID 而非四元组标识连接,手机从 WiFi 切到 4G 不断线——这是它在移动端最实在的价值。
- 代价:QUIC 把传输层也加密了,中间盒看不见 → 依赖 CDN/网关支持;部分企业网封 UDP 会直接不可用; 排障工具链不如 TCP 成熟。启用策略:HTTP/2 与 h3 同时通告(
Alt-Svc),客户端协商,降级无感。
三、HTTPS / TLS
3.1 三个目标与两套加密
| 目标 | 手段 |
|---|---|
| 机密性 | 握手后用对称加密(AES-GCM / ChaCha20-Poly1305)传数据 |
| 完整性 | AEAD 自带认证标签(或 MAC) |
| 身份认证 | 证书链验证(非对称签名) |
为什么不全用非对称加密:性能差几个数量级。非对称只用来安全地协商出对称密钥。
3.2 握手流程与 RTT
TLS 1.2(2 RTT):
C → S: ClientHello (随机数、支持的套件列表、SNI 域名)
S → C: ServerHello + Certificate + ServerKeyExchange (选定套件、证书链)
C → S: 验证书 → PreMasterKey(用服务端公钥加密)→ ChangeCipherSpec + Finished
S → C: ChangeCipherSpec + FinishedTLS 1.3(1 RTT):ClientHello 直接带上 key_share(猜一个套件先算好密钥), 服务端回 ServerHello 就能开始加密 → 省 1 个 RTT。同时砍掉了:RSA 密钥交换、静态 DH(无前向安全)、 RC4/SHA1/CBC 等旧套件、压缩(CRIME)。
前向安全(PFS):用临时 ECDHE,即使服务器私钥将来泄露,历史流量也解不开。 TLS 1.3 强制 ECDHE,1.2 需要正确配置套件才具备。
3.3 证书:校什么,怎么被攻破
浏览器校四件事:签名链(一路验到操作系统/浏览器内置的根证书)、有效期、 域名匹配(看 SAN,不是 CN)、吊销状态(CRL / OCSP / OCSP Stapling,现代浏览器多用自家推送的吊销列表)。
| 攻击 / 场景 | 原理 | 防御 |
|---|---|---|
| 中间人(MITM) | 在客户端装一个受信任的根证书,就能伪造任意站点 | 用户侧别乱装根证书;App 侧做证书锁定(pinning);服务端上 HSTS |
| CA 误签发 / 被入侵 | 拿到某域名的合法证书 | 证书透明(CT):所有证书进公开日志,浏览器要求带 SCT |
| 混合内容 | HTTPS 页面里加载 http:// 资源 | 主动内容(脚本/iframe)被直接阻断;用 upgrade-insecure-requests |
| 抓包调试 | Charles / whistle / mitmproxy 就是一次"合法的中间人" | 装根证书 + App 开启可调试 pinning |
SNI 的隐私洞:为了一个 IP 承载多域名,ClientHello 里明文带域名 → 链路上的观察者能看到你访问哪个站。 解决方案是 ECH(Encrypted Client Hello),正在逐步推进。
四、DNS
4.1 解析过程
浏览器缓存 → 操作系统缓存 + hosts → 本地 DNS(递归解析器,运营商/公司)
↓ 迭代查询
根服务器 → 顶级域(.com)→ 权威服务器(example.com)- 递归 vs 迭代:你到本地 DNS 是递归(它替你跑完),本地 DNS 到各级服务器是迭代(自己问下一级)。
- TTL 决定各级缓存多久。换 IP 前要提前把 TTL 调小(比如先降到 60s),否则会出现"改了 DNS 半天不生效"。
4.2 常见记录与坑
| 记录 | 用途 | 坑 |
|---|---|---|
| A / AAAA | IPv4 / IPv6 地址 | 双栈下优先 AAAA 会有回退等待(Happy Eyeballs) |
| CNAME | 别名(CDN 就是 CNAME 到调度域名) | 同名不能有其他记录;链太长会多几次解析;不能用在根域名(用 ALIAS/ANAME 代替) |
| NS / SOA | 委派与区信息 | — |
| MX / TXT | 邮件 / SPF、域名验证 | — |
| CAA | 限制哪些 CA 能给你签发 | 配了却没包含你用的 CA → 签发失败 |
4.3 前端能做的三件事
- 减少域名数:HTTP/2 之后,多一个域名 = 多一次 DNS + TCP + TLS。域名分片已成历史。
<link rel="dns-prefetch">:只做 DNS 预解析,很便宜,可以放宽用。<link rel="preconnect">:DNS + TCP + TLS,贵(占连接、抢带宽),控制在 3~4 个, 只给确定要用的关键域名(API 域、CDN 域、字体域)。
4.4 劫持与污染
表现:返回错误 IP(广告页、缓存服务器)、或干脆解析失败。传统 Local DNS 是重灾区。
| 方案 | 说明 | 适用 |
|---|---|---|
| HTTPDNS | 不走 53 端口,直接 HTTP 请求解析服务拿 IP | App 内最常用;Web 端拿不到(浏览器发请求时已经要解析了) |
| DoH / DoT | DNS over HTTPS/TLS,加密防篡改 | 系统与浏览器层面,需环境支持 |
| 换公共 DNS | 8.8.8.8 / 1.1.1.1 / 223.5.5.5 | 简单,但解决不了基于出口的劫持,且可能让 CDN 调度不准(Local DNS 定位错) |
五、状态码与方法语义
5.1 必须能脱口而出的码
| 码 | 含义 | 面试 / 实战要点 |
|---|---|---|
| 101 | Switching Protocols | WebSocket / HTTP 升级成功 |
| 103 | Early Hints | 先把 Link: rel=preload 给浏览器再出正文,能提前几十~几百 ms 开始下载 |
| 204 | No Content | 不能有 body;DELETE 常用;fetch 解析 JSON 会报错,要特判 |
| 206 | Partial Content | 配合 Range / Accept-Ranges:断点续传、视频拖动、大文件分片下载 |
| 301 / 302 | 永久 / 临时重定向 | ⚠️ 301 会被浏览器长期缓存,改不回来。HTTP→HTTPS 建议 301,但要知道"后悔成本高" |
| 307 / 308 | 保持方法 + 保持 body 的重定向 | 302/303 历史上会把 POST 降级成 GET;要保住方法就用 307/308 |
| 304 | Not Modified | 协商缓存命中,没有 body |
| 401 vs 403 | 未认证 vs 已认证但无权限 | 401 必须带 WWW-Authenticate;别把"没登录"写成 403 |
| 409 | Conflict | 版本冲突/乐观锁失败,比用 400 更准确 |
| 429 | Too Many Requests | 应带 Retry-After;前端要做指数退避 + 抖动,别整齐地一起重试(放大雪崩) |
| 502 | Bad Gateway | 网关拿到了上游的无效响应(上游崩了 / reset) |
| 503 | Service Unavailable | 上游不可用,通常可重试(带 Retry-After) |
| 504 | Gateway Timeout | 上游超时了 |
| 499 | (Nginx 私有)客户端提前断开 | 常见原因:前端超时设太短、用户切走页面、弱网 |
5.2 5xx 与排障对应关系
502 → 上游进程挂了 / 重启中 / 返回了非法报文 → 看上游日志与发布记录
504 → 上游慢或网关超时配太短 → 看慢查询、回源链路、超时配置
499 → 客户端先走了 → 看前端超时上报、用户行为(切页/切后台)
503 → 主动限流 / 熔断 / 无可用实例 → 看限流配置与容量5.3 幂等:重试能不能乱来
| 方法 | 安全 | 幂等 | 说明 |
|---|---|---|---|
| GET / HEAD | ✅ | ✅ | 不应改变服务端状态;GET 会被缓存,所以加版本号/时间戳 |
| PUT / DELETE | ❌ | ✅ | 用完整资源替换 / 删除,重复执行结果一致 |
| POST | ❌ | ❌ | 每次都可能产生新资源 |
| PATCH | ❌ | ❌(部分更新语义上不保证) | 可用条件请求(If-Match + ETag)做成幂等 |
为什么重要:网络超时时你不知道服务端执行了没有。此时能否重试,取决于幂等。 POST 要做幂等,就引入 Idempotency-Key(客户端生成,服务端去重,保留首次结果)。
六、同源与跨域
6.1 同源策略到底限制了什么
同源 = scheme + host + port 三者全等(https://a.com:443 与 http://a.com:80 不同源)。
它限制三类东西:DOM 访问(跨域 iframe 的 contentDocument)、 数据读取(XHR/fetch 响应、Canvas 像素、localStorage)、部分脚本行为。
6.2 一个必须先讲清的误区
CORS 报错 ≠ 请求没发出去。 请求发出去了,服务端也处理了、也返回了 200,是浏览器拿到响应后按 CORS 头判断"不给你看",把结果丢掉了。
排障意义:看到控制台 CORS 报错,别只顾着改前端—— 先看 Network 里那个请求到底有没有返回、返回头里有没有 Access-Control-Allow-Origin。 如果是 OPTIONS 预检失败,真正的请求根本没发出去,这时候才是服务端没配好。
6.3 简单请求 vs 预检
简单请求(不触发预检)必须同时满足:
- 方法是
GET/HEAD/POST; - 请求头只包含 CORS 安全列表(
Accept、Accept-Language、Content-Language、Content-Type且值仅限application/x-www-form-urlencoded/multipart/form-data/text/plain、Range…); - 未使用
ReadableStream等特殊 API。
→ 换句话说:Content-Type: application/json 的 POST 一定触发预检。
预检(OPTIONS) 会多一个 RTT。优化手段: Access-Control-Max-Age 缓存预检结果(浏览器有上限,Chrome 约 600s); 或把接口改成简单请求(如 urlencoded);或根本别跨域——用同源代理 / BFF。
6.4 带 Cookie 的跨域
三方条件缺一不可:
Access-Control-Allow-Origin: https://your-site.com ← 不能是 *
Access-Control-Allow-Credentials: truefetch(url, { credentials: 'include' }) // 或 axios withCredentials: true6.5 跨域方案怎么选
| 方案 | 适用 | 评价 |
|---|---|---|
| CORS | 前后端分离、有控制权 | 正解,唯一标准方案 |
| 同源代理 | 开发期 devServer.proxy;生产 Nginx / BFF 转发 | 最省心,把跨域消灭在架构层;还能顺便聚合接口、隐藏 token |
postMessage | iframe 间通信 | 必须校验 event.origin |
| JSONP | 只有 GET 的老接口 | ❌ 已淘汰,有 XSS 风险 |
document.domain | 同父域 | ❌ 已废弃 |
七、Cookie 与鉴权
7.1 Cookie 的属性逐个讲
| 属性 | 作用 | 面试点 |
|---|---|---|
HttpOnly | 禁止 JS 读取 | 防 XSS 窃取会话。注意它不防 XSS 本身,只防"偷走 token" |
Secure | 仅 HTTPS 传输 | 明文站点下不会发送 |
SameSite=Strict | 完全禁止跨站携带 | 从外链点进来不带 Cookie(会显示未登录,用户体验差) |
SameSite=Lax | 现代浏览器默认 | 跨站顶级导航的 GET 带,跨站 POST / iframe / XHR 不带 → 天然挡掉大部分 CSRF |
SameSite=None | 允许跨站携带 | 必须同时配 Secure,否则被拒 |
Domain / Path | 作用域 | Domain 不能随意设成父域以上;不设则仅当前 host |
__Host- 前缀 | 锁 host、必须 Secure、不能设 Domain | 防子域覆盖会话 Cookie,安全加固手段 |
Partitioned(CHIPS) | 按顶级站点分区存储 | 第三方嵌入场景下的隔离方案 |
容量:单条 Cookie 约 4KB,每域约 50 条(各浏览器有差异)。 结论:Cookie 只放会话标识,别塞业务数据——它会无条件跟着每个请求发出去,白白吃掉上行带宽。
7.2 Token 存哪(高频争议题)
| 方案 | 主要风险 | 缓解 |
|---|---|---|
localStorage | XSS 直接读走 | 严格 CSP + 依赖扫描 + 不引入不可信脚本;短期 token |
Cookie + HttpOnly + SameSite | CSRF | SameSite=Lax + 双重提交 token / 校验 Origin 头 |
| 内存(变量)+ Refresh Token 在 HttpOnly Cookie | 刷新页面即失效 | 较优解:access token 存内存,refresh token 存 Cookie |
Refresh Token 工程要点:轮换(rotation)——每次刷新都发新的 refresh token 并作废旧; 若检测到旧 token 被重用,说明可能泄露,直接吊销整条 token 家族。
→ 完整讨论(含 CSP、XSS/CSRF 攻防代码)见 A08。
八、缓存
本页只给决策要点,完整机制(决策流程、配置三档、常见误区)见 A04 · 浏览器与网络。
两套机制:
| 类型 | 字段 | 是否发请求 |
|---|---|---|
| 强缓存 | Cache-Control: max-age / Expires | ❌ 不发,200 (from disk cache) |
| 协商缓存 | ETag/If-None-Match(优先)、Last-Modified/If-Modified-Since | ✅ 发,可能回 304(无 body) |
决策口诀:
| 资源 | 配置 |
|---|---|
| 带内容 hash 的 JS/CSS/图片 | Cache-Control: public, max-age=31536000, immutable |
index.html(入口) | Cache-Control: no-cache(每次协商)或 很短 max-age + 发版主动刷 CDN |
| 个性化接口响应 | private, no-store(或 no-cache + ETag) |
| 绝不缓存 | no-store(注意与 no-cache 的区别:no-cache 是"可以存,但每次要问") |
高频追问:
- 为什么带 hash 的资源可以写一年? 因为内容一变文件名就变,浏览器会去请求新 URL,旧缓存自然失效—— 这比
max-age=0+ 协商更快也更省。 - 启发式缓存:响应既没有
Cache-Control也没有Expires时,浏览器可能按Last-Modified推一个 (常见为(now - Last-Modified) * 10%)缓存时间 → 表现为"没配缓存却缓存了"。 Vary的代价:Vary: User-Agent会让 CDN 缓存碎片化、命中率暴跌。一般只Vary: Accept-Encoding。s-maxage/stale-while-revalidate:分别控制共享缓存(CDN)时长、以及"先用旧的再后台更新"。
九、长连接选型
9.1 四种方案横向对比
| 方案 | 方向 | 实时性 | 实现成本 | 关键代价 |
|---|---|---|---|---|
| 短轮询 | 客户端拉 | 差 | 极低 | 大量无效请求;间隔决定延迟 |
| 长轮询 | 半推 | 较好 | 中 | 请求挂起占连接;每次收到后要重发 |
| SSE | 服务端 → 客户端 | 好 | 低(EventSource 自带重连) | 只能单向、只能 GET、文本;HTTP/1.1 下占一个连接 |
| WebSocket | 双向 | 最好 | 高 | 要自己做心跳 + 重连退避 + 断线补偿;代理需支持 Upgrade |
判断顺序(这样答很加分):
- 只需服务端推、数据单向、文本 → SSE。它比 WebSocket 简单一个量级,还自带重连。
- 需要客户端频繁上行 / 二进制 / 低延迟双向 → WebSocket。
- 更新频率低(分钟级)→ 轮询,别过度设计。
- 内部系统、已有网关限制 → 看链路是否支持
Upgrade与长连接。
9.2 WebSocket 工程要点(面试官会追问"那你怎么保证它可靠")
- 握手:HTTP
Upgrade: websocket→101 Switching Protocols。浏览器 API 不能自定义请求头, token 通常走 query / subprotocol / 首个消息;注意别把 token 写进容易被记录的 URL 日志。 - 心跳:TCP 有 keepalive 但默认时间太长(小时级),且中间代理可能静默掐断空闲连接 → 应用层必须自己 ping/pong。
- 重连:指数退避 + 随机抖动(避免服务端恢复瞬间被全体客户端打爆),并限制最大重试次数与降级提示。
- 断线补偿:重连后断连期间的消息已经丢了 → 必须设计补偿(客户端带上一次收到的序号/游标,服务端补发增量,或客户端主动拉全量快照)。 这是"会用 WS"和"能把 WS 上生产"的分水岭。
- 鉴权与超时:握手时鉴权;服务端 idle timeout 要大于客户端心跳间隔。
- 压缩:
permessage-deflate能省带宽,但对敏感数据有类 CRIME/BREACH 的侧信道风险。 - 多标签页:每个标签开一条连接会放大服务端压力 → 用
SharedWorker或主标签页选举实现单连接共享。
十、CDN
10.1 它在哪一层起作用
用户 → 边缘节点(POP)→ (命中直接返回)
→ (未命中)回源 → 源站三类能力:静态加速(边缘缓存)、动态加速(回源链路优化:长连接复用、最优路径、协议优化)、 边缘计算(Edge Functions:在边缘做鉴权、A/B、重定向、改写响应——把逻辑搬到离用户最近的地方)。
10.2 前端必须配对的几件事
| 事 | 说明 |
|---|---|
| 内容 hash + 长缓存 | 资源名带 hash,CDN 可长期缓存;发版 = 换新 URL,无需刷新 |
| HTML 别被 CDN 缓存住 | 否则发版用户拿到的还是旧入口 → HTML 用 no-cache 或发版时主动刷新 |
| 缓存键要写对 | 默认常按 URL;带 query 的接口/资源要明确"是否忽略参数",否则命中率惨不忍睹 |
| CORS 与字体 | 跨域字体必须带 CORS 头,否则被浏览器拒绝 |
| 图片处理 | 用 CDN 的缩放 / WebP-AVIF 协商(按 Accept),别让用户下原图 |
Vary | 谨慎使用,会造成缓存碎片化 |
10.3 常见坑
- 发版不生效:HTML 被缓存 / 回源缓存没刷 → 检查
Age与X-Cache响应头。 - 命中率低:URL 带随机参数、
Vary: Cookie/User-Agent、缓存键包含无关 header。 - 缓存了个性化响应:带
Set-Cookie或用户信息的响应被边缘缓存 → 用private/no-store,或按登录态分流。 - 回源压力:缓存时间太短 + 大量未命中 → 加
stale-while-revalidate/stale-if-error抗雪崩。
10.4 经典坑:502 与 keepalive 超时不匹配
这是"能答出来的都是真干过"的一题。
现象:接口偶发 502,频率很低,重试就成功。 原因:网关(Nginx/CDN)与上游复用长连接,但上游的 idle 超时比网关短—— 上游先关了连接,网关正好拿这个连接发请求 → 收到 RST → 502。
排查与解法:
- 保证 网关的 keepalive 空闲超时 < 上游的(让网关主动回收);
- 网关侧开启 对幂等请求的重试(
proxy_next_upstream error timeout http_502,且只用于 GET/幂等); - 上游关闭
tcp_tw_recycle之类的激进参数;观察netstat里连接状态分布。
十一、排障方法论
11.1 用 Timing 分段定位(第一件事,也是最重要的一件)
Chrome DevTools → Network → 单个请求 → Timing:
| 段 | 长说明什么 |
|---|---|
| Queueing / Stalled | 排队:同域并发打满(HTTP/1.1 的 6 连接)、代理排队、高优先级资源抢占 |
| DNS Lookup | DNS 慢 / 域名太多 → dns-prefetch、HTTPDNS、减少域名 |
| Initial connection / SSL | 建连与 TLS 慢 → 复用连接、升级 TLS 1.3、上 CDN |
| Request sent | 通常很短;很长说明请求体巨大或上行差 |
| Waiting (TTFB) | 服务端处理 + 网络往返 + 回源 → 后端/数据库/CDN 缓存命中率 |
| Content Download | 包体太大 / 带宽不足 / 慢启动 → 压缩、分片、砍体积 |
一句话判断法:
TTFB大 → 找后端/回源;Content Download大 → 找包体;Stalled大 → 找并发与连接;SSL大 → 找 TLS 与链路距离。
11.2 命令行分段(服务端/本地通用)
curl -o /dev/null -s -w \
"dns:%{time_namelookup} tcp:%{time_connect} tls:%{time_appconnect} ttfb:%{time_starttransfer} total:%{time_total}\n" \
https://example.com/apitime_namelookup:DNStime_connect:TCP 建连完成time_appconnect:TLS 完成 →tls - tcp即握手耗时time_starttransfer:TTFBtime_total - time_starttransfer:下载耗时
11.3 抓包与代理
| 工具 | 层 | 用途 |
|---|---|---|
| DevTools / whistle / Charles / mitmproxy | HTTP(S) | 看请求头响应头、改包、mock、弱网模拟。HTTPS 需装并信任根证书(本质是一次 MITM) |
| Wireshark / tcpdump | TCP/IP | 看重传、RST、零窗口、乱序——"偶发慢/偶发断"这类问题只有这层能定性 |
openssl s_client / testssl.sh | TLS | 看证书链、套件、是否支持 1.3 |
dig / nslookup | DNS | 看解析结果、TTL、链路 |
11.4 一张排障对照表
| 现象 | 优先怀疑 | 验证 |
|---|---|---|
| 首屏特别慢,但接口都很快 | 关键链路 RTT 多 / 资源阻塞 | 看瀑布图:是否有串行依赖链;DNS/TLS 段占比 |
| 接口偶发 502 | 上游重启 / keepalive 不匹配(见 10.4) | 网关错误日志 + 连接状态分布 |
| 接口大面积 504 | 上游慢 / 超时配太短 | 对比上游耗时 P99 与网关超时 |
| 控制台 CORS 报错 | 响应头缺失(不是请求没发) | Network 看真实响应 + OPTIONS 是否被拦 |
| 登录态莫名丢失 | SameSite / 跨域未带凭据 / 时钟 / 域名不一致 | 看 Set-Cookie 属性与请求是否携带 |
| 只有部分用户慢 | 地域 DNS 调度错 / 运营商链路 / CDN 节点 | 按地域与运营商维度聚合;换 DNS 对比 |
| 弱网下特别差 | HTTP/2 单连接队头阻塞 / 慢启动 | 试试 1.1 多连接或 h3 对比;看丢包率 |
→ 前端侧的可观测性体系(日志/指标/链路追踪/告警)见 A12。
十二、移动端 / Flutter 对照
有 Flutter 背景的话,这部分是差异化优势——大多数纯前端答不出移动网络的坑。
| 主题 | Web | 移动端(Flutter/Dart) |
|---|---|---|
| 连接复用 | 浏览器自动 keep-alive / h2 多路复用 | HttpClient 需要自己维护连接池与 idle 超时(Dio 默认已做) |
| TLS | 系统信任库 + HSTS | 可证书锁定(pinning):安全性高,但证书轮换会直接让老版本 App 挂掉,且抓包调试变难 → 建议留可配置的开关 |
| 弱网 | 丢包 → h2 队头阻塞 | QUIC 收益更大(连接迁移:WiFi↔4G 不断线);可接 Cronet/系统 QUIC 栈 |
| 明文流量 | 混合内容被阻断 | Android 高版本默认禁明文(networkSecurityConfig 显式开),iOS ATS 同理 |
| DNS | dns-prefetch | App 内常见 HTTPDNS(绕过运营商 Local DNS 劫持) |
| 平台限制 | — | 息屏/切后台会被系统掐连接 → 必须重连 + 断线补偿(同 9.2) |
| 调试 | DevTools 一把梭 | 需代理 + 装根证书;iOS/Android 上抓 HTTPS 比浏览器麻烦得多 |
十三、自检清单
协议层
- [ ] 能画出一次 HTTPS 请求的 RTT 分解,说出每一段能怎么优化
- [ ] 说清三次握手"为什么不是两次",四次挥手"为什么不是三次"
- [ ] 说清 TIME_WAIT 属于哪一端、两个存在理由、CLOSE_WAIT 堆积意味着什么
- [ ] 说清 HTTP/2 解决了什么、没解决什么,以及为什么 HTTP/1.1 的域名分片现在成了负优化
- [ ] 说清 TLS 1.2 与 1.3 的 RTT 差异,以及 0-RTT 的重放风险
- [ ] 说清证书校验的四项,以及 MITM / 证书锁定 / HSTS 的关系
应用层
- [ ] 看到 502/504/499 能立刻说出怀疑方向
- [ ] 判断一个请求是否触发预检,并知道如何消除它
- [ ] 说出
SameSite三档行为差异,能解释"为什么登录态掉了" - [ ] 给带 hash 的静态资源 / HTML / 个性化接口各自配正确的
Cache-Control - [ ] 在 SSE 与 WebSocket 之间做出有理由的选择,并说清 WS 上线需要补的三件事
排障
- [ ] 拿到一个慢请求,能按 Timing 分段定位到是 DNS / 建连 / TTFB / 下载
- [ ] 会用
curl -w在命令行上复现分段耗时 - [ ] 遇到"偶发 502"能想到 keepalive 超时不匹配
十四、常见误区
| ❌ 误区 | ✅ 正解 |
|---|---|
| "跨域是服务端把请求拦了" | 是浏览器拦的响应;预检失败才是没发出去 |
| "HTTPS 用了非对称加密传数据" | 非对称只用来协商密钥,数据走对称加密 |
| "TIME_WAIT 多就是泄漏" | 它是协议设计的一部分;CLOSE_WAIT 多才是代码 bug |
| "HTTP/2 一定比 1.1 快" | 高丢包弱网下单连接反而更慢;要实测 |
"no-cache 就是不缓存" | 是"可以存,但每次必须验证";不缓存是 no-store |
| "301/302 差不多" | 301 被浏览器长期缓存,改不回来;302/303 可能把 POST 降级成 GET |
| "Cookie 里多存点数据省事" | 每个请求都会带上它,白白增加上行;还有 4KB 上限 |
| "WebSocket 连上就完事了" | 没有心跳、退避重连、断线补偿,上线必出事 |
| "加个域名能提升并发" | HTTP/2 之后这是负优化(多 DNS + 多 TLS + 多慢启动) |
| "装了 HTTPS 就安全了" | 还有混合内容、HSTS、Cookie 属性、CSP 一整套要配 |
相关文档
- 面试题总览 → I00 总览
- 安全与鉴权(XSS/CSRF/CSP)→ A08 · 前端安全
- 缓存与 CORS 完整机制 → A04 · 浏览器与网络
- 性能度量与诊断 → A25 / 优化清单 → A10
- 线上排障体系 → A12 / 踩坑区
- 系统设计类网络题 → IX · 综合场景题
- 后端视角(未写)→ 计划见 docs/plan/network.md