WebSocket 连接失败

概述

本指南涵盖 OpenClaw 网关服务器中的 WebSocket 连接失败,基于 src/gateway/server/ws-connection.ts 中的连接处理逻辑。WebSocket 连接对于客户端和代理之间的实时通信至关重要。

症状

立即断开连接

  • 客户端连接后几秒内断开
  • 连接建立后无数据交换
  • 连接最初看似成功但很快失败

握手超时

  • 日志消息: handshake timeout conn=<connection-id>
  • 连接因特定超时原因关闭
  • 客户端从未完成身份验证/配对握手

过早关闭

  • 网关日志中出现警告: "closed before connect"
  • 握手阶段连接关闭
  • 握手完成前收到关闭事件

噪声辅助连接

  • Swift Package Manager 辅助连接立即关闭
  • 这些被单独过滤和记录(不是真正的错误)

根本原因

1. 握手超时(第 256-265 行)

网关强制执行握手超时以防止僵尸连接:

const timeout = setTimeout(() => {
  closeWithReason(
    conn,
    CloseCode.PolicyViolation,
    `handshake timeout`,
    /* broadcast= */ false
  );
}, handshakeTimeoutMs);

默认超时: 通过网关设置可配置 触发条件: 客户端未在超时窗口内完成握手 恢复: 连接关闭,客户端必须重新连接

2. Socket 连接错误(第 185-188 行)

连接建立期间的底层 socket 错误:

ws.on("error", (err) => {
  logger.warn("connection error", { conn: conn.id, err });
  closeWithReason(conn, CloseCode.InternalError, "connection error", false);
});

常见原因: 网络问题、代理问题、TLS 错误 恢复: 立即关闭,服务器端不重试

3. 握手前客户端关闭(第 215-223 行)

客户端在完成身份验证之前断开连接:

if (!conn.ready) {
  logger.warn("closed before connect", {
    conn: conn.id,
    code,
    reason,
    durationMs: Date.now() - conn.createdAt,
  });
  return;
}

检测: 关闭时 conn.ready 标志为 false 日志记录: 包含连接持续时间用于诊断 恢复: 干净断开,无需状态更新

4. Ed25519 配对失败

如果使用 Ed25519 身份验证,无效的签名或密钥会导致握手失败:

常见原因:

  • 公钥/私钥对不匹配
  • 签名验证失败
  • 凭据过期或被撤销

恢复机制

服务器端恢复

  1. 在线状态更新: 断开连接时,网关更新在线状态并向其余客户端广播快照
  2. 节点清理: 对于网关节点,连接从路由表中移除
  3. 无自动重连: 服务器不尝试重新连接;客户端负责重连

客户端恢复

  1. 检测断开: 监听 WebSocket close 事件
  2. 检查关闭码: 检查 event.codeevent.reason 以了解根本原因
  3. 指数退避: 使用递增延迟实现重连
  4. 握手优化: 确保连接后快速完成握手

诊断步骤

步骤 1: 检查网关日志

查找特定连接的日志条目:

# 搜索握手超时
grep "handshake timeout" gateway.log

# 查找连接错误
grep "connection error" gateway.log

# 检查过早关闭
grep "closed before connect" gateway.log

步骤 2: 分析关闭码

常见 WebSocket 关闭码:

  • 1000: 正常关闭(干净断开)
  • 1002: 协议错误(消息格式错误)
  • 1008: 策略违规(握手超时)
  • 1011: 内部错误(服务器端错误)

步骤 3: 测量连接持续时间

检查日志中的 durationMs 字段:

  • < 100ms: 可能是网络/防火墙问题
  • 100ms - timeout: 握手缓慢,可能需要优化
  • = timeout: 触发握手超时

步骤 4: 验证网络路径