今天想跟大家聊聊一个不咋被提起的话题:后端如何把数据"推"给前端?,作为前端我们要知道这些技术以便和后端联调,作为后端更是不用说,需要了解这些技术,从而选择适合的技术来实现需求。
咱们都知道,传统的 Web 模式是"请求-响应"模型,浏览器不问,服务器不说。这在展示静态页面或者偶尔查个数据的场景下没毛病。但现在是什么时代?实时聊天、在线协作、股价更新、比赛直播、系统监控...哪个等得起用户一遍遍刷新或者前端用 setTimeout
/ setInterval
去傻傻轮询?
短轮询(Polling)的缺点显而易见:
- 延迟高:取决于你轮询的间隔,实时性差。
- 浪费资源:大量请求可能只是空轮询,啥新数据也没有,白白消耗服务器和网络资源。
- 服务器压力:并发量高的时候,大量无效轮询请求能把服务器压垮。
所以,我们需要更优雅、更高效的方式------后端主动推送。今天,咱们就来深入扒一扒几种主流的实现技术。
1. WebSocket:全双工通信的瑞士军刀
说到推送,WebSocket 绝对是第一个跳进大多数人脑海的。这家伙是真·大佬级别的存在。

核心理论:
WebSocket 是 HTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的协议。啥叫全双工?就是客户端和服务器可以同时向对方发送数据,像打电话一样,而不是像对讲机那样一次只能一个人说。
它是怎么做到的?
- 握手阶段 :看着像 HTTP,其实是"借壳上市"。客户端发起一个特殊的 HTTP 请求(包含
Upgrade: websocket
,Connection: Upgrade
等头信息)。 - 协议升级:服务器如果同意,返回一个 101 Switching Protocols 状态码,之后这个 TCP 连接就从 HTTP 协议升级成了 WebSocket 协议。
- 持续通信:一旦升级成功,这个连接就保持打开状态,双方可以随时互相发送数据,数据格式可以是文本,也可以是二进制。连接关闭前,不需要再进行 HTTP 请求了。
技术特性:
- 真·实时:数据传输延迟极低,因为连接一直在线。
- 全双工:客户端、服务端都能主动发消息。
- 性能开销小:握手之后,数据帧的头部信息很小,比 HTTP 请求轻量得多。
- 事件驱动 :基于事件(
onopen
,onmessage
,onerror
,onclose
)处理,符合现代前端开发习惯。 - 支持二进制 :可以直接传输二进制数据(
ArrayBuffer
/Blob
),对音视频、游戏等场景友好。
不足与挑战:
- 协议兼容性 :虽然现在主流浏览器都支持,但一些老的网络设备、防火墙、代理服务器可能不认识 WebSocket 协议(或者
ws://
,wss://
),需要特殊配置或升级。 - 服务器状态维护:每个 WebSocket 连接都是一个状态,需要服务器维护。当连接数非常大时(比如百万级),对服务器的内存和管理能力是个考验(当然,现代框架和服务器在这方面优化很多了)。
- 实现相对复杂:相比于简单的 HTTP API,WebSocket 的生命周期管理、心跳保活、断线重连等机制需要开发者自己考虑得更周全(很多库封装了这些,但理解原理还是必要的)。
适用场景:
WebSocket 几乎是需要高实时性、频繁双向交互场景的首选:
- 在线聊天室/即时通讯:最经典的场景。
- 实时协作编辑:比如 Google Docs, Figma。
- 在线多人游戏:玩家状态、动作同步。
- 实时数据仪表盘/监控:股票行情、系统监控、实时地理位置跟踪。
- 需要服务端主动通知用户的场景:比如订单状态更新、消息提醒。
与 HTPP 的联系:
- HTTP 和 WebSocket 是两种不同的协议,分别适用于不同的场景。
- HTTP 基于请求-响应模型,适用于传统的 Web 通信。
- WebSocket 是双向通信协议,适用于实时通信和服务器推送。
- WebSocket 的建立依赖于 HTTP 协议,但一旦握手成功,通信将使用 WebSocket 协议。

代码示例 (Node.js + JS):
2. Server-Sent Events (SSE):轻量级单向推送
如果你的场景主要是服务器向客户端单向推送信息,而且不想搞那么复杂的 WebSocket,那么 SSE (Server-Sent Events) 可能是个更好的选择。

核心理论:
SSE 基于 HTTP 协议 ,本质上是一个长连接的 HTTP 响应 。客户端发起一个普通的 HTTP GET 请求,但服务器响应头里会包含 Content-Type: text/event-stream
,并且这个连接会一直保持打开状态。服务器随后可以随时通过这个连接,按照特定格式(下面会说)向客户端发送文本消息。
它就是 HTTP,所以天然能穿透防火墙和代理,兼容性好。
技术特性:
- 简单 :基于 HTTP,实现和理解都相对简单,前端有标准的
EventSource
API,后端也就是维护一个长连接响应流。 - 轻量:相比 WebSocket,协议开销更小(没有额外的握手和协议升级)。
- 单向:服务器 -> 客户端。客户端不能通过 SSE 连接向服务器发送数据(还得用普通的 HTTP 请求)。
- 自动重连 :
EventSource
API 标准就包含了断线自动重连机制,省心。 - 事件类型 :可以给不同的消息定义
event
类型,方便前端分类处理。 - 只支持 UTF-8 文本:不能直接传二进制数据。
不足与挑战:
- 单向通信:最大的限制,只能服务器推给客户端。需要客户端发数据?老老实实用 AJAX/fetch。
- 浏览器连接数限制:浏览器对同域名下的 HTTP 长连接(包括 SSE)数量有限制(通常是 6 个左右),开多个标签页或者有其他长连接请求时可能会达到上限。不过 HTTP/2 在一定程度上缓解了这个问题,因为多个请求可以复用一个 TCP 连接。
- 不支持二进制:传输图片、音视频等二进制数据比较麻烦(需要 base64 编码等)。
- IE/Edge 老版本不支持:虽然现代浏览器都支持了,但如果你需要兼容古董级的 IE,需要 Polyfill。
适用场景:
SSE 非常适合只需要从服务器接收实时更新的场景:
- 目前大火的 ai chat 响应数据的返回,分块返回
- 新闻 Feed / 股票行情 / 体育比分:服务器持续推送最新信息。
- 系统通知 / 站内信提醒:服务器有了新通知就推给前端。
- 日志流展示:实时显示服务器日志。
- 任务进度更新:比如文件上传、数据处理的进度条。
代码示例 (Node.js + JS):
后端 (Node.js - 原生 http
模块)
javascript
const express = require('express');
const app = express();
const port = 3000;
// 模拟 AI 逐块生成响应
function* generateResponse(prompt) {
const words = prompt.split(' ');
for (const word of words) {
yield word + ' '; // 每次生成一个单词
}
}
// SSE 路由
app.get('/chat', (req, res) => {
const prompt = req.query.prompt || 'Hello, this is a test';
// 设置 SSE 响应头
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');
// 逐块发送响应
const generator = generateResponse(prompt);
const interval = setInterval(() => {
const { value, done } = generator.next();
if (done) {
clearInterval(interval);
res.write('data: [DONE]\n\n'); // 发送结束标志
res.end();
} else {
res.write(`data: ${value}\n\n`); // 发送数据块
}
}, 500); // 每 500ms 发送一个单词
});
// 启动服务器
app.listen(port, () => {
console.log(`Server is running on http://localhost:${port}`);
});
前端 (HTML + JavaScript - 使用 EventSource
)
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AI Chat</title>
</head>
<body>
<h1>AI Chat</h1>
<div id="output">等待响应...</div>
<script>
// 建立 SSE 连接
const eventSource = new EventSource('/chat?prompt=你好,这是一个测试');
// 监听服务器发送的消息
eventSource.onmessage = (event) => {
const chunk = event.data;
const outputDiv = document.getElementById('output');
if (chunk === '[DONE]') {
outputDiv.innerHTML += '\n\n响应结束。';
eventSource.close(); // 关闭连接
} else {
outputDiv.innerHTML += chunk; // 追加内容
outputDiv.scrollTop = outputDiv.scrollHeight; // 自动滚动到底部
}
};
// 处理错误
eventSource.onerror = () => {
console.log('连接错误');
eventSource.close();
};
</script>
</body>
</html>
3. Long Polling(长轮询)
这玩意儿算是短轮询(Polling)的优化版,也是在 WebSocket 出现之前,模拟"服务器推"效果的一种常见手段。

核心理论:
- 客户端发起请求:和普通轮询一样,客户端向服务器发送一个 HTTP 请求,询问"有新数据吗?"
- 服务器hold住连接 :关键区别来了!如果服务器没有 新数据,它不会立即返回空响应,而是**挂起(hold)**这个连接,等待一段时间(比如 30 秒、1 分钟,或者直到有新数据为止)。
- 返回响应 :
- 如果在等待期间,有新数据到达了,服务器立即将数据作为响应发送给客户端,然后关闭这个连接。
- 如果在等待期间,一直没有新数据 ,直到超时(比如设定的 30 秒到了),服务器会返回一个空响应或特定状态码(表明没新数据),然后关闭连接。
- 客户端再请求 :客户端收到响应(无论是带数据的还是超时的)后,立即再次发起一个新的长轮询请求,重复步骤 1。
这样看起来就像是服务器在数据到达时"推送"了数据,因为它减少了大量无效的空轮询。
技术特性:
- 兼容性好:纯 HTTP,几乎所有浏览器、网络环境都支持。
- 实现相对简单:相比 WebSocket,后端逻辑主要是管理挂起连接和超时。
- 相比普通轮询,实时性提高,无效请求减少。
不足与挑战:
- 有延迟:数据从产生到被推送,还是存在延迟(服务器检查数据、网络传输)。虽然比短轮询好,但不如 WebSocket/SSE 实时。
- 服务器资源消耗:每个客户端连接都需要服务器挂起一个请求,虽然比 WebSocket 轻量(因为它不是持久连接),但在高并发下仍然消耗资源(主要是连接句柄、内存)。请求/响应的头部开销依然存在。
- 可能的消息丢失或重复:在网络不稳定的情况下,请求发出和响应返回之间可能出现问题,需要客户端和服务器做一些额外的逻辑来保证消息的可靠性(比如带上最后接收消息的 ID)。
- 超时处理:需要仔细设计超时时间,太短退化成普通轮询,太长则占用资源。
适用场景:
- 对实时性要求不是极致,但又不想频繁轮询的场景。
- 需要兼容非常古老的浏览器或网络环境。
- 作为 WebSocket/SSE 不可用时的降级方案。
- 一些消息队列的 Web 客户端可能采用这种方式。
与短轮询对比:
特性 | 轮询(Polling) | 长轮询(Long Polling) |
---|---|---|
请求频率 | 固定时间间隔发送请求 | 收到响应后立即发送下一个请求 |
服务器响应 | 立即响应,无论是否有新数据 | 保持连接打开,直到有新数据或超时 |
效率 | 低效,频繁发送请求 | 高效,减少不必要的请求 |
实时性 | 延迟较高 | 实时性较好 |
服务器资源占用 | 较低 | 较高 |
实现复杂度 | 简单 | 较复杂 |
适用场景 | 数据更新频率较低的场景 | 对实时性要求较高的场景 |
下图可以看出,短轮询存在大量无意义的请求,浪费资源。

代码示例:
后端(Node.js):
js
const http = require('http');
let messages = [
{ id: 1, message: "Hello, world!" },
{ id: 2, message: "This is a long polling example." },
{ id: 3, message: "Waiting for new messages..." }
];
let messageId = 3; // 初始消息 ID
const server = http.createServer((req, res) => {
if (req.url === '/getMessage') {
const lastMessageId = parseInt(req.headers['last-message-id']) || 0;
// 检查是否有新消息
const checkForNewMessage = () => {
const newMessages = messages.filter(msg => msg.id > lastMessageId);
if (newMessages.length > 0) {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify(newMessages));
} else {
// 如果没有新消息,等待一段时间再检查
setTimeout(checkForNewMessage, 1000);
}
};
checkForNewMessage();
} else {
res.writeHead(404, { 'Content-Type': 'text/plain' });
res.end('Not Found');
}
});
server.listen(3000, () => {
console.log('Server is listening on port 3000');
});
// 模拟新消息的生成
setInterval(() => {
messageId++;
messages.push({ id: messageId, message: `New message at ${new Date().toLocaleTimeString()}` });
}, 5000); // 每 5 秒生成一条新消息
前端:
js
let lastMessageId = 0;
function fetchMessages() {
fetch('/getMessage', {
headers: {
'Last-Message-Id': lastMessageId
}
})
.then(response => response.json())
.then(messages => {
if (messages.length > 0) {
messages.forEach(msg => {
document.getElementById('messages').innerHTML += `<p>${msg.message}</p>`;
lastMessageId = msg.id;
});
}
// 继续长轮询
fetchMessages();
})
.catch(error => {
console.error('Error fetching messages:', error);
// 继续长轮询
fetchMessages();
});
}
// 开始长轮询
fetchMessages();
4. HTTP Streaming
SSE 其实是 HTTP Streaming 的一种标准化应用 。但你也可以不用 text/event-stream
格式,直接用 HTTP Streaming 推送任意格式的数据流。
服务器在响应 HTTP 请求时,使用 Transfer-Encoding: chunked
。这意味着响应体不是一次性发送完成的,而是分成多个"块"(chunk)发送。服务器保持连接打开,并根据需要随时发送新的数据块给客户端。客户端则持续接收和处理这些数据块。
例如视频的下载播放:

特点:
- 分块传输
- 媒体文件被分割成多个小片段(chunks),每个片段可以独立传输和播放。
- 服务器将这些片段按顺序发送给客户端。
- 渐进式加载
- 客户端在接收到第一个片段后即可开始播放,同时继续下载后续片段。
- 这种方式避免了用户等待整个文件下载完成。
- 动态调整
- 根据网络带宽和设备性能,客户端可以动态选择不同质量的片段(如高清、标清等),以提供最佳播放体验。
与 SSE 的区别:
- 格式 :SSE 有固定的
event
,data
,id
,retry
格式,并以\n\n
分隔。通用 HTTP Streaming 没有规定格式,可以是 JSON、XML、纯文本,甚至二进制(需要小心处理),由应用自行约定。 - 客户端 API :SSE 有标准的
EventSource
API。通用 HTTP Streaming 需要用fetch
或XMLHttpRequest
,并处理response.body
(ReadableStream) 来逐步读取数据,相对更底层。 - 自动重连/事件类型 :
EventSource
自带这些功能。通用 Streaming 需要自己实现。
适用场景:
- 需要推送非文本或特定格式的数据流。
- 服务端已经有现成的流式接口,不想为了 SSE 改造。
- 下载大文件时,边下载边处理 (虽然这不算严格意义的"推送",但技术原理相似)。
基于 HTTP Streaming 实现的技术:
- HLS (HTTP Live Streaming) :
- 由 Apple 开发,广泛用于 iOS 和 macOS 设备。
- 将媒体内容分割成
.ts
文件(MPEG-2 Transport Stream),并通过.m3u8
播放列表文件进行索引。
- DASH (Dynamic Adaptive Streaming over HTTP) :
- 由 MPEG 标准化,支持多种设备和平台。
- 将媒体内容分割成
.mp4
或.webm
文件,并通过.mpd
文件(Media Presentation Description)进行索引。
- MSS (Microsoft Smooth Streaming) :
- 由 Microsoft 开发,主要用于 Windows 设备和 Silverlight 平台。
- 使用
.ism
和.ismv
文件格式进行传输。
5. HTTP/2 Server Push
HTTP/2 带来了一个叫 Server Push 的特性,听起来好像也是服务器推送?但此"推"非彼"推"。
核心理论:
HTTP/2 Server Push 允许服务器在客户端请求一个资源(比如 HTML)时,主动将它认为客户端接下来会需要的其他资源(比如 CSS、JS、图片)一并推送给客户端。目的是减少请求的 RTT (Round Trip Time),提前把资源准备好,加快页面加载速度。
注意关键词:资源 。它主要设计用来推送静态资源 ,而不是像 WebSocket 或 SSE 那样推送动态数据流。
推送过程大致是:
- 客户端请求
/index.html
。 - 服务器判断这个页面需要
/style.css
和/script.js
。 - 服务器在发送
/index.html
的响应的同时,主动发起两个 "PUSH_PROMISE" 帧,告诉客户端:"我马上要给你推/style.css
和/script.js
了,你先别自己去请求了。" - 随后服务器就把这两个文件的内容推给客户端缓存。
技术特性:
- 性能优化:减少了客户端发起请求的等待时间,理论上能提升首屏加载速度。
- 基于 HTTP/2:需要客户端和服务器都支持 HTTP/2。
- 与请求关联:推送是与客户端的某个初始请求相关联的。
不足与挑战:
- 缓存问题(致命伤) :服务器怎么知道客户端是否已经缓存了某个资源?如果推了客户端已有的资源,就浪费了带宽。这个问题很难完美解决(虽然有
Cache-Digest
等提案,但实现复杂,效果不一)。这是导致它实用性大打折扣的主要原因。 - 实现复杂:服务器需要有逻辑去判断哪些资源应该被推送,这可能需要复杂的配置或应用层逻辑。
- 浏览器支持和策略变化 :虽然浏览器实现了 H/2 Push,但由于缓存等问题带来的实际效益不确定,甚至有时会起反作用(推了不需要或已缓存的资源),Chrome 已经宣布计划移除对 HTTP/2 Server Push 的支持。这基本上给它的未来判了死缓。
- 不是用来推动态数据的:再次强调,它不适合用来做实时消息、通知等动态数据的推送。你想用它搞个聊天室?那真是找错人了。
适用场景:
理论上,适用于首次访问 网站时,将关键的 CSS、JS 和 Logo 等伴随 HTML 一起推送。但鉴于上述的挑战和浏览器支持的变化,目前已经不推荐在新项目中使用 HTTP/2 Server Push 了。
6. WebTransport (新一代实验性)
这是个更新的技术,基于 QUIC (HTTP/3 的基础),旨在提供比 WebSocket 更现代、更灵活的传输层。可以看作是 WebSocket + WebRTC Data Channels 的结合与进化。
核心理论:
WebTransport 利用 QUIC 协议的特性,提供:
- 多个流 (Streams):在一个连接上可以并发、独立地传输多个数据流,避免了 WebSocket 的队头阻塞问题(一个大消息或慢消息会阻塞后面所有消息)。
- 可靠和不可靠传输:可以选择像 TCP 一样可靠有序的流 (Datagrams),也可以选择像 UDP 一样尽力而为、可能乱序或丢失的不可靠数据报 (Datagrams),对实时游戏、音视频等场景更友好。
- 单向和双向流。
- 更快的连接建立:基于 QUIC 的 0-RTT 或 1-RTT 连接。
适用场景:
- 对延迟极其敏感的应用:在线游戏、云游戏流送、实时音视频传输。
- 需要在一个连接上高效传输多个独立数据流的场景。
- 希望利用不可靠传输优化性能的场景。
7. Web Push API (离线/后台通知)
这个严格来说,跟上面讨论的应用内实时数据推送目的不同,但它确实是"后端推送前端"的一种技术,所以提一下以示区别。

- 核心理论 :它允许网站(在获得用户授权后)通过浏览器或操作系统的推送服务 (如 Google 的 FCM, Apple 的 APNS)向用户发送通知 (Notifications) ,即使用户当前没有打开该网站的标签页,甚至浏览器没有运行(Service Worker 在后台接收)。
- 工作流程:后端需要调用 Push Service 的 API,Push Service 再把通知推给对应的设备/浏览器。前端需要 Service Worker 来接收和显示通知。
- 关键点:它不是用来在网页打开时实时更新页面内容的!它是用来发送操作系统级别的通知的。
- 适用场景:新闻更新提醒、社交媒体新消息通知、日历事件提醒、PWA 的离线消息推送等。
8. 第三方推送服务 (PaaS/SaaS)
别忘了还有"钞能力"和"站在巨人肩膀上"的选择。市面上有很多成熟的第三方实时消息推送服务。
- 例子:Pusher, Ably, PubNub, Google Firebase Realtime Database / Firestore (通过 SDK 实现实时同步), AWS AppSync (GraphQL Subscriptions, 通常底层是 WebSocket), Socket.IO (虽然是库,但其托管服务也可算此类)...
- 核心思想:你不用自己搭 WebSocket/SSE 服务器、处理连接管理、扩容、心跳、重连等麻烦事。直接用他们的 SDK 或者 API,把消息发给他们的平台,由他们负责把消息可靠地推送到你的前端客户端。
- 优点:开发速度快,省心省力,通常提供跨平台 SDK,功能丰富(如在线状态、消息历史、权限控制等),高可用和弹性伸缩有保障。
- 缺点:成本(通常按连接数、消息量等收费),有厂商锁定风险,数据隐私和合规性考量,可能不如自建方案灵活或性能极致可控。
- 适用场景:快速开发、中小项目、需要跨平台推送、不想自己维护复杂后端实时架构、预算允许。
技术选型思考:到底用哪个?
对于大部分需要实时数据更新和后端推送前端 的场景,WebSocket 和 SSE 是目前最成熟、最实用的选择。
- 如果只是简单的通知、信息流类(例如ai chat)推送,SSE 的简单性很有吸引力。
- 如果交互复杂,需要客户端也频繁发数据,WebSocket 更强大。
下图是短轮询、长轮询、SSE、WebSocket 对比图,可根据需要决定使用哪一种。

到底该怎么选?总结一下:
技术 | 适用场景 | 特点 |
---|---|---|
WebSocket | 需要实时、双向通信的场景(如聊天、游戏、协作) | 双向通信,低延迟,适合实时交互 |
SSE | 服务器向客户端单向推送消息,且对兼容性、简单性要求高(如新闻、通知、状态更新、AI Chat) | 单向通信,基于 HTTP,简单易用,兼容性好 |
HTTP Streaming | 更灵活的流式传输,适合自定义数据格式的场景,例如视频播放、直播、视频会议 | 更灵活,也更麻烦。 |
长轮询 (Long Polling) | 兼容性好,模拟推送,适合兼容或降级场景 | 古老但兼容性好,有延迟,资源消耗较高 |
第三方服务 | 快速开发,省心省力 | 有成本和锁定风险,适合快速实现功能 |
Web Push API | 发送系统级通知,非应用内数据流 | 用于推送系统通知,与实时数据流无关 |
WebTransport | 对延迟极其敏感,需要超低延迟和可靠传输(如云游戏、金融高频交易的 Web 端) | 基于 QUIC/HTTP/3,结合 WebSocket 的灵活性和 UDP 的低延迟,目前还在发展和标准化阶段,浏览器支持有限 |
HTTP/2 Push | 优化静态资源加载速度 | 曾经用于优化资源加载,但现在不推荐,建议使用预加载、预连接、CDN 和良好的缓存策略替代 |