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

WebSocket close code 1006 为何没有服务端原因

来源:17golang原创

时间:2026-09-11 14:57:36 154浏览 收藏

浏览器里看到 WebSocket close code 1006,却发现 event.reason 是空字符串,服务端也没有“主动关闭”的记录,这并不表示服务端漏写了原因。1006 是 WebSocket 规范保留的异常关闭表示:连接结束时没有收到 Close 帧,浏览器只能告诉你“连接非正常结束”,不能凭这个数字还原真正的故障原因。

排查时要把客户端事件、反向代理连接日志和应用日志放在同一条时间线上。只有应用确实发出了 Close 帧,客户端才能拿到对应的状态码和 reason;如果是代理超时、网络切换、进程崩溃或 TCP/TLS 连接被直接切断,浏览器通常只会落到 1006。

要点速览
  • 1006 是异常关闭的本地表示,不能由服务端放进 Close 帧发送。
  • 先记录 codereasonwasClean 和时间,再去对齐三层日志。
  • 修复方向取决于断点:应用关闭、网关超时和传输中断的处理方式不同。
看到 1006 时,先把它理解为“没有完成 WebSocket 关闭握手”,而不是“服务端返回了一个叫 1006 的业务错误”。

WebSocket 1006 为什么没有服务端原因

RFC 6455 将 1006 定义为 Abnormal Closure,并明确它是保留值,不能作为 Close 控制帧里的状态码。浏览器的 CloseEvent.code 在没有收到 Close 帧时使用这个值,CloseEvent.reason 因而没有服务端提供的文本,wasClean 通常也会是 false

这解释了一个常见误会:服务端日志里写了“准备关闭”,不等于客户端收到了 Close 帧。进程可能在写帧前崩溃,代理可能在转发前超时,或者移动网络切换让底层连接先断掉。此时浏览器只能报告结果,不能替你判断断点。

WebSocket close code 1006 的静态结构图,展示浏览器 CloseEvent、Close 帧、代理链路和服务端之间的异常关闭边界
图1:1006 表示关闭握手没有完成,浏览器只能观察到异常断开,不能把它当作服务端发送的原因码。

先把 CloseEvent 的四个字段记完整

不要只打印 event.code。将四个字段和连接建立时间、最后一条消息时间一起记录,才能判断是刚握手就断,还是空闲一段时间后被切断。

socket.addEventListener('close', (event) => {
  // 记录客户端能观察到的全部关闭信息,方便和服务端日志按时间对齐。
  console.warn('WebSocket closed', {
    code: event.code,
    reason: event.reason || '(empty)',
    wasClean: event.wasClean,
    closedAt: new Date().toISOString(),
  });
});

判断可以先按这个表分层:

现象更可能说明什么先查哪里
1000wasClean=true完成了关闭握手业务关闭原因和用户操作
1006、reason 为空未收到 Close 帧代理超时、进程退出、网络链路
10081009对端发送了明确的协议/策略结果服务端策略和消息大小限制

按客户端、代理、服务端三层查断点

第一层看浏览器:确认连接 URL、握手是否成功、最后一次收发消息的时间,以及页面是否发生切换或网络类型变化。第二层看网关:检查 WebSocket Upgrade 是否被保持、空闲超时是否短于业务心跳间隔、是否有 upstream reset 或连接被回收。第三层看应用:确认是否进入了主动关闭分支、是否在写 Close 帧前异常退出、是否因部署或健康检查被终止。

最有价值的不是一句“连接断了”,而是同一个连接标识在三层日志中的最后状态。例如应用有“发送 1000”的记录而网关没有对应出站帧,优先查代理;应用完全没有关闭记录但进程刚重启,优先查进程生命周期;三层都没有错误而客户端恰好切换网络,则要保留传输中断的可能。

WebSocket 异常断开三层排查图,连接客户端、反向代理、应用服务与心跳和日志证据
图2:用客户端、代理、应用三层的静态关系对齐最后消息、心跳、Close 帧和连接终止记录。

修复后用重连和心跳验证结果

重连可以恢复体验,但不能掩盖原因。客户端应使用递增退避和上限,避免服务端或代理异常时瞬间建立大量新连接;重连前保存必要的订阅状态,重连成功后再补订阅。心跳间隔要小于代理的空闲超时,并在服务端确认收到心跳后更新连接活跃时间。

function reconnectWithBackoff(connect, attempt) {
  // 退避上限避免 1006 连续出现时形成重连风暴。
  const delay = Math.min(30000, 1000 * 2 ** Math.min(attempt, 5));
  return window.setTimeout(connect, delay);
}

如果用户主动退出页面,先调用 close(1000, 'user-left'),并在页面卸载后停止重连;如果是 1006,则把它当作需要重新建立连接的传输异常,同时保留一次可检索的客户端诊断记录。这样既能恢复连接,也不会把所有断开都伪装成正常关闭。

常见问题

1006 能不能由服务端直接发送?

不能。1006 是保留值,服务端不应把它写进 Close 帧;服务端应根据实际协议或业务情况发送允许的关闭码,异常断开则由客户端观察到 1006。

reason 为空是不是服务端没有写日志?

不一定。reason 只来自收到的 Close 帧,没有 Close 帧时为空很正常。服务端日志要和网关、客户端的时间及连接标识一起看。

遇到 1006 只要不断重连就行吗?

不行。先限制退避,再确认是否为代理空闲超时、应用重启、网络切换或协议错误;否则重连可能把故障放大成连接风暴。

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