Skip to content

实时通信方案演进(轮询→长轮询)

基于 HTTP 现代标准 · 核于 2026-06

速查

  • 根本矛盾:HTTP 是请求-响应(request-response)模型,连接由客户端发起,服务器只能「被问到才答」,无法主动把数据推给客户端——这是一切实时方案要绕开的限制。
  • 拉(pull) vs 推(push):轮询家族都是「客户端反复拉」来模拟推送;真正的推送需要 SSE(服务器→客户端单向)或 WebSocket(全双工)这类持久连接
  • 短轮询 short polling:客户端定时发请求问「有新数据吗」,无论有没有都立刻返回。实现最简单,但延迟高(取决于轮询间隔)、空轮询多、开销大
  • 短轮询的浪费:绝大多数请求返回空也照样付出完整的 HTTP 往返 + 首部 + (可能的)TCP/TLS 建连成本,QPS 随客户端数线性膨胀。
  • 长轮询 long polling:服务器hold 住请求不立即响应,直到有新数据超时才返回;客户端收到后立刻再发下一个请求。
  • 长轮询的收益:把「无数据时的空响应」消掉,几乎每个请求都带回有效数据延迟接近实时(数据一到即返回)。
  • 长轮询的代价本质仍是 HTTP——每条消息一次完整请求-响应、重复携带首部/Cookie;服务端要长时间占用连接,海量并发挤占内存与文件描述符。
  • 长轮询的弱点:不支持多路复用/连接复用,消息顺序与可靠性无保证,「响应→重连」的间隙存在丢消息窗口,需配合消息序号/缓冲补救。
  • Comet:长轮询、HTTP 流式(hidden iframe / XHR streaming)、BOSH 等一类「在 HTTP 上模拟服务器推送」技术的统称,是 SSE/WebSocket 普及前的主流方案。
  • 演进方向:单向推送(如行情、通知、日志流)→ SSE(HTTP 原生、text/event-stream、自带重连);双向高频交互(如聊天、协作、游戏)→ WebSocket(一条 TCP 上全双工)。
  • 选型直觉:能用更轻的就别上更重的——单向选 SSE,双向才上 WebSocket,轮询只在无法持久连接实现成本敏感的兜底场景才用。

一、为什么需要「实时通信」

Web 早期的页面是「点一下、刷一次」:浏览器发请求,服务器回响应,连接随即可丢。这套 HTTP 请求-响应模型在「用户主动获取」的场景下工作得很好,但它有一个结构性限制

HTTP 的根本局限

HTTP 连接只能由客户端发起。服务器是被动的一方——没有客户端的请求,服务器无法主动把数据送达浏览器。于是「服务器有了新数据想第一时间通知前端」这件事,在原生 HTTP 语义下做不到

可现实里到处都是「服务器侧先有变化、希望前端尽快知道」的需求:

  • 聊天/IM 的新消息、协作文档的他人光标
  • 股票行情、体育比分、运维监控大盘
  • 订单状态变更、支付回调、扫码登录确认
  • 构建日志、AI 流式回答的逐字输出

这类需求统称实时通信(real-time communication)。既然服务器不能主动推,工程师最早只能用**客户端反复「拉」**来逼近「推」的效果——这就是轮询(polling)的由来。理解推与拉的分界,是理解后续所有方案的钥匙:

模型谁发起数据流向代表
拉 pull(请求-响应)客户端客户端按需取普通 HTTP、短轮询、长轮询
推 push服务器服务器主动送SSE(单向)、WebSocket(双向)

注意:短轮询、长轮询本质都还是「拉」,只是用不同手法模拟出推送的体感;真正的「推」要等到持久连接登场。

二、短轮询(short polling)

最朴素的思路:客户端定时问,服务器随到随答。前端每隔固定间隔(如 3 秒)发一次请求问「有新数据吗」,服务器不管有没有都立刻返回。

客户端                                  服务器
  │ ──── GET /messages?since=… ────►   │  t=0s
  │ ◄──── 200 [](没有新数据) ─────    │
  │   …(等 3 秒)…                     │
  │ ──── GET /messages?since=… ────►   │  t=3s
  │ ◄──── 200 [](还是没有) ───────    │
  │   …(等 3 秒)…                     │
  │ ──── GET /messages?since=… ────►   │  t=6s
  │ ◄──── 200 [{新消息}] ──────────    │  ← 数据其实 t=4s 就到了,却拖到 t=6s 才被取走

优点:实现极其简单,前端一个定时器 + 一个普通接口即可,服务端无需任何特殊状态,对现有 HTTP 基建零侵入

缺点也很直白:

  • 延迟高:消息平均要等约「半个轮询间隔」才被取走,间隔越大延迟越高(上图数据 t=4s 到、t=6s 才取到)。
  • 空轮询浪费:绝大多数请求返回空也照样付出完整开销——TCP/TLS(若每次重连)、HTTP 首部、Cookie、鉴权校验、一次服务器处理。
  • 开销随规模线性膨胀:N 个在线客户端 × 每秒 1/间隔 次请求 = 巨大的恒定 QPS,且与是否真有数据无关

调间隔治不了本病

缩短间隔 → 延迟降但空请求暴涨;拉长间隔 → 省请求但延迟变差。短轮询永远在「实时性」和「开销」之间二选一,无法两全——这正是长轮询要解决的痛点。

三、长轮询(long polling)

长轮询只改一处关键动作:服务器收到请求后不立即回,而是把连接「挂起(hold)」,直到有新数据到达超时才返回;客户端一拿到响应,立刻发起下一个请求接着等。

客户端                                  服务器
  │ ──── GET /messages?since=… ────►   │  t=0s,服务器 hold 住,不返回
  │           (连接保持打开…)          │  ← Keep-Alive,超时设得很长
  │                                     │  t=4s:有新消息了!
  │ ◄──── 200 [{新消息}] ──────────    │  ← 数据一到立即响应(延迟≈0)
  │ ──── GET /messages?since=… ────►   │  收到后「立刻」再发,进入下一轮等待
  │           (继续 hold…)            │
  │ ◄──── 200 [](到达超时,空返回) ─   │  t=30s:超时,正常返回,客户端再续一轮

相比短轮询,长轮询带来两点质变:

  • 几乎每个请求都带回有效数据——消灭了「问了却没有」的空响应(超时返回是少数例外),无效往返大幅下降。
  • 延迟接近实时——数据在服务端一旦就绪即返回,不用再等下一个轮询周期。

实现要点(按 Ably 等资料的标准流程):

  1. 客户端发起一个普通 GET 请求;
  2. 服务器用 Keep-Alive 保持连接,设一个很长的超时,在此期间挂起不响应
  3. 一旦有数据,服务器结束本次响应把数据送回;若超时未等到数据,则返回空响应(避免连接被中间设备掐断);
  4. 客户端收到响应(无论有无数据)后立即再发一个请求,循环往复。

Comet:HTTP 上「伪推送」技术的统称

长轮询属于一个更大的家族——Comet。它泛指 SSE/WebSocket 标准化之前,一切在 HTTP 之上模拟服务器推送的技巧,主要有三类:

  • 长轮询(long polling):如上,hold 住请求直到有数据。
  • HTTP 流式(streaming):服务器保持一个长开响应持续往里写数据块,常见实现有 hidden iframe(不断塞 <script> 片段更新页面)和 XHR streaming
  • BOSH(Bidirectional-streams Over Synchronous HTTP):在无法用持久 TCP 连接时,作为长轮询的双向流式替代,曾广泛用于 XMPP(即时通讯)。

这些都是「巧劲」,本质仍受 HTTP 单向请求模型约束——它们正是 SSE 与 WebSocket 要替代的对象。

四、长轮询仍要付的代价

长轮询解决了「空轮询」,但没有改变底层是 HTTP 这一事实,代价依旧存在:

  • HTTP 头部重复开销:每条消息都是一次完整的请求-响应,反复携带相同的请求头、Cookie、鉴权信息——高频小消息时首部占比可能远超有效载荷
  • 连接长时间占用:服务器要同时挂起成千上万个连接等待数据。Ably 指出,即便在异步(event-loop)架构下,管理海量并发长轮询连接仍会显著抬高内存与计算负载,并消耗文件描述符等资源。
  • 无多路复用/连接复用:不像为持久连接设计的协议(如 WebSocket),长轮询无法在一条连接上复用多路消息,连接利用率低。
  • 消息可靠性与顺序无保证:Ably 明确指出长轮询不保证消息按序到达、甚至不保证一定到达;尤其在「收到响应」到「下一个请求发出」的间隙里服务端若产生新消息,存在丢消息窗口,需要用 since/序号 + 服务端缓冲来补救。
  • 代理与超时不可控:中间的反向代理、负载均衡可能有自己的空闲超时,把被 hold 的连接提前掐断,需要小心对齐超时配置。

下面把两种轮询并排对比,痛点一目了然:

维度短轮询 short polling长轮询 long polling
触发方式客户端定时发,服务器立即答服务器 hold 住,有数据/超时才答
实时性差(受间隔限制)好(数据就绪即返回)
空请求多(绝大多数空返回)少(仅超时时空返回)
服务器状态无需挂起需长时间挂起大量连接
头部开销高且频繁仍是每消息一次 HTTP 往返
实现复杂度最低中(超时/重连/丢消息处理)
本质HTTP 拉,模拟推HTTP 拉,模拟推(更逼真)

五、从轮询走向真正的「推」

轮询家族再怎么优化,也始终被 HTTP「客户端发起 + 请求-响应」的模型困住——真正的服务器推送,需要一条持久连接。这就引出了两条现代标准路线:

  • SSE(Server-Sent Events):在一条 HTTP 长连接上做服务器→客户端的单向推送,MDN 强调它是单向的——「这是一条单向连接,你不能从客户端向服务器发送事件」。它用标准 HTTP(Content-Type: text/event-stream)、自带断线重连与事件 ID,特别适合行情、通知、日志、AI 流式输出这类「服务器单方面推」的场景。详见 下一页 SSE 服务器推送
  • WebSocket:一次 HTTP 握手后升级(Upgrade)为持久的 TCP 连接,此后在同一条连接上全双工收发,客户端与服务器都能随时主动发消息,适合聊天、协作、在线游戏等高频双向交互。详见本叶后续《WebSocket 协议握手与帧》。

一句话选型

单向推用 SSE,双向交互用 WebSocket,轮询留给「无法建立持久连接」或「实现成本极度敏感」的兜底场景。本叶最后一页 实时方案对比与选型 会给出完整决策表。

小结

  • HTTP 的请求-响应模型决定了服务器无法主动推送,实时通信的所有方案都是在「绕开或突破」这一限制。
  • 短轮询用定时请求模拟推送,实现最简单,但延迟高、空请求多、开销随规模线性增长,调间隔无法两全。
  • 长轮询让服务器 hold 住请求直到有数据或超时,消灭空响应、延迟接近实时,但本质仍是 HTTP:头部重复、连接占用、无多路复用、消息顺序/可靠性无保证。
  • Comet 是 SSE/WebSocket 之前「在 HTTP 上伪造推送」的技术统称(长轮询、HTTP 流式、BOSH 等)。
  • 真正的服务器推送要靠持久连接:单向用 SSE,双向用 WebSocket

下一页进入真正的服务器推送:SSE 服务器推送