WebSocket心跳检测如何避免频繁断开?

IT人春凤 阅读 39

在开发实时聊天功能时,我给WebSocket加了心跳检测,但每隔10分钟还是会被断开。已经用setInterval()每30秒发送心跳,服务端超时设置是35秒,这是哪里出了问题?

代码是这样的:

let ws = new WebSocket('wss://api.example.com');
ws.addEventListener('open', () => {
  setInterval(() => {
    if (ws.readyState === 1) ws.send('heartbeat');
  }, 30000);
});

测试时发现,如果用户手机锁屏超过5分钟,重新点亮后就会触发连接断开。难道是移动端网络状态变化导致心跳包没发出?试过把间隔缩短到15秒但服务器说频次太高,这个怎么平衡啊?

我来解答 赞 7 收藏
二维码
手机扫码查看
2 条解答
爱学习的小秋
你这个心跳机制看着没问题,但移动端锁屏后浏览器会冻结页面定时器,setInterval根本不会执行,所以心跳发不出去,35秒超时断开是正常的。光缩短间隔没用,还容易被服务端限流。

得加个页面可见性监听,检测用户是否在前台。不在前台的时候别傻等定时器,等回来再重连。可以这样改:

let ws = new WebSocket('wss://api.example.com');
let heartbeatInterval;

function startHeartbeat() {
clearInterval(heartbeatInterval);
heartbeatInterval = setInterval(() => {
if (ws.readyState === WebSocket.OPEN) {
// 注意安全:别直接send字符串,用对象标记类型
ws.send(JSON.stringify({ type: 'heartbeat' }));
}
}, 30000);
}

// 监听页面可见性变化
document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'hidden') {
// 锁屏或切后台,清掉定时器省电
clearInterval(heartbeatInterval);
} else {
// 回到前台,重启心跳(也可以判断连接状态,断了就重连)
startHeartbeat();
}
});

ws.addEventListener('open', () => {
startHeartbeat();
});

// 断线重连逻辑也加上,比单纯依赖心跳更稳
ws.addEventListener('close', () => {
setTimeout(() => connect(), 5000); // 重连机制你自己封装下
});


另外跟后端确认下是不是代理层(比如Nginx)有60秒读超时限制,有时候不是你的锅。建议心跳包用 ping 帧(opcode=9)而不是文本消息,更标准也更轻量。如果服务端支持,优先用 WebSocket 内建的 ping/pong 机制。
点赞 1
2026-02-12 11:02
博主培聪
我之前也碰到过这问题,看着心跳机制好像没问题,但移动端锁屏后就是容易断。你这个30秒发一次、服务端35秒超时理论上是够的,但问题很可能出在浏览器或系统层面——手机锁屏后JS可能被节流甚至暂停,setInterval不保准了。

关键不是调得更频繁,而是要加两个东西:一个是心跳失败重试,另一个是网络状态监听。你可以改成这样:

let ws = new WebSocket('wss://api.example.com');
let heartBeatTimer = null;
let reConnectTimer = null;

function startHeartBeat() {
// 清掉旧定时器避免重复
if (heartBeatTimer) clearInterval(heartBeatTimer);

heartBeatTimer = setInterval(() => {
if (ws.readyState !== 1) {
console.log('连接异常,准备重连');
// 连接断了就清定时器
if (heartBeatTimer) clearInterval(heartBeatTimer);
reconnect();
return;
}
// 发心跳前最好用ping帧或者自定义消息
try {
ws.send('ping');
} catch (err) {
console.error('心跳发送失败', err);
clearInterval(heartBeatTimer);
reconnect();
}
}, 30000);
}

function reconnect() {
if (reConnectTimer) return; // 防止多次触发
reConnectTimer = setTimeout(() => {
console.log('尝试重新连接...');
ws = new WebSocket('wss://api.example.com');
ws.onopen = () => {
console.log('重连成功');
if (reConnectTimer) clearTimeout(reConnectTimer);
reConnectTimer = null;
startHeartBeat(); // 重新开启心跳
};
ws.onerror = () => {
console.log('重连失败,继续重试...');
};
}, 2000); // 每2秒试一次直到连上
}

// 监听网络变化,比如从弱网恢复
window.addEventListener('online', () => {
console.log('网络恢复,检查连接');
if (ws.readyState !== 1) reconnect();
});

// 打开时开始心跳
ws.addEventListener('open', () => {
startHeartBeat();
});

// 关闭和错误也要处理
ws.addEventListener('close', () => {
console.log('连接关闭,尝试重连');
reconnect();
});
ws.addEventListener('error', () => {
console.log('发生错误,准备重连');
reconnect();
});


另外提醒一点,有些服务器会限制连续心跳包频率,你可以把客户端设成30秒一次,只要保证“一个心跳周期内至少成功发出一次”就行。重点是加上重连机制和网络监听,别指望setInterval在后台一直跑。

我之前测试的时候发现iOS微信里锁屏几分钟,setInterval直接卡住不执行,等亮屏才一股脑补回来,这时候虽然时间看起来没超,但实际已经断了。所以必须靠readyState判断+自动重连兜底。
点赞 1
2026-02-11 03:01