打开一个股票行情页面,价格每隔几秒就自己跳动;在网页版聊天工具里发一条消息,对方几乎立刻收到。这些体验背后都离不开浏览器与服务端之间的实时通信机制。JavaScript作为跑在浏览器里的主角,承担了客户端通信的全部工作。这篇文章把主流的实时通信方案逐一拆解,配上可以直接运行的代码,帮你把理论落到实操层面。

一、为什么传统HTTP请求做不好实时通信
HTTP协议天生是请求-响应模型:客户端不发问,服务端就不会主动回答。如果你想让页面及时拿到服务端的最新数据,最朴素的办法是定时发请求去查,也就是短轮询。写法很简单,用setInterval每隔两秒请求一次接口即可。但短轮询的问题很明显:大部分请求都是无效的,服务器明明没有新数据,客户端还是反复来问,白白消耗带宽和连接资源。
于是有人提出了长轮询作为改良:客户端发起请求后,服务端先不响应,把请求挂起,直到有新数据或者超时才返回。客户端收到响应后立刻再次发起请求,循环往复。这种方式比短轮询聪明不少,但它本质上还是HTTP请求,每次往返都带着完整的HTTP头部,开销依然可观,而且服务端要维护大量挂起的请求,并发压力大。
// 短轮询的典型写法
setInterval(function () {
fetch('/api/messages')
.then(function (res) { return res.json(); })
.then(function (data) {
if (data.length > 0) {
renderMessages(data); // 渲染新消息
}
});
}, 2000);真正解决这个问题的,是后来出现的两个专门协议:WebSocket和SSE(Server-Sent Events)。它们跳出了请求-响应的框架,让服务端具备了主动推送数据的能力,这也是现代实时应用的技术基石。
二、WebSocket:全双工双向通信的首选方案
WebSocket是HTML5规范中定义的协议,它通过一次HTTP握手升级为独立的TCP连接,之后浏览器和服务端就可以在这个连接上互相发消息,没有HTTP头部的额外负担,延迟极低。在JavaScript中使用WebSocket非常简单,浏览器原生提供了WebSocket构造函数。
建立连接时传入服务端地址,注意协议要写成ws开头(加密连接用wss),而不是http。连接建立成功后会触发onopen事件,之后调用send方法就能把数据发给服务端,服务端推过来的消息会触发onmessage事件。
// 创建WebSocket连接
const ws = new WebSocket('wss://example.ipipp.com/chat');
// 连接打开
ws.onopen = function () {
console.log('连接已建立');
ws.send(JSON.stringify({ type: 'join', user: 'tom' }));
};
// 收到服务端消息
ws.onmessage = function (event) {
const msg = JSON.parse(event.data);
console.log('收到消息:', msg);
};
// 连接关闭
ws.onclose = function () {
console.log('连接已断开');
};
// 发生错误
ws.onerror = function (err) {
console.error('连接出错:', err);
};实际生产环境中,光有基本连接还不够。网络抖动、代理服务器超时都会导致连接悄悄断掉,所以心跳保活和断线重连是必须自己补上的两块拼图。心跳的思路是客户端定时发一个小消息,告诉服务端我还活着;重连的思路是监听onclose事件,用定时器延迟重试,并配合指数退避避免服务端被重连风暴打垮。
class ReconnectWebSocket {
constructor(url) {
this.url = url;
this.retryCount = 0;
this.connect();
}
connect() {
this.ws = new WebSocket(this.url);
this.ws.onopen = () => {
this.retryCount = 0;
// 每30秒发一次心跳
this.heartTimer = setInterval(() => {
this.ws.send(JSON.stringify({ type: 'ping' }));
}, 30000);
};
this.ws.onclose = () => {
clearInterval(this.heartTimer);
// 指数退避重连,最长间隔30秒
const delay = Math.min(1000 * Math.pow(2, this.retryCount), 30000);
this.retryCount++;
setTimeout(() => this.connect(), delay);
};
this.ws.onmessage = (event) => {
const msg = JSON.parse(event.data);
if (msg.type === 'pong') return; // 忽略心跳响应
this.onMessage && this.onMessage(msg);
};
}
send(data) {
this.ws.send(JSON.stringify(data));
}
}封装成类之后,业务代码只需要关心消息的收发,连接管理的细节全部被隐藏起来,这是比较推荐的做法。服务端方面,Node.js可以用ws库快速搭建WebSocket服务,Java有Netty和Spring的WebSocket模块,选择很丰富。
三、SSE:单向推送场景下的轻量选择
如果你的场景只是服务端往浏览器推数据,比如通知提醒、进度条更新、AI生成内容的逐字输出,那么用WebSocket就有点杀鸡用牛刀了。SSE(Server-Sent Events)专门为单向推送设计,浏览器端通过EventSource接口建立连接,之后服务端就能持续推送事件流,浏览器自动解析并触发回调。
SSE的优势在于它走的是普通HTTP协议,天然支持跨域配置、经过代理服务器时兼容性更好,而且浏览器断线后会自动重连,你不需要手写重连逻辑。缺点是只能单向传输,且在旧版IE上不受支持,不过现在主流浏览器都已经覆盖。
// 客户端:监听服务端推送
const source = new EventSource('/api/stream');
source.onmessage = function (event) {
const data = JSON.parse(event.data);
console.log('默认事件:', data);
};
// 监听自定义命名事件
source.addEventListener('progress', function (event) {
console.log('进度:', event.data + '%');
});
source.onerror = function () {
console.log('连接异常,浏览器会自动重连');
};// 服务端:Node.js实现SSE推送
const http = require('http');
http.createServer(function (req, res) {
if (req.url === '/api/stream') {
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive'
});
let percent = 0;
const timer = setInterval(function () {
percent += 10;
res.write('event: progress\n');
res.write('data: ' + percent + '\n\n');
if (percent >= 100) {
res.write('data: [DONE]\n\n');
clearInterval(timer);
res.end();
}
}, 500);
}
}).listen(3000);注意服务端响应头中的Content-Type必须是text/event-stream,每条消息以两个换行符结尾,格式约定错了浏览器会解析失败。如果是跨域访问,服务端还要加上Access-Control-Allow-Origin响应头。
四、方案选型:一张表理清思路
四种方案各有地盘,选型时抓住两个关键问题:数据流向是单向还是双向,以及对实时性的要求有多高。整理成表格方便对照:
| 方案 | 方向 | 实时性 | 实现复杂度 | 典型场景 |
|---|---|---|---|---|
| 短轮询 | 客户端拉取 | 低 | 极低 | 数据变化不频繁的页面 |
| 长轮询 | 客户端拉取 | 中 | 中 | 兼容老旧环境的消息通知 |
| SSE | 服务端单向推送 | 高 | 低 | 通知、进度、流式输出 |
| WebSocket | 全双工双向 | 极高 | 中高 | 聊天、协作、游戏、行情 |
举个具体例子:做一个在线客服系统,访客和客服互发消息,这是典型的双向通信,直接上WebSocket;做一个大文件上传的进度展示,数据只从服务端流向浏览器,SSE足矣;做一个后台管理系统的统计面板,数据五分钟才更新一次,老老实实用轮询反而是最稳妥省事的选择。
另外提醒一点,无论选哪种方案,消息体尽量用JSON序列化,字段设计上带上消息类型和时间戳,方便客户端做幂等处理和乱序容错。传输内容如果涉及敏感信息,务必使用wss和https加密通道,避免数据在中间环节被窃听。把这些细节处理好,你的实时通信应用才算真正达到了可上线的水平。
JavaScript实时通信WebSocketSSE修改时间:2026-09-11 05:14:32