WebSocket是唯一原生支持真正双向通信的浏览器方案,因其基于TCP长连接、握手后复用同一连接并免HTTP头开销;fetch和XMLHttpRequest本质为单向请求-响应模型,轮询会导致延迟高、开销大、连接频繁重建。
是浏览器中实现真正双向通信的唯一原生方案,不需要轮询、不依赖 HTTP 请求-响应模型,连接建立后客户端和服务器可随时互发消息。
为什么不能用
或
做双向通信
它们本质是单向请求工具:客户端发一次,服务端回一次,之后连接就断了。即使加
轮询,也会带来明显延迟、重复 header 开销、连接频繁重建等问题。而
握手成功后复用同一个 TCP 连接,后续所有数据帧都不带 HTTP 头,协议开销极小。
常见错误现象包括:
用户看到“新消息”总比实际晚 2–5 秒(轮询间隔导致)
服务端日志里每秒出现数百个
请求(其实是假实时)
移动端耗电快、流量消耗异常高(高频短连接)
连接必须用
吗
不是必须,但生产环境几乎必须。HTTP 页面加载时,浏览器会阻止非加密的
升级请求(Mixed Content 报错),控制台直接报
。
所以实际部署要注意:
前端页面走
→ 后端 WebSocket 地址必须用
本地开发用
时,
可以临时工作,但别依赖它
Nginx 反向代理
需显式配置
和
头,漏掉就会卡在
如何判断连接是否真正可用,而不是“假在线”
值为
(
)只代表握手完成,不代表网络通或服务端逻辑就绪。真实场景中常遇到:
WebSocket 8.18.2
WebSocket 8.18.2 是该协议规范的一个重要迭代版本,主要优化了连接稳定性与数据传输效率。它通过全双工通信机制,允许客户端与服务器在单一长连接上实时交换数据,大幅降低传统 HTTP 轮询的开销。该版本增强了心跳保活、自动重连及二进制帧传输能力,适用于即时通讯、在线游戏及金融行情推送等低延迟场景,为开发者提供更可靠的实时网络交互基础。
下载
连接已打开,但服务端没做鉴权,直接关闭(
触发,
是
类自定义码)
防火墙或代理中途静默断连,客户端仍显示
服务端进程崩溃,但 TCP 连接未及时 FIN,客户端无感知
因此必须加心跳:
服务端广播时为什么有些客户端收不到消息
最常见原因是没检查
。Node.js 的
库中,
对已断开但尚未触发
事件的连接会静默失败,不抛错也不重试。
正确写法必须逐个判断状态:
另外注意:
默认不支持跨域,若前端域名与服务端不一致,需手动设置
或启用
白名单,否则连接直接被拒绝,前端看不到任何错误,只有
被触发一次。
真实双向通信的关键不在“连上”,而在“持续可控地交换”。很多项目卡在连接能建、消息发不出、断连不重连、广播漏人这些细节上,而不是协议本身有多难。
WebSocketfetchXMLHttpRequestsetIntervalWebSocketGET /api/notifyWebSocketwss://ws://Failed to construct 'WebSocket': An insecure WebSocket connection may not be initiated from a page loaded over HTTPShttps://wss://http://localhostws://wss://UpgradeConnectionreadyState === 0readyState1OPENoncloseevent.code4001readyState === 1let heartbeatTimer;
socket.onopen = () => {
heartbeatTimer = setInterval(() => {
if (socket.readyState === WebSocket.OPEN) {
socket.send(JSON.stringify({ type: 'ping' }));
}
}, 30000);
};
socket.onmessage = (e) => {
const data = JSON.parse(e.data);
if (data.type === 'pong') return; // 忽略心跳响应
// 处理业务消息
};
socket.onclose = () => {
clearInterval(heartbeatTimer);
};client.readyState === WebSocket.OPENwsclient.send()closewss.clients.forEach(client => {
if (client.readyState === WebSocket.OPEN) {
client.send(payload);
} else {
console.warn('Dropped message: client not ready', client.readyState);
}
});wsverifyClientoptions.originonerror