前端实时推送 & WebSocket 面试题(2026版)

一、历史背景 + 时间轴

网页一旦需要 "实时" ,麻烦就开始了:数据在不断变化,用户却只能等下一次刷新;

  • 刷新解决不了的延迟,用短轮询凑数,又被无数空请求反噬;
  • 再加长轮询,试图把"有了新数据再说"变成一种伪推送,却仍困在请求---响应的笼子里。
  • 开发者于是继续前探:让连接不再频繁重建,尝试分块直输,把事件像水一样持续送达,于是有了更顺滑的 Streaming 与标准化的 SSE。

直到某一刻,我们不再满足于"更聪明的单向",而是迈向真正的"同时说话与倾听"------WebSocket 把通信从一次次请求,变成一条持久而通透的通道。此后,

  • HTTP/2、HTTP/3 与 QUIC 又在底层为效率和时延开了绿灯,甚至提供了可选可靠与无序传输的更多可能。

接下来,我们就沿着这条主线,层层展开:它们各自解决了什么、在哪些场景最合拍、又如何在你的系统里形成清晰的选型边界。

01|从整页刷新出发:减少浪费的一条链路

这一块是为了解决"整页刷新导致的高延迟与带宽浪费",逐级细化与优化。

a. 早期网页:整页刷新

  • 背景与问题:每次更新都整页请求,体验割裂、带宽浪费、延迟高。
  • 直接影响:促使前端与服务端思考"只取变化"。

b. 短轮询(Short Polling)为解决整页刷新的低效

  • 解决:改为"隔一段时间拉一次",显著减少整页重载带来的浪费。
  • 局限:高频请求带来大量空响应与服务器开销。
  • 承接改进:为减少空转,演进到长轮询;同时催生更流式的思路(Streaming/SSE)。

c. 长轮询(Comet/挂起请求)为减少短轮询的空转

  • 解决:请求挂起,服务器有新数据才返回,接近"伪推送",显著降低空转。

  • 局限:本质仍是请求-响应;连接频繁重建;难做真正双向。

  • 承接改进:

    • 单向推送更稳:SSE 标准化单向事件流。
    • 若要真双向与二进制:交给 WebSocket(见独立块)。

d. HTTP Streaming(分块传输/持续输出)为进一步降低重连与延迟

  • 解决:保持连接,分块持续输出,适合连续文本/事件流,重连更少、延迟更低。
  • 局限:多为单向,受代理/中间件影响,兼容性不一。
  • 承接改进:单向事件由 SSE 标准化;双向场景仍需 WebSocket。

e. SSE(Server-Sent Events)单向推送的标准化终点

  • 解决:以标准事件流语义提供单向推送,浏览器原生支持,资源占用低。
  • 适配范围:通知、进度、日志/监控等文本或事件流。
  • 位置关系:在"只需单向推送"的场景中,SSE 是这一链条的稳定落点,而非过渡技术。

02|范式跃迁:WebSocket(独立大块)

这不是前面链条的"又一改良",而是从请求-响应转向全双工持久连接的范式变化。

WebSocket(全双工、持久、低开销)

  • 解决:真正的双向实时通信,降低握手与头部开销,支持文本与二进制,端到端延迟低。

  • 典型场景:聊天、协同编辑、在线游戏、行情推送、IoT。

  • 与上一链条的关系:

    • 在"需要双向实时"的主战场,实质上取代了短轮询/长轮询等过渡方案。
    • 与 SSE 并存:若只有单向通知/事件流,SSE 更简单更省资源;若需要双向或二进制,WebSocket 更合适。
  • 运维关注:连接状态管理、容量与反压、企业代理/负载均衡兼容。

03|底座升级与新选项:HTTP/2·HTTP/3·QUIC 家族

这部分不是替代前两块,而是提供更高效的承载与更灵活的传输语义。

WS over H2/H3

  • 价值:与同域请求复用连接、更好穿透与效率、更低握手成本。
  • 作用:让 WebSocket 的部署与网络效率更优。

WebTransport(基于 QUIC)

  • 价值:可选可靠与有序/无序、更低延迟,适合实时媒体、游戏、定制协议。
  • 关系:不是取代 WebSocket/SSE 的通吃方案,而是当你需要"更细粒度的可靠性与顺序控制"时的新工具。

二、速查表

实时推送的目标是"低延迟、双向或单向地把数据从服务端送到客户端"。主流技术选型包括:

三、WebSocket 核心定义(重要)

WebSocket 是 HTML5 推出的一种全双工(Full-Duplex)、持久化(Persistent)的网络通信协议, 基于TCP协议构建,允许客户端(浏览器)与服务器之间建立一条长期稳定的连接通道,实现「服务器主动向客户端推送数据」和「客户端实时向服务器发送数据」的双向通信,无需频繁建立/断开连接。

其核心特点可概括为:

  • 全双工:通信双方可同时发送/接收数据(区别于HTTP的「请求-响应」单向通信);
  • 持久连接:连接建立后长期保持,避免HTTP每次通信都需重新握手的开销;
  • 轻量协议:数据帧头部信息简洁(仅2-14字节),传输效率远高于HTTP;
  • 协议标识:客户端发起连接时使用ws://(非加密)或wss://(加密,基于TLS,类似HTTPS)作为协议前缀。

从零开始的完整流程

下面是一条你在前端真实会走的链路:创建连接 → HTTP 握手与协议切换 → 进入 WebSocket 双向通信 → 启动心跳检测 → 发现异常并重连 → 重连成功后的补偿 → 服务端跨域放行 → 正常/异常关闭。


1) 创建连接(入口)

你写下第一行代码时,要解决两件事:用对协议前缀、绑定好事件。

  • 如果网页是 HTTPS 必须用 wss://HTTP 页面用 ws://
  • 立刻绑定 onopen/onmessage/onerror/onclose,以便后续重用。

示意代码(精简):

  • new WebSocket(url)
  • 绑定事件:initWebSocketEvents(ws)

2) HTTP 握手与协议切换(从"请求"到"长连")

客户端(浏览器) 创建 WebSocket 实例时,会发起一个特殊的 HTTP - GET,核心目的是 「请求将协议从HTTP升级为WebSocket」 。服务端验证通过后返回 101,双方切换到 WebSocket 帧通信。

WebSocket 帧是双向通信中的最小传输结构,携带 " 数据类型 是否为消息的最后一段 负载长度 掩码/密钥 实际数据 " 。消息可以被拆成多帧连续发送,也可以一个帧就送完。

客户端发起「协议升级请求」(HTTP - GET 请求)

请求头中关键字段(面试高频考点):

  • GET /ws-endpoint HTTP/1.1:请求方法为 GET,路径为服务器的 WebSocket 端点(如/ws);
  • Host:example.com:服务器域名;
  • Upgrade:websocket:核心字段,告知服务器「要升级协议为 WebSocket」;
  • Connection:Upgrade:配合 Upgrade,表示「这是一个协议升级请求」;
  • Sec-WebSocket-Key:dGhlIHNhbXBsZSBub25jzQ==:客户端生成的随机字符串(Base64 编码,长度 16 字节),用于服务器验证(防止恶意连接);
  • Sec-WebSocket-Version:13:WebSocket 协议版本(当前主流为 13,需服务器支持);
  • Sec-WebSocket-Origin:https://example.com:客户端所在域名(用于服务器跨域验证)。

服务器响应「协议升级成功」(HTTP - 101状态码)

服务器收到请求后,若支持 WebSocket 协议且验证通过(如 Sec-WebSocket-Key 验证、跨域验证) ,会返回HTTP - 101(SwitchingProtocols)状态码,表示「同意协议升级」。

响应头中关键字段 (面试高频考点):

  • HTTP/1.1 101 Switching Protocols:101 状态码是协议切换的标志;

  • Upgrade: websocket:确认升级为WebSocket 协议;

  • Connection:Upgrade:确认连接用于协议升级;

  • Sec-WebSocket-Accept:s3pPLMBiTxaQ9kYGzzhZRbK+xOo=:服务器对Sec-WebSocket-Key 的处理结果(核心验证逻辑):

    1. 服务器将客户端发送的 Sec-WebSocket-Key 与固定字符串 258EAFA5-E914-47DA-95CA-C5AB0DC85B11 拼接;
    2. 对拼接后的字符串进行 SHA-1 哈希计算;
    3. 将哈希结果转为 Base64 编码,即为 Sec-WebSocket-Accept 的值;
    4. 客户端会验证该值是否正确,若不正确则拒绝建立连接(防止伪造响应)。

3) 进入通信阶段(双向数据 + 基础发送)

握手通过,onopen 会触发。此时做两件事:

  • 发送初始化数据(如身份、订阅主题)
  • 启动心跳(下一步会讲)

通信注意:

  • onmessage 既可能是字符串,也可能是二进制(Blob/ArrayBuffer)
  • bufferedAmount 可用来做背压控制(积压太大时暂停继续 send)

4) 启动心跳(让连接"活着"且可感知)

持久连接会遭遇 Wi‑Fi 抖动、防火墙清理等问题。心跳=周期性发 ping,超时未收到 pong 就判死链

推荐参数(可按业务调优):

  • HEARTBEAT_INTERVAL ≈ 30s
  • HEARTBEAT_TIMEOUT ≈ 10s

实操要点:

  • 启动前先清理旧定时器,避免重复
  • 收到 pong 立即清除超时定时器
  • onclose/onerror 必须停止心跳

5) 异常→重连(恢复连接但不过载)

一旦 onerroronclose(code ≠ 1000) 或心跳判定超时,进入重连。

目标是:能恢复、不过载、可被用户停止。

策略三件套:

  • 指数退避:1s → 2s → 4s ... 最多 30s
  • 次数上限:如 10 次(达到即停)
  • 可控停止:提供"停止重连"或页面关闭时停

补偿机制:

  • 在断开前缓存"待发送"数据(例如未发出的聊天消息)
  • 重连成功后按序补发,确保业务连续性

6) 服务器放行跨域(握手能否过关的关键)

虽然 WebSocket 原生"支持跨域",但握手是 HTTP,服务端需要对 Sec-WebSocket-Origin 做白名单校验。

否则会 403 或直接关闭。

  • Node.js(ws)

    • 读取 req.headers['sec-websocket-origin']
    • 不在 allowedOrigins 列表:关闭 1008 "Cross-origin access denied"
  • Spring Boot

    • registry.addHandler(...).setAllowedOrigins("https://a.com", "https://b.com")
    • 需要时 .withSockJS()提供降级

7) 正常关闭与资源清理(善始善终)

  • 用户离开页面或主动退出:ws.close(1000, '用户主动退出')
  • onclose 中停止心跳与重连,清空定时器与队列,避免泄漏
  • 记录关闭原因码:1000 正常、1006 常见于异常/心跳超时

四、面试题

面试关注点通常围绕"协议对比、连接管理、消息语义、可靠性与扩展性、安全与运维成本"。

1、WebSocket 与 SSE 的差异与使用场景,HTTP轮询呢?

WebSocket(全双工,二进制/文本)

  • 适用:即时聊天、协作编辑、游戏状态同步、行情推送、需要客户端→服务端主动上行的实时交互。
  • 优点:低延迟、头开销小、全双工、支持二进制、可自定义子协议。
  • 注意:需处理心跳、重连、背压、鉴权与扇出扩展;代理/LB 要正确透传 Upgrade 和超时设置。

Server-Sent Events(SSE,单向 server→client)

  • 适用:通知流、日志流、监控事件、流式生成文本(如增量输出)、只需服务端下行的实时更新。
  • 优点:浏览器原生 EventSource、文本流、自动重连、支持 Last-Event-ID 断点续传;实现简单。
  • 注意:单向、仅文本(可 base64 二进制)、连接数限制与代理超时需要关注;移动端网络切换要做容错。

HTTP 轮询/长轮询(兼容兜底)

  • 适用:对实时性要求不高的小流量场景、受网络环境或企业防火墙限制无法使用 WS/SSE 时的兜底。
  • 优点:最易落地、与缓存/鉴权/监控体系天然兼容;对中间设备最友好。
  • 注意:延迟更高、资源利用低;高频轮询会带来成本与限流压力。

✅ 重点

  • WebSocket 通过 HTTP/1.1 Upgrade → 101 完成切换,此后是帧协议的全双工通道;Keep-Alive 仅是复用 TCP,不改变 HTTP 的请求-响应语义。
  • 选型规则:需要双向实时交互选 WebSocket;单向事件流选 SSE;受限或低实时性场景用轮询作兜底。

2、如何设计一个可水平扩展的实时推送系统?

在可水平扩展的实时推送系统中,WebSocket 连接会分布在多台网关节点上。

核心挑战是如何在连接与消息不在同一台机器时,仍能把消息快速路由到正确的连接。可行的范式是

  • 网关层负责连接
  • 管道层负责路由与发布订阅
  • 存储层负责状态与回放

🔌 网关层(负责连接)

  • 终止 TLS/WS,维持心跳与速率限制,保持无状态实例。
  • 建立本地索引:connectionId → 订阅集合,userId → connectionIds。
  • 将连接元数据上报共享存储:connectionId、userId、nodeId、订阅、最近心跳。
  • 仅订阅"与自己有关的分片":按 userId/topic 的哈希分片从管道层拉取,减少无关扇出。
  • 写通道背压与优先级:控制帧/关键消息优先,低优先级可丢尾或抽样。

🚇 管道层(负责路由与发布订阅)

  • 选型具备分片与回放能力的总线(Kafka/Pulsar/NATS/Redis Streams)。

  • 分片策略

    • 点推:按 userId/connectionId 一致性哈希到分区,保证单用户局部有序。
    • 主题推送:按 topic 分区,网关本地再做订阅过滤与扇出。
  • 路由方式

    • 生产者只需写对的分片;总线按分区把消息送到订阅该分片的网关。
    • 广播/超大房间采用"分层扇出":先到分片,再由各网关本地扇出,必要时加中间扇出代理。
  • 去重与幂等:messageId 或 (topic, partition, offset) 作为幂等键,网关/客户端各自维护短期去重集合。

🗃️ 存储层(负责状态与回放)

  • 会话与订阅状态:使用 Redis Cluster 或 KV 服务存 userId→connectionIds、connectionId→nodeId、订阅清单、心跳时间。
  • 游标与回放:在总线层保留 offset;客户端重连携带 resumeToken,网关据此恢复订阅并按 offset 增量补发。
  • 一致性与更新:订阅变更写事件流,相关网关消费后刷新本地索引;用版本号/逻辑时钟避免乱序覆盖。

3、如何保证消息不丢、不重、按序?

  1. 不丢: 消息先落到能持久化、带副本确认的总线里(像"写盘且多副本到位才算成功"),写失败就退避重试;消费侧是"先送到用户手里或进可靠下行队列,再更新位点",断线后拿着 resumeToken+offset 从保留的历史里把漏掉的补回来。
  2. 不重: 每条消息都有一个不会撞车的"指纹"(messageId 或 topic-partition-offset);网关用一小块内存做近端去重,只有第一次真正写入才前进位点,重复的一概忽略;客户端也按同一指纹做幂等处理,避免业务状态被二次改动。
  3. 按序: 把需要有序的对象(userId/roomId)哈希到同一分区,借用分区内天然顺序;同一个键在网关里串行发送、同队列内重试,不跨分区不并行穿插,这样即使重试和补发也不会把顺序打乱。

4、心跳如何设计?超时如何判定?

这里的心跳,目标是 "保活、探测、可平滑重连"。

  1. 不失联: 用应用层 ping/pong,客户端主发、服务端回;

    1. 间隔 20--60s,加±10%抖动
    2. 未知网络时取 20--30s,确保小于最短 NAT 空闲回收。
  1. 怎么判死: 别一跳不回就拍板。记录 lastSeen允许 2--3 次心跳未达或 now-lastSeen 超过 2--3 个周期再判断 ;进入"Suspect"时降级写入,仍有业务流量即立刻恢复。
  2. 断了咋办:重连走指数退避并带抖动,携带 resumeToken/offset 补发;移动端切网优先复用会话,失败再重建。监控 RTT/丢包与 Suspect 比例,自动调心跳与阈值。

5、如何在 Nginx/Envoy 反向代理后稳定运行 WebSocket?

核心思路:让代理"不瞎操心"、连接"常被看见"、后端"可续上"。

  • 代理设置:开启 WebSocket 升级;调大超时,禁用缓冲与压缩;保持 TCP keepalive,HTTP/2 用 CONNECT(H2/WebSocket)。
  • 心跳与保活:应用层 ping/pong 20--30s(±10%抖动),保证小于代理/NAT空闲回收;大连接数用轻量负载均衡(hash by userId/roomId)避免跨节点迁移。
  • 断线与重连:客户端指数退避+抖动,带会话 token/offset 续传;后端幂等去重,重放不重不丢。
  • 运维与观测:开代理层指标(升级成功率、idle 关闭数、5xx)、RTT/丢包与重连率,异常时自适应缩短心跳或放宽超时。

6、如何做鉴权与权限隔离?

握手前校验 JWT;Subprotocol 指定租户/版本;频道级 ACL;避免敏感数据从客户端请求非授权频道。

  • 握手前校验 JWT :在 HTTP Upgrade 前验证 iss/aud/exp/签名并解析 tenant_id/user_id/scopes,避免建立长连后再踢。
  • Subprotocol 指定租户/版本:用 Sec-WebSocket-Protocol 携带 tenant 和策略版本做白名单匹配,确保连接上下文一致。
  • 频道级 ACL:频道强制以租户前缀命名,每次 subscribe/publish 依据 RBAC+scope 前缀(到资源或前缀)做服务端授权。
  • 避免敏感数据越权:仅按服务器维护的"已授权订阅集合"下发数据,忽略客户端自报筛选请求并拒绝未授权频道。

7、如何评估性能与成本?

  • 每连接内存占用: 用基线压测量出 MB/1k 连接的实际占用,结合目标并发外推单机上限并监控 GC/碎片。
  • 每秒消息数(fanout×频率): 用发布频率×平均扇出得到总吞吐,核算带宽与发送队列容量,识别热点频道放大效应。
  • 尾延迟 P95/P99: 持续跟踪端到端延迟长尾并关联队列深度与CPU/GC事件,确保在SLA红线下仍稳定。
  • 压测考虑广播峰值与重连风暴: 分别模拟大扇出瞬时广播与大量短时间内握手重连,验证背压、限速和握手路径的韧性。

8、遇到"重连风暴"怎么处理?

  • 抖动退避(指数退避 + 随机抖动): 客户端按指数退避间隔重试并加入随机抖动,避免同相位同时重连造成尖峰。
  • 分批恢复: 将连接恢复按固定批次/时间片发放(如每 100ms 开放 N 个),把尖峰摊平到更长窗口。
  • 服务端限流与排队: 在握手与认证路径设置并发/速率上限与队列,超限直接返回可重试错误或延迟令牌。
  • 灰度放量: 按租户/区域/版本逐步提升允许重连比例,结合健康度与错误率自动调节放量速度。

9、前端如何封装一个健壮的 WebSocket 客户端?

  • 状态机( CONNECTING / OPEN / CLOSING / CLOSED ): 用有限状态机驱动所有事件与迁移,单航道控制避免并发重连与回调竞态。
  • 心跳/重连策略: 按固定心跳探活与半开检测,重连采用指数退避叠加随机抖动并设上限与冷却期。
  • 消息序列化: 统一 envelope(type/id/ts/payload),默认 JSON,性能敏感时切 Protobuf/MessagePack 并保持向后兼容。
  • 离线缓存与去重: 未连通时将待发入队、跨刷新用 IndexedDB,按 seq/uuid 去重并用 last-seq 做断点续传。
  • 可观测日志: 记录连接尝试/关闭码/重连次数/心跳RTT/队列长度等指标与事件,便于快速定位长尾与故障。
相关推荐
寻星探路10 小时前
【深度长文】万字攻克网络原理:从 HTTP 报文解构到 HTTPS 终极加密逻辑
java·开发语言·网络·python·http·ai·https
王达舒199410 小时前
HTTP vs HTTPS: 终极解析,保护你的数据究竟有多重要?
网络协议·http·https
朱皮皮呀10 小时前
HTTPS的工作过程
网络协议·http·https
Binary-Jeff10 小时前
一文读懂 HTTPS 协议及其工作流程
网络协议·web安全·http·https
崔庆才丨静觅11 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby606112 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了12 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅12 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅13 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端