登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  文章 >  前端

WebSocket 怎样区分 close code 与网络断开

来源:17golang原创

时间:2026-10-24 23:17:13 182浏览 收藏

WebSocket 断开时,真正有用的不是只看“触发了 close 事件”,而是一起读取 event.code、event.wasClean 和 event.reason。完成关闭握手通常属于正常关闭;如果底层连接丢失、根本没有收到 Close 帧,浏览器会把它归为非正常关闭,常见表现是 code === 1006。

官方资料地址:https://www.rfc-editor.org/rfc/rfc6455#section-7.1.5

判断原则可以压缩成一句话:wasClean 先回答“关闭握手是否完成”,code 再回答“收到的关闭状态是什么”,reason 只是对端附带的可读说明;网络断开不能靠一个自定义 close code 伪装成服务端原因。
要点速览
  • 1006 是本地用于表示异常关闭的保留值,端点不能把它写进 Close 控制帧发送。
  • error 事件通常不给出可依赖的原因文本,最终要在 close 事件里记录分类线索。
  • 清理心跳和旧连接后,只对可恢复的非正常关闭安排退避重连;用户主动离开页面不应继续重连。

先分清 Close 帧与传输断开

WebSocket 的关闭握手由 Close 控制帧开始:一端发送 Close,另一端回应 Close,底层 TCP 连接随后关闭。只有关闭握手已经完成,规范才把这次关闭称为 clean。服务端主动发来 1000、客户端调用 close(1000),都属于“有协议关闭过程”的方向。

网络拔掉、代理超时、进程崩溃或连接在握手阶段失败时,浏览器可能没有收到任何 Close 帧。这时 1006 只是客户端对“异常关闭、没有收到关闭状态”的本地表示,不能据此断言具体是 Wi-Fi、代理还是服务端崩溃。RFC 6455 还明确规定,1005 和 1006 都不能作为端点实际发送的 Close 状态码。

WebSocket Close 帧、关闭握手、底层 TCP 连接与 1006 本地表示的静态关系说明图
图1:结构说明图,查看 Close 帧、关闭握手和传输断开的边界;这是原创静态说明图,不是浏览器截图或运行证据。

用 close 事件保留三个线索

浏览器会在客户端关闭、服务端关闭或发生错误后触发 close。因此不要把 error 处理器当成唯一的断线入口;它更适合记录“曾经发生异常”,真正统一清理和分类应放在 close 里。

let reconnectTimer = null;
let intentionalClose = false;

function attachSocket(socket) {
  // error 通常没有稳定的可读原因,只保留发生过异常这一线索。
  socket.addEventListener('error', () => {
    console.warn('WebSocket transport error');
  });

  socket.addEventListener('close', (event) => {
    // code 是收到的关闭状态;wasClean 判断关闭握手是否完成。
    const report = {
      code: event.code,
      wasClean: event.wasClean,
      reason: event.reason || '',
      abnormal: !event.wasClean || event.code === 1006,
    };
    console.info('WebSocket closed', report);

    // 先清理心跳和旧连接,再决定是否创建新连接,避免多个连接并存。
    stopHeartbeat();
    if (report.abnormal && !intentionalClose) {
      scheduleReconnect();
    }
  });
}

这里的 reason 只能当作对端提供的短文本,可能为空,也不等同于网络层错误描述。日志至少保留时间、连接地址的脱敏标识、code、wasClean、页面状态和是否由本地主动关闭,后续才能和服务端日志对照。

按 code 和 wasClean 分类,而不是只看 1006

分类时先看 wasClean,再按业务关心的代码做细分。1000 一般表示正常完成任务,1001 常用于离开页面或服务端下线,1008 可表示策略限制,1011 可表示服务端遇到未预期条件。它们表达的是关闭握手里携带的协议状态,不是 HTTP 状态码。

一个 clean 关闭也不等于“业务一定成功”:服务端可能在拒绝登录后用自定义的 4000–4999 应用代码完成握手。相反,wasClean === false 也不能直接证明服务端有故障,它只说明客户端没有完成可确认的正常关闭过程。

CloseEvent 的 code、wasClean、reason 与 error 事件分类及重连决策关系图
图2:结构说明图,查看 CloseEvent 三个字段与异常线索、清理和重连边界;这是原创静态说明图,不是运行结果。

把分类接到清理与退避重连

用户点击退出、页面进入 pagehide 或组件卸载时,应先设置本地意图,再调用 close(1000, 'client shutdown') 并停止重连。反之,网络异常导致的非正常关闭,可以使用单一重连定时器和指数退避,避免短时间内创建多个 WebSocket。

function closeByUser(socket) {
  // 先标记主动关闭,close 回调就不会误启动重连。
  intentionalClose = true;
  stopHeartbeat();
  socket.close(1000, 'client shutdown');
}

function scheduleReconnect() {
  // 已经有定时器时不重复排队,避免断线风暴。
  if (reconnectTimer !== null) return;
  reconnectTimer = window.setTimeout(() => {
    reconnectTimer = null;
    intentionalClose = false;
    connect();
  }, 1000);
}

window.addEventListener('pagehide', () => {
  // 页面离开是明确的生命周期信号,不应被当作网络故障重连。
  if (socket) closeByUser(socket);
}, { once: true });

重连策略只回答“是否再次建立连接”,不能替代根因诊断。若日志长期只有 1006,还要把客户端时间点与服务端连接、代理空闲超时、网络切换和页面生命周期记录放在一起比较;若看到稳定的 1008 或应用自定义代码,则先处理认证、权限或业务状态,不要无限重试。

常见问题

看到 1006 能确定是服务端挂了吗?

不能。它表示客户端没有收到 Close 帧或底层连接异常结束,服务端崩溃只是可能性之一,还可能来自代理、网络切换、浏览器生命周期或握手失败。

wasClean 为 true 就不用看 code 了吗?

不用。wasClean 只说明关闭过程是否完整,业务仍需要根据 1000、1008 或应用自定义代码决定提示、登出或重连行为。

为什么 error 里拿不到服务端原因?

WebSocket API 的 error 事件不是稳定的原因载体;应在 close 事件中读取 code、wasClean 和 reason,并结合服务端日志还原网络路径。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>