当前位置:首页 > 文章列表 > 文章 > 前端 > WebSocket 断线重连如何避免多个定时器叠加

WebSocket 断线重连如何避免多个定时器叠加

来源:17golang原创 2026-09-12 18:00:49 0浏览 收藏

WebSocket 断线重连要避免多个定时器叠加,关键不是把 setTimeout 的时间调大,而是让所有断线入口只调用一个调度函数,并在函数内部用 reconnectTimer 做幂等保护。实际实现中只由 close 事件安排下一次连接,error 负责记录原因;连接成功后清零重试次数,停止组件时同时清理计时器和旧连接。

要点速览
  • 一个控制器只保留一个待执行的重连计时器。
  • 等待时间按指数增长,并设置最大值和轻微随机抖动。
  • 旧 socket 的回调要被忽略,页面销毁时必须 clearTimeout。

先把 WebSocket 重连问题拆成三个状态

浏览器的 WebSocket 对象通过 readyState 表示连接状态,open 表示连接建立,close 表示连接已经关闭,error 表示发生了连接错误。最容易出错的写法是在 onerroronclose 中分别调用重连函数:一次网络故障可能同时经过两个回调,于是每次失败都会多出一条定时器链。

可以先明确三个状态:当前 socket、当前重试次数、当前待执行的计时器。它们由同一个控制器拥有,其他业务代码只调用 startstop,不直接创建定时器。

把重连责任集中在 close 事件

下面的控制器把“创建连接”和“安排重连”分开。error 不再触发第二次重试,真正的调度入口只有 closescheduleReconnect 开头的计时器判断,则让重复的关闭通知变成无操作。

class ReconnectingSocket {
  constructor(url) {
    this.url = url;
    this.socket = null;
    this.reconnectTimer = null;
    this.retryCount = 0;
    this.stopped = false;
  }

  start() {
    // 重新启动前清除停止标记;真正的连接只从这里创建。
    this.stopped = false;
    this.connect();
  }

  connect() {
    if (this.stopped) return;
    const current = new WebSocket(this.url);
    this.socket = current;

    current.addEventListener("open", () => {
      // 成功后恢复首轮等待,避免下一次断线直接使用长延迟。
      if (current !== this.socket) return;
      this.retryCount = 0;
      console.info("WebSocket 已连接");
    });

    current.addEventListener("error", () => {
      // error 只保留诊断职责;close 事件会负责唯一一次调度。
      console.warn("WebSocket 连接发生错误");
    });

    current.addEventListener("close", () => {
      // 忽略旧连接的回调,避免它改写新连接的状态。
      if (current !== this.socket || this.stopped) return;
      this.scheduleReconnect();
    });
  }

  scheduleReconnect() {
    // 已经有计时器时直接返回,保证全局只有一个待执行任务。
    if (this.reconnectTimer !== null || this.stopped) return;
    const base = Math.min(1000 * 2 ** this.retryCount, 30000);
    const jitter = Math.floor(Math.random() * 500);
    const delay = base + jitter;
    this.retryCount += 1;
    console.info(`第 ${this.retryCount} 次重连,等待 ${delay}ms`);
    this.reconnectTimer = window.setTimeout(() => {
      // 回调开始就归还计时器槽位,下一次失败才能重新预约。
      this.reconnectTimer = null;
      this.connect();
    }, delay);
  }

  stop() {
    // 页面销毁或用户退出时,计时器和连接都必须一起收口。
    this.stopped = true;
    if (this.reconnectTimer !== null) {
      window.clearTimeout(this.reconnectTimer);
      this.reconnectTimer = null;
    }
    if (this.socket) {
      this.socket.close(1000, "主动停止");
      this.socket = null;
    }
  }
}
WebSocket 连接、重连调度器和停止清理之间的静态关系示意图
图1:操作示意图,查看连接状态、唯一重连调度器与停止清理边界之间的关系。

指数退避要有上限,也要能停止

1000 * 2 ** retryCount 让连续失败的请求逐步拉开间隔,30000 是等待上限,随机的 jitter 可以避免多个客户端在同一时刻同时冲击服务端。上限不是越大越好:实时看板通常可以接受几十秒恢复,但应根据业务允许的离线时长调整。

场景推荐处理原因
首次断线短延迟重试快速恢复临时网络抖动
连续失败指数增长并封顶减少无效连接请求
连接成功retryCount 清零下一次故障从短延迟开始
页面离开clearTimeout 后 close不让后台回调重新建连

这里的计时器只是“未来某个时刻尝试一次”的预约,不代表连接已经恢复。只有 open 回调确认当前 socket 仍是控制器持有的对象,才可以把重试次数归零。

WebSocket 指数退避参数与连接生命周期的静态边界示意图
图2:结果示意图,展示退避参数、连接生命周期和停止入口如何共同约束重连调度。

检查旧回调、重复启动和页面销毁

如果旧 socket 已经关闭,而新 socket 正在连接,旧对象稍晚到达的 close 回调不能再次预约计时器,所以示例用 current !== this.socket 做对象身份判断。应用层也要避免重复调用 start;如果确实需要“重置连接”,先调用 stop,再启动新的控制器。

日志至少保留重试次数、等待毫秒数和当前 readyState。当同一时刻出现两条“第 N 次重连”日志,或一个控制器同时拥有两个非空计时器引用,就应回到事件绑定处排查是否还有隐藏的 setInterval、组件重复挂载或 error 回调重试。

常见问题

为什么不建议在 error 和 close 中都重连?

它们可能由同一次连接故障连续触发,两个入口会各自预约任务。让 error 只记录诊断,让 close 统一调度,职责更容易保持唯一。

重连定时器已经 clearTimeout,为什么还会重新连接?

通常是回调已经进入事件循环,或者旧 socket 的 close 回调没有做身份判断。清理计时器之外,还要设置 stopped,并忽略不再属于当前控制器的 socket 回调。

参考资料:MDN WebSocket API、WebSocket close 事件与 WHATWG WebSockets 规范。示意图为原创静态关系图,不代表本机真实运行截图。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
Go template 执行时报 function not defined 怎么修Go template 执行时报 function not defined 怎么修
上一篇
Go template 执行时报 function not defined 怎么修
Go time.Date 如何处理月末日期自动进位
下一篇
Go time.Date 如何处理月末日期自动进位
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    104次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    20次使用
  • OpenCompass大模型评测体系详解:功能、使用指南与应用场景
    OpenCompass
    OpenCompass是上海AI实验室推出的开源大模型评测平台,提供CompassKit、CompassHub和CompassRank三大核心组件,支持LLM及多模态模型的一站式标准化评估与排行榜查询。
    31次使用
  • AGI-Eval大模型评测平台:权威榜单、数据集与人机协同评测方案
    AGI-Eval
    AGI-Eval是由上海交大等高校联合发布的大模型评测社区,提供公正透明的LLM能力榜单、多领域评测集及Data Studio数据服务,助力AI模型性能评估与NLP科研开发。
    21次使用
  • SuperCLUE中文大模型评测基准:功能、能力维度与应用指南
    SuperCLUE
    SuperCLUE是权威的中文大语言模型综合评测基准,涵盖语言理解、知识应用、AI Agent智能体及安全性等12项核心能力。通过多轮对话与客观测试,定期发布榜单与技术报告,为模型研发、优化及行业选型提供科学依据。
    258次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议隐私政策
返回登录
  • 重置密码