首页/文章/浏览器

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

2024-05-27
22038 分钟
...

点击上方 前端Q,关注公众号

回复加群,加入前端Q技术交流群

作者:tager

https://juejin.cn/post/7362576319928008755

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

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

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

ws1.png

浏览器节能机制简介

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

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

WS频繁断连原因分析

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

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

  • 服务器发送 ping,如果客户端在毫秒内 pingTimeout 没有用 pong 应答,则服务器认为连接已关闭。

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

看文档发现其实高版本的socket.io是由服务端定时发起ping。而在socket.io 2.X的版本中内置的心跳机制是由客户端定时发起。而浏览器在后台运行时,即使你设置了一个每秒触发的定时器,它也只能每分钟触发一次,超过了pingInterval + pingTimeout设置的时间,最后看到的日志是很有规律的每分钟重连一次。在之前写的这篇文章中也有相关的介绍《掌握Web Workers:彻底解锁前端多线程编程的潜力》[2]

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

javascript 复制代码 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. 查看日志发现每分钟重连一次。
    在实际排查中,是从第七步倒退排查发现是浏览器节能机制所引起的问题。。。

总结

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

参考资料

[1]

https://socket.io/zh-CN/docs/v4/server-options/#pinginterval: https://link.juejin.cn?target=https%3A%2F%2Fsocket.io%2Fzh-CN%2Fdocs%2Fv4%2Fserver-options%2F%23pinginterval

[2]

https://juejin.cn/post/7360890308845404200: https://juejin.cn/post/7360890308845404200

往期推荐

实战分享:20分钟页面不操作,页面失效

Vue 存储插件的底层原理,你不知道的 localStorage API

苦等三年,React Compiler 终于能用了。使用体验:很爽,但仍有瑕疵

如果您觉得这篇文章有帮助,请点个赞吧~

分享文章

相关文章

更多文章 →
浏览器2025-07-30
浏览器内核到底有哪些?它们各自又有什么区别?
这问题其实很多刚入行的同学经常搞混,所以今天我来一次性讲明白—— 浏览器内核 到底有哪些?它们各自又有什么区别? 说白了,浏览器内核就是浏览器的大脑,分两部分: 渲染引擎(Rendering Engine) :负责网页的排版渲染,HTML、CSS统统交给它。 JS引擎(JavaScript Engine) :处理JavaScript脚本逻辑,比如你点按钮后弹出对话框这种行为。 1)常见浏览器内核盘点 下面我直接列几个主流的浏览器内核,顺...
学习面试
浏览器2025-03-07
浏览器缓存及优化手段
前言 当我们通过url首次访问某个网站的时候,我们如果使用浏览器的工具查看网络请求,会发现有很多http请求,但是当我们下一次再访问时,这些请求都不会发送而是展现在了页面上,而这就是浏览器缓存发挥的作用。 浏览器缓存 浏览器缓存是指将页面长时间不更新的资源缓存到浏览器上,下次访问页面时该部分长时间不更新的资源直接从缓存中获取,从而减少了网络请求次数,提高了页面的加载速度。浏览器的缓存方式主要分为两种: 强缓存 和 协商缓存 ,下面将为大...
学习面试
浏览器2024-12-16
chrome调试工具overrides调试代码
利用Chrome开发者工具断点调试功能调试修改代码,一刷新就什么都没有了,且只能调试JS代码,太尴尬了。 想刷新后,原来修改调试的的代码还在,且JSP、JS、CSS等能调试,那赶快试试下面这种方法吧。 第一步: 在浏览器开发者工具中打开下面的面板sources overrides! 第二步: 打开Select folder for overrides,选择要存放代码的文件夹 第三步: 点击允许访问文件夹,overrides生成刚才选择的...
学习
浏览器2024-09-13
震惊!🐿浏览器居然下毒
发生什么事了 某天,我正在愉快的摸鱼,然后我看到测试给我发了条消息,说我们这个系统在UC浏览器中有问题,没办法操作,点了经常没反应(测试用得iPhone14,是一个h5的项目)。我直接懵了,这不是都测了好久了吗,虽然不是在uc上测得,chrome、safari、自带浏览器等,都没这个问题,代码应该是没问题的,uc上为啥会没有反应呢?难道是有什么隐藏的bug,需要一定的操作顺序才能触发?我就去找了测试,让他重新操作一下,看看是啥样的没反应...
学习
浏览器2024-09-10
深入理解浏览器的事件循环机制
在现代Web开发中,JavaScript作为浏览器中最常用的编程语言,其执行机制深刻影响着Web应用的性能和用户体验。事件循环(Event Loop)作为JavaScript处理异步操作的核心机制,是理解JavaScript执行模型的关键。本篇文章从多个维度去分析事件循环的机制,希望可以帮大家更加深入的了解它。 在讨论事件循环之前,理解浏览器的执行环境是至关重要的。浏览器是一个多线程环境,包含以下主要线程: GUI 渲染线程(GUI R...
学习面试
浏览器2024-09-02
优化浏览器渲染主线程的 api 都有哪些?怎么用?
为了提高浏览器的性能和用户体验,现代浏览器采用了一些技术来优化主线程的执行。主要有: setTimeout 延迟加载 (使用不当可能适得其反) web worker 多线程 (不能操作dom,主要计算密集型的任务) requestIdleCallback 帧空闲时运行 (react18带🔥的,低优先级任务) requestAnimationFrame 每帧都会运行 (主要做动画效果调优) setTimeout 延迟加载非常常见,但是它...
学习面试

评论

请登录后发表评论

去登录
加载评论中...

目录