在浏览器端使用JavaScript建立实时通信时,WebSocket是最基础也最常用的方案。和传统的HTTP轮询不同,WebSocket在单次握手后保持全双工长连接,服务端可以随时向客户端推送数据。但很多初学者只写了简单的new WebSocket然后发消息,一旦网络波动导致连接关闭,页面就再也无法收到更新。要写出健壮的实时应用,必须理解连接状态机、错误恢复机制以及心跳保活策略。

一、WebSocket的基础连接与状态
JavaScript中的WebSocket对象提供了四种核心事件:onopen、onmessage、onclose和onerror。其中onopen代表握手成功,此时才能安全发送数据;onclose表示连接已断开,不管正常还是异常都会触发;onerror通常在连接建立前发生网络层错误时触发,随后一般紧跟着onclose。很多开发者误以为onerror之后连接还在,其实浏览器规范里错误后必然进入关闭流程。
WebSocket实例自身有一个readyState属性,值为0到3的数字,分别对应CONNECTING、OPEN、CLOSING、CLOSED。在调用send方法前必须判断readyState是否等于WebSocket.OPEN,否则会抛出InvalidStateError。下面是一段最基础的连接代码,展示了事件绑定方式:
const ws = new WebSocket('wss://ipipp.com/ws');
ws.onopen = function () {
console.log('连接已建立');
ws.send('hello server');
};
ws.onmessage = function (event) {
console.log('收到消息:', event.data);
};
ws.onclose = function (event) {
console.log('连接关闭, code:', event.code);
};
ws.onerror = function (err) {
console.log('发生错误');
};
上面的代码能跑通,但缺少重连逻辑。在生产环境中,手机切换网络、服务端重启、代理超时都会让连接进入CLOSED状态。如果不主动重连,用户看到的就是功能“假死”。因此我们需要在onclose里做文章,而不是仅打印日志。
二、断线重连与指数退避
最简单的重连是在onclose里用setTimeout重新执行一遍连接函数。但如果在服务端宕机时,所有客户端都以固定一秒间隔重连,恢复瞬间会形成惊群效应,把刚启动的服务压垮。更合理的做法是采用指数退避:每次重连失败后等待时间翻倍,并设一个上限,比如最大十秒。同时引入随机抖动,避免大量客户端同步重连。
下面封装了一个带重连的WebSocket管理类,核心逻辑是记录重连次数,计算延迟,并在关闭时调度。注意我们在onopen里把重连计数清零,因为只有成功连上才代表恢复:
class ReconnectingWebSocket {
constructor(url) {
this.url = url;
this.retryCount = 0;
this.maxDelay = 10000;
this.ws = null;
this.connect();
}
connect() {
this.ws = new WebSocket(this.url);
this.ws.onopen = () => {
this.retryCount = 0;
console.log('重连成功或首次连接');
};
this.ws.onclose = () => {
this.scheduleReconnect();
};
this.ws.onerror = () => {
this.ws.close();
};
}
scheduleReconnect() {
this.retryCount++;
const delay = Math.min(this.maxDelay, 1000 * Math.pow(2, this.retryCount));
const jitter = Math.random() * 1000;
console.log('将在', delay + jitter, '毫秒后重连');
setTimeout(() => this.connect(), delay + jitter);
}
}
const client = new ReconnectingWebSocket('wss://ipipp.com/ws');
这种封装比裸写WebSocket稳妥很多。不过仍有一个隐患:如果页面被切到后台,浏览器可能限制定时器精度,且用户并不在看页面,频繁重连是浪费。可以结合document.visibilityState,在页面隐藏时拉长重连间隔,或者暂停重连,等页面可见再恢复。
三、用心跳包维持长连接
有些网络环境(如运营商NAT、负载均衡)会在一段时间无流量后悄悄丢弃映射,导致TCP连接表面OPEN但实际已不通。此时onclose不会马上触发,send也不会报错,但消息就是发不过去。解决办法是客户端定时发轻量心跳,比如每三十秒发一个ping字符串,服务端回pong。若连续两次没收到回包,就主动close让重连逻辑接管。
心跳逻辑可以集成到前面的类里,用setInterval在onopen后启动,onclose前清除。下面的片段展示了如何加心跳:
class HeartbeatWebSocket extends ReconnectingWebSocket {
constructor(url) {
super(url);
this.heartbeatTimer = null;
this.missed = 0;
}
connect() {
super.connect();
this.ws.onopen = () => {
this.retryCount = 0;
this.missed = 0;
this.startHeartbeat();
};
this.ws.onmessage = (e) => {
if (e.data === 'pong') {
this.missed = 0;
}
};
this.ws.onclose = () => {
this.stopHeartbeat();
this.scheduleReconnect();
};
}
startHeartbeat() {
this.heartbeatTimer = setInterval(() => {
if (this.ws.readyState === WebSocket.OPEN) {
this.ws.send('ping');
this.missed++;
if (this.missed >= 2) {
this.ws.close();
}
}
}, 30000);
}
stopHeartbeat() {
clearInterval(this.heartbeatTimer);
}
}
通过心跳,我们能主动发现“假死”连接,而不是等浏览器最终报错。对于行情、协同编辑这类对实时性敏感的场景,心跳间隔可以缩短到十秒级别,但也要考虑移动端耗电,不能太过频繁。
四、总结与实践建议
处理WebSocket连接绝不是简单调一个构造函数。完整的客户端策略应包含:状态判断后再发消息、异常关闭后指数退避重连、页面不可见时降级重连频率、用心跳检测假死连接。将这些点组合进一个可复用的类,业务代码只需关心onmessage里的数据解析。
如果项目允许引入依赖,也可以看socket.io等库,它们内置了上述大部分逻辑。但理解原生JavaScript如何处理WebSocket连接,有助于在复杂网络下快速定位问题,也能在无法引库的环境(如小程序原生、内嵌Webview)中自己实现可靠通信层。
WebSocketJavaScript断线重连修改时间:2026-08-08 07:12:32