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

WebSocket 关闭码怎样区分正常断开和异常

来源:17golang原创

时间:2026-11-12 12:00:41 172浏览 收藏

页面里的 WebSocket 断开时,控制台往往只留下一句“closed”,真正有用的信息藏在 CloseEvent 里。判断正常还是异常,不能只看一个关闭码:先看 wasClean 表示关闭握手是否完成,再结合 code 的协议含义,最后把 reason 作为服务端或子协议提供的补充线索。

下面只解决一个问题:浏览器收到 close 事件后,怎样把正常离开、服务端主动结束、策略拒绝和连接中断分开处理。

实用判断顺序是:先记录 code、wasClean、reason 三个字段;1000 通常表示正常结束,1001 多数是端点离开,1006 表示没有完成正常关闭握手,其他业务相关码再按 IANA 与 RFC 6455 的含义分流。真正决定是否重连的,不是某个数字本身,而是关闭码和当前页面动作是否一致。

先看 close 事件携带的三个字段

浏览器的 close 事件会交给一个 CloseEvent。其中 code 是连接关闭码,reason 是对端提供的文字原因,wasClean 则表示关闭是否按正常握手完成。三个字段要一起记录,否则只看到“1006”时,很难知道是页面主动离开、服务器重启,还是中间网络突然断开。

WebSocket close 事件把 code、wasClean、reason 汇入应用日志的静态关系说明图
图1:CloseEvent 三个字段与应用日志的静态关系说明图,不是浏览器截图或运行证据。
function attachCloseLog(socket) {
  socket.addEventListener("close", (event) => {
    // 三个字段一起保存,避免只凭关闭码猜原因。
    const record = {
      code: event.code,
      wasClean: event.wasClean,
      reason: event.reason || "",
    };

    // reason 可能为空,日志系统不要把空原因当成新的异常。
    console.info("WebSocket closed", record);
  });
}

这里没有把 reason 当作机器可识别的状态。它是对端或子协议提供的人类可读说明,可能为空,也可能只在服务端主动发送关闭帧时有内容。代码判断应依赖 code 和 wasClean,日志里再保留 reason 方便排查。

常见关闭码分别说明什么

关闭码不是 HTTP 状态码,连接升级成功后要按 WebSocket 协议的关闭码理解。下面列出前端排查中最常遇到的一组,重点是它们对客户端动作的提示。

关闭码通常含义前端判断重点
1000Normal Closure,正常关闭通常不需要报警或重连,先确认是否由页面主动调用了 close()。
1001Going Away,端点离开可能是页面离开、浏览器环境结束或服务端准备下线,结合页面生命周期判断。
1006Abnormal Closure,异常关闭表示没有收到正常关闭状态,不能由应用主动发送;重点检查网络、代理、服务端进程和握手结果。
1008Policy Violation,策略违规通常需要看鉴权、权限、消息规则或服务端策略,不应盲目快速重连。
1009Message Too Big,消息过大缩小消息、拆分传输或调整协议边界,而不是把它当成临时网络抖动。
1011Internal Error,服务端内部错误保留 reason 和请求上下文,重连前先考虑服务端是否仍处于故障状态。
WebSocket 常见关闭码按可预期结束和协议运行异常分组的静态说明图
图2:常见 WebSocket 关闭码的判断边界说明图,不表示某次真实连接的执行流程。

其中最容易误解的是 1006。它是异常关闭的保留值,浏览器在没有拿到正常关闭状态时可以把它报告给脚本,但它不是一个应该由客户端调用 close(1006) 发送给服务端的业务码。遇到它,结论应是“关闭握手没有完整完成”,而不是直接断言“服务端一定宕机”。

用 wasClean 和 code 组合判断

如果只判断 event.code === 1000,会漏掉服务端用其他合法码完成关闭的情况;如果只判断 !event.wasClean,又会丢失策略拒绝、消息过大等处理方向。可以先把当前页面是否正在主动离开作为上下文,再做关闭码分级。

function classifyClose(event, pageLeaving) {
  // 页面离开时的 1001 不应和业务连接故障混在一起。
  if (pageLeaving && (event.code === 1000 || event.code === 1001)) {
    return "expected-page-leave";
  }

  // 1000 是正常关闭;wasClean 是对关闭握手是否完整的补充证据。
  if (event.code === 1000 && event.wasClean) {
    return "normal";
  }

  // 1006 不能直接发送,通常表示没有拿到正常关闭状态。
  if (event.code === 1006 || !event.wasClean) {
    return "transport-or-handshake-failure";
  }

  // 这些码已经给出较明确的处理方向,不要统一当成网络抖动。
  if (event.code === 1008) return "policy-rejected";
  if (event.code === 1009) return "message-too-large";
  if (event.code === 1011) return "server-internal-error";

  // 未覆盖的合法码先保留原值,交给业务日志继续分析。
  return "other-close-code";
}

这个函数的关键不是把所有场景都命名完,而是把“异常传输”和“有明确协议含义的拒绝”分开。对未知码保留原始数字,方便服务端和前端版本升级后继续对照协议登记表。

把分类结果接到 close 处理函数

有了分类结果之后,处理动作可以保持克制:正常结束只清理资源;页面离开不触发提示;传输类异常才进入受控重连;策略和消息边界错误则先记录并提示用户或开发者。

function handleSocketClose(event, state) {
  const kind = classifyClose(event, state.pageLeaving);

  // 无论原因是什么,先停止依赖当前连接的定时任务。
  clearInterval(state.heartbeatTimer);
  state.connected = false;

  switch (kind) {
    case "normal":
    case "expected-page-leave":
      // 正常离开不弹错误,也不创建新的连接。
      return { action: "stop", code: event.code };
    case "policy-rejected":
      // 鉴权或业务策略未通过时,重连通常不会改变结果。
      return { action: "show-policy-message", code: event.code };
    case "message-too-large":
      // 先修正消息大小,再决定是否恢复连接。
      return { action: "reduce-payload", code: event.code };
    case "server-internal-error":
    case "transport-or-handshake-failure":
      // 交给带退避和上限的重连器,避免连续打满服务端。
      return { action: "schedule-reconnect", code: event.code };
    default:
      // 未知关闭码只记录,不在这里做激进的自动修复。
      return { action: "record-and-review", code: event.code };
  }
}

示例中的 schedule-reconnect 只是动作名,不代表应该立即循环创建连接。实际项目要给重连设置退避、次数上限和页面可见性条件,并且在鉴权失败或消息过大时停止自动重试。

这几种现象怎样快速定位

遇到断开时,可以按下面顺序读现场信息,避免把不同原因混成“WebSocket 不稳定”。

  • 1000 且 wasClean 为 true:先查是谁主动调用了 close(),或服务端是否按正常流程结束连接。
  • 1001:对照页面的 pagehide、路由切换、浏览器关闭和服务端下线事件;它表达的是端点离开,不等同于代码报错。
  • 1006 且 wasClean 为 false:把连接建立时间、最后一条消息、网络切换、代理日志和服务端进程状态放在同一时间窗口里对比;不要只根据一个码下结论。
  • 1008、1009、1011:优先阅读 reason 和服务端日志,分别从策略、消息尺寸、服务端内部错误方向处理。

另外,error 事件通常只告诉前端发生了错误,具体关闭信息仍要在 close 事件里读取。两个监听器都保留,才能知道“先报错后关闭”是否和连接异常对应。

最后确认:什么才算正常断开

可以把判断浓缩成一句话:1000 + wasClean=true 是最典型的正常结束;1001 需要结合端点是否离开;1006 + wasClean=false 说明没有完成正常关闭握手;1008、1009、1011 则分别给出策略、消息尺寸和服务端错误方向。

写日志时至少保留 code、wasClean、reason、页面动作和连接持续时间。这样下一次出现异常断开时,前端可以先完成正确分级,再决定是否重连,而不是看到一个 1006 就反复刷新页面。

相关问题

  • WebSocket 的 1006 能不能在客户端主动发送?
  • 为什么 WebSocket 关闭时 reason 为空?
  • 1008 和 1011 都出现时应该先查哪里?
  • WebSocket 异常断开时怎样避免无限重连?
声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>