LOGO OA教程 ERP教程 模切知识交流 PMS教程 CRM教程 开发文档 其他文档  
 
网站管理员

浏览器节能机制导致Websocket断连的坑

freeflydom
2024年4月29日 16:20 本文热度 1069

序言

你踩过吗?浏览器节能机制导致Websocket断连的坑~~~

近期,在使用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设置的时间,最后看到的日志是很有规律的每分钟重连一次。在之前写的这篇文章中也有相关的介绍《掌握Web Workers:彻底解锁前端多线程编程的潜力》

WS频繁断连解决方法

@升级socket.io到最新版本

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

@自定义WS心跳事件

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

// 客户端的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就不会自动中断重连。

@使用setTimeout

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

setTimeout丢失精度的情况:

// 以下setTimeout仍然会丢失精度

let _cacheTs = Date.now()

const _setTimeoutFn = () => {

  console.log('setTimeout :>> ', Date.now() - _cacheTs);

  _cacheTs = Date.now()

  setTimeout(() => {

    _setTimeoutFn()

  }, 5000)

}

_setTimeoutFn()

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

setTimeout不丢失精度的情况:

// 客户端CODE

// 监听服务端发送的custom-pong事件

socket.on('custom-pong', onHeart)


const onHeart = () => {

  if (timer) {

    clearTimeout(pingTime.current)

  }

  timer = window.setTimeout(() => {

    socket.emit('custom-ping', Date.now())

  }, 5000)

}


// 服务端CODE

socket.on('custom-ping', ()=>{

  socket.emit('custom-pong', Date.now())

})

@使用Web-Workers

在Web-Workers线程内发起定时不受浏览器节能机制的限制,相关示例在这篇文章里也有介绍《掌握Web Workers:彻底解锁前端多线程编程的潜力》

@页面保活(实测无效)

在后台运行时也保持浏览器的活跃,用得最多的方式是在页面隐藏一个循环播放的音频 或者 使用nosleep.js


const noSleepInstance = new NoSleep();

document.addEventListener('click', function enableNoSleep() {

  document.removeEventListener('click', enableNoSleep, false);

  noSleepInstance.enable();

}, false);


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

小结

WS频繁断连的原因:

  1. 使用了低版本(2.x)的socket.io

  2. 客户端每5秒定时发送 心跳

  3. 浏览器后台运行时触发节能机制限制了定时器的精度,由每5秒变成了实际的每分钟执行一次

  4. 每分钟执行一次远大于socket.io设置的pingTimeout时间

  5. WS断开连接

  6. socket.io内置的重连机制,立即重连成功

  7. 查看日志发现每分钟重连一次。
    在实际排查中,是从第七步倒退排查发现是浏览器节能机制所引起的问题。。。

总结


作者:tager
链接:https://juejin.cn/post/7362576319928008755
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。



该文章在 2024/4/29 16:20:37 编辑过
关键字查询
相关文章
正在查询...
点晴ERP是一款针对中小制造业的专业生产管理软件系统,系统成熟度和易用性得到了国内大量中小企业的青睐。
点晴PMS码头管理系统主要针对港口码头集装箱与散货日常运作、调度、堆场、车队、财务费用、相关报表等业务管理,结合码头的业务特点,围绕调度、堆场作业而开发的。集技术的先进性、管理的有效性于一体,是物流码头及其他港口类企业的高效ERP管理信息系统。
点晴WMS仓储管理系统提供了货物产品管理,销售管理,采购管理,仓储管理,仓库管理,保质期管理,货位管理,库位管理,生产管理,WMS管理系统,标签打印,条形码,二维码管理,批号管理软件。
点晴免费OA是一款软件和通用服务都免费,不限功能、不限时间、不限用户的免费OA协同办公管理系统。
Copyright 2010-2024 ClickSun All Rights Reserved