你踩了吗?浏览器节能机制的坑🕳️🕳️🕳️

序言

近期,在使用WebSocket(WS)连接时遇到了频繁断连的问题,这种情况在单个用户上每天发生数百次。尽管利用了socket.io的自动重连机制能够在断连后迅速恢复连接,但这并不保证每一次重连都能成功接收WS消息。因此,我们进行了一些的排查和测试工作。

最终发现问题的根本原因:正是浏览器的节能机制,不经意间成为了这一问题的幕后黑手

浏览器节能机制简介

浏览器的节能机制逐渐成为前端开发者需要关注的问题。特别是这些节能机制可能会对定时器的精度产生影响,这直接关系到前端应用的用户体验,在某些场景下甚至影响到用户的使用。

为了减少电能消耗,提高电池续航能力,现代浏览器都引入了节能机制。这些机制包括但不限于降低空闲标签页的CPU使用率、减少后台JavaScript的执行频率、限制定时器的精确度等。虽然这些措施显著提高了设备的能效,但也给前端开发带来了一些挑战。

WS频繁断连原因分析

查阅socket.io官网服务端配置的pingTimeoutpingInterval两个参数发现WS心跳异常时会导致重连,具体说明:

WS连接中服务端和客户端两端必须一致保持心跳。如果有一端停止,则满足如下条件之一就会自动断连:

  • 服务器发送 ping,如果客户端在毫秒内 pingTimeout 没有用 pong 应答,则服务器认为连接已关闭。
  • 同样,如果客户端在毫秒内 pingInterval + pingTimeout 未收到来自服务器的 ping,则客户端也会认为连接已关闭。

看文档发现其实高版本的socket.io是由服务端定时发起ping。而在socket.io 2.X的版本中内置的心跳机制是由客服端定时发起。而浏览器在后台运行时,即使你设置了一个每秒触发的定时器,它也只能每分钟触发一次 ,超过了pingInterval + pingTimeout设置的时间,最后看到的日志是很有规律的每分钟重连一次。

WS频繁断连解决方法

升级socket.io到最新版本

上面的截图其实就是最近版本(4.x)的,升级后由服务器定时发起心跳。在服务端运行定时,避开了浏览器节能机制对定时的影响

自定义WS心跳

为了减小直接升级对已有业务的影响,目前使用的也是这种方案:在服务端自定义事件,定时发送心跳custom-ping

javascript 复制代码
// 客服的CODE
io.on('custom-ping', function () {
  io.emit('custom-pong', Date.now())
})

// 服务端CODE
io.on('connection', (socket) => {
  console.log('New client connected');

  // 发送自定义ping消息
  const pingInterval = setInterval(() => {
    socket.emit('custom-ping', Date.now());
  }, 10000); // 每10秒发送一次

  // 监听自定义pong消息
  socket.on('custom-pong', (data) => {
    console.log('Pong received:', data);
  });

  socket.on('disconnect', () => {
    clearInterval(pingInterval);
    console.log('Client disconnected');
  });
});

注意:断连时一定要销毁定时器

其实,socket.io是有内置心跳的(2.x版本客服端定时发起,4.x由服务端定时发起),自定义心跳的意义主要在于保持数据交换,在这个时间间隔内保持数据交换,socket就不会自动中断重连。

  1. 页面保活(在后台运行时也保持浏览器的活跃)

用得最多的方式是在页面隐藏一个循环播放的音频 或者 使用nosleep.js

javascript 复制代码
const noSleepInstance = new NoSleep();
document.addEventListener('click', function enableNoSleep() {
  document.removeEventListener('click', enableNoSleep, false);
  noSleepInstance.enable();
}, false);

实测,使用这种方式时,浏览器在后台运行仍然存在定时器精度降低的问题

  1. 使用setTimeout

这里要注意使用setTimeout的姿势,如果是直接这样使用、依然会有精度问题:

javascript 复制代码
let _cacheTs = Date.now()
const _setTimeoutFn = () => {
  console.log('setTimeout :>> ', Date.now() - _cacheTs);
  _cacheTs = Date.now()
  setTimeout(() => {
    _setTimeoutFn()
  }, 5000)
}
_setTimeoutFn()

在setTimeout里面去执行一个函数栈会被浏览器监控到,会认为和setInterval一样,其在后台运行时会降低其定时精度。但如果这样可以避开节能机制的限制:

javascript 复制代码
// 客服端CODE
// 监听服务端发送的custom-pong事件
socket.on('custom-pong', onHeart)

const onHeart = () => {
  if (pingTime.current) {
    clearTimeout(pingTime.current)
  }
  pingTime.current = window.setTimeout(() => {
    socket.emit('custom-ping', {
      pingTime: Date.now(),
      chatroom: lecture.chatroom,
    })
  }, 5000)
}

// 服务端CODE
socket.on('custom-ping', ()=>{
  socket.emit('custom-pong', Date.now())
})

总结

随着浏览器技术的发展,节能机制无疑会越来越完善,但与此同时也给前端开发带来了新的挑战。了解和适应这些变化,采用正确的策略来解决由此引起的问题,对于开发高质量的前端应用至关重要。通过上述方法,我们可以有效地缓解或解决浏览器节能机制对定时器精度降低带来的影响,从而提升用户体验。

相关推荐
王哈哈^_^1 小时前
【数据集】【YOLO】【目标检测】交通事故识别数据集 8939 张,YOLO道路事故目标检测实战训练教程!
前端·人工智能·深度学习·yolo·目标检测·计算机视觉·pyqt
cs_dn_Jie2 小时前
钉钉 H5 微应用 手机端调试
前端·javascript·vue.js·vue·钉钉
开心工作室_kaic2 小时前
ssm068海鲜自助餐厅系统+vue(论文+源码)_kaic
前端·javascript·vue.js
有梦想的刺儿3 小时前
webWorker基本用法
前端·javascript·vue.js
cy玩具3 小时前
点击评论详情,跳到评论页面,携带对象参数写法:
前端
清灵xmf3 小时前
TypeScript 类型进阶指南
javascript·typescript·泛型·t·infer
小白学大数据4 小时前
JavaScript重定向对网络爬虫的影响及处理
开发语言·javascript·数据库·爬虫
qq_390161774 小时前
防抖函数--应用场景及示例
前端·javascript
334554324 小时前
element动态表头合并表格
开发语言·javascript·ecmascript
John.liu_Test4 小时前
js下载excel示例demo
前端·javascript·excel