前端知库

短轮询、长轮询、SSE 和 WebSocket 有什么区别?怎么选?

短轮询是隔一段时间问一次,没数据也回空响应;长轮询是服务器把请求挂住,有数据才回;SSE 是一条一直不结束的 HTTP 响应,服务器单向推;WebSocket 是双向连接。按“谁要发消息、多实时、基础设施支持什么”来选。

参考答案

简答

比如订单页要及时显示“已发货”。最简单的是短轮询,每隔几秒发一次请求问服务器,没有新数据就回一个空响应。长轮询是服务器收到请求先不回,等有新数据或者超时再回,浏览器收到后马上发下一个。SSE 是服务器回一个一直不结束的 HTTP 响应,有事件就往里写。WebSocket 是先握手升级,然后浏览器和服务器在同一条连接上双向收发。

它们的差别来自“谁先开口”。轮询都是浏览器去拉,短轮询的延迟取决于间隔,间隔越短,服务器和网络的负担越重;长轮询能在有数据时立刻返回,但每条消息都是完整的 HTTP 请求和响应,带一整套头。SSE 和 WebSocket 是连接建好后服务器直接推。

选的时候看需求。只有服务器往浏览器推,比如通知、进度、大模型的流式输出,用 SSE 就够了,它走普通 HTTP,浏览器还会自动重连。聊天、协同编辑这种双向高频的场景用 WebSocket。更新不频繁、或者环境不支持长连接时,轮询最简单可靠。

要注意,长轮询、SSE 和 WebSocket 都要服务器为每个客户端长期占着连接,规模大时要算好资源;SSE 只能传文本,WebSocket 不自带重连和消息确认,要自己写。

短轮询和长轮询差在哪

RFC 6202 把这两种做法放在一起比较。短轮询(short polling)是浏览器定期发请求“拉”数据,服务器没有数据就回空响应,浏览器等一会儿再问。所以轮询频率由能接受的延迟决定,要是想做到秒级延迟,频繁的请求就会给服务器和网络带来不小的负担。

长轮询(long polling)把等待挪到了服务器那边。服务器收到请求后先不回,直到有了新数据、状态变化或者超时才回复,浏览器收到响应后通常马上发下一个请求。这样服务器手里几乎总挂着一个请求,有新消息就能立刻用它回复,看起来像是服务器主动推送。

下面是浏览器端长轮询的写法,/api/updates 是假设的接口,服务器在有新数据或超时时才返回:

JavaScript
async function longPoll(signal) {
  let cursor = '0';

  while (!signal.aborted) {
    try {
      const response = await fetch(`/api/updates?after=${cursor}`, { signal });
      if (response.status === 204) continue; // 服务器等到超时也没有新数据
      const data = await response.json();
      cursor = data.cursor;
      render(data.items);
    } catch (error) {
      if (signal.aborted) return;
      await new Promise((resolve) => setTimeout(resolve, 3000)); // 出错后等一会儿再连
    }
  }
}

const controller = new AbortController();
longPoll(controller.signal);

为什么要带 cursor?因为两次请求之间有空档,这段时间产生的消息不能丢。浏览器把上次收到的位置告诉服务器,服务器从那里往后给,就不会漏也不会重复。出错后等几秒再连,是为了避免服务器出问题时浏览器不停地猛发请求。

长轮询也有代价,RFC 6202 列了几条。每条消息都是一次完整的 HTTP 请求加响应,消息小的时候,头部可能占了传输量的大部分。服务器刚回完一条消息,要等浏览器发来下一个请求才能再推,所以最坏情况下延迟超过三次网络传输。而且每个客户端都要占着一个 TCP 连接和一个未完成的 HTTP 请求,一些网关、代理对同时挂着的请求数有限制。

SSE 和 WebSocket 解决了什么

SSE(Server-Sent Events)不再一次请求只回一条消息。服务器回一个 Content-Type: text/event-stream 的响应,但不结束它,有事件就往里写一段。浏览器用 EventSource 接收,断线后会自动重连,并通过 Last-Event-ID 告诉服务器最后收到哪条。它只能服务器往浏览器单向推 UTF-8 文本。

WebSocket 是另一种协议。浏览器先发一个 HTTP 请求要求升级,握手成功后,这条连接上双方可以随时发文本或二进制消息,不再有每条消息一套 HTTP 头的开销。可是重连、心跳和消息确认,浏览器都不管,要自己实现。

这两者的细节和对比见《SSE 与 WebSocket 有什么区别?》,WebSocket 的重连和补发见《WebSocket 适合什么场景,连接断开后怎样恢复?》。

放在一起怎么选

四种方式可以按下面的思路对比:

  • 短轮询:实现最简单,任何 HTTP 环境都能用;延迟取决于间隔,没有新数据时也在发请求。适合更新慢、对实时性要求不高的场景,比如几分钟刷新一次的看板。
  • 长轮询:有数据能立刻返回,也只用普通 HTTP;每条消息都带完整的头,服务器要挂着请求。适合消息不多、又没法用长连接的环境。
  • SSE:服务器单向推,自动重连,走普通 HTTP;只能传文本,EventSource 只能发 GET、不能自定义请求头。适合通知、进度、流式输出。
  • WebSocket:双向、可传二进制、每条消息开销小;要处理协议升级,重连和确认要自己写。适合聊天、协同编辑、多人游戏。

真正做选择时,先问“浏览器是不是也要频繁发消息”。如果不需要,SSE 往往比 WebSocket 简单。再看代理、网关和服务器能不能撑住大量长连接,撑不住时轮询反而更稳。

常见追问

长轮询和短轮询哪个对服务器压力更大?

不能一概而论。短轮询在没有数据时也不停地发请求,间隔越短浪费越多;长轮询请求数少,但每个客户端都长期挂着一个请求和一个连接。RFC 6202 提到,有些网关、代理和服务器对同时挂着的请求数有限制,所以规模大时长轮询要专门评估。

长轮询两次请求之间来的消息会丢吗?

如果只是“有数据就回”,会。所以要让浏览器每次带上最后收到的位置,比如一个游标或消息编号,服务器从这个位置之后开始返回,空档里产生的消息就能在下一次请求里补上。

为什么大模型的流式输出常用 SSE 而不是 WebSocket?

因为这类场景主要是服务器往浏览器推,浏览器只在开头发一次请求。SSE 走普通 HTTP,现有的鉴权、代理都能直接用。要发 POST 请求体或者自定义头时,通常用 fetch 读响应流,自己按 SSE 格式解析,代价是重连要自己写。