一、历史背景 + 时间轴
网页一旦需要 "实时" ,麻烦就开始了:数据在不断变化,用户却只能等下一次刷新;
- 刷新解决不了的延迟,用短轮询凑数,又被无数空请求反噬;
- 再加长轮询,试图把"有了新数据再说"变成一种伪推送,却仍困在请求---响应的笼子里。
- 开发者于是继续前探:让连接不再频繁重建,尝试分块直输,把事件像水一样持续送达,于是有了更顺滑的 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 的处理结果(核心验证逻辑):- 服务器将客户端发送的 Sec-WebSocket-Key 与固定字符串
258EAFA5-E914-47DA-95CA-C5AB0DC85B11拼接; - 对拼接后的字符串进行
SHA-1哈希计算; - 将哈希结果转为 Base64 编码,即为 Sec-WebSocket-Accept 的值;
- 客户端会验证该值是否正确,若不正确则拒绝建立连接(防止伪造响应)。
- 服务器将客户端发送的 Sec-WebSocket-Key 与固定字符串
3) 进入通信阶段(双向数据 + 基础发送)
握手通过,onopen 会触发。此时做两件事:
- 发送初始化数据(如身份、订阅主题)
- 启动心跳(下一步会讲)
通信注意:
onmessage既可能是字符串,也可能是二进制(Blob/ArrayBuffer)bufferedAmount可用来做背压控制(积压太大时暂停继续 send)
4) 启动心跳(让连接"活着"且可感知)
持久连接会遭遇 Wi‑Fi 抖动、防火墙清理等问题。心跳=周期性发 ping,超时未收到 pong 就判死链。
推荐参数(可按业务调优):
HEARTBEAT_INTERVAL ≈ 30sHEARTBEAT_TIMEOUT ≈ 10s

实操要点:
- 启动前先清理旧定时器,避免重复
- 收到 pong 立即清除超时定时器
- onclose/onerror 必须停止心跳
5) 异常→重连(恢复连接但不过载)
一旦 onerror、onclose(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、如何保证消息不丢、不重、按序?
- 不丢: 消息先落到能持久化、带副本确认的总线里(像"写盘且多副本到位才算成功"),写失败就退避重试;消费侧是"先送到用户手里或进可靠下行队列,再更新位点",断线后拿着
resumeToken+offset从保留的历史里把漏掉的补回来。 - 不重: 每条消息都有一个不会撞车的"指纹"(messageId 或 topic-partition-offset);网关用一小块内存做近端去重,只有第一次真正写入才前进位点,重复的一概忽略;客户端也按同一指纹做幂等处理,避免业务状态被二次改动。
- 按序: 把需要有序的对象(userId/roomId)哈希到同一分区,借用分区内天然顺序;同一个键在网关里串行发送、同队列内重试,不跨分区不并行穿插,这样即使重试和补发也不会把顺序打乱。
4、心跳如何设计?超时如何判定?
这里的心跳,目标是 "保活、探测、可平滑重连"。
-
不失联: 用应用层
ping/pong,客户端主发、服务端回;- 间隔 20--60s,加±10%抖动
- 未知网络时取 20--30s,确保小于最短 NAT 空闲回收。
- 怎么判死: 别一跳不回就拍板。记录
lastSeen,允许 2--3 次心跳未达或now-lastSeen超过 2--3 个周期再判断 ;进入"Suspect"时降级写入,仍有业务流量即立刻恢复。 - 断了咋办:重连走指数退避并带抖动,携带
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/队列长度等指标与事件,便于快速定位长尾与故障。