jlcodes99/cockpit-tools · warning

[WS] 接收错误 {}: {}

Error message

[WS] 接收错误 {}: {}

What it means

Logged in the receive branch of the per-connection select! loop when ws_receiver.next() yields Some(Err(e)), meaning the underlying tungstenite stream errored while reading. This is a transport/protocol-level failure, not an application error: the loop breaks and the connection is torn down. Typical causes are the peer dropping TCP abruptly, IO errors, or tungstenite protocol violations.

Source

Thrown at crates/cockpit-core/src/modules/websocket.rs:567

    // 订阅广播
    let mut broadcast_rx = server.tx.subscribe();

    loop {
        tokio::select! {
            // 接收客户端消息
            msg = ws_receiver.next() => {
                match msg {
                    Some(Ok(Message::Text(text))) => {
                        if let Err(e) = handle_client_message(&server, &mut ws_sender, &text).await {
                            crate::modules::logger::log_error(&format!("[WS] 处理消息失败: {}", e));
                        }
                    }
                    Some(Ok(Message::Close(_))) => {
                        crate::modules::logger::log_info(&format!("[WS] 客户端断开: {}", addr));
                        break;
                    }
                    Some(Err(e)) => {
                        crate::modules::logger::log_error(&format!("[WS] 接收错误 {}: {}", addr, e));
                        break;
                    }
                    None => break,
                    _ => {}
                }
            }
            // 发送广播消息
            msg = broadcast_rx.recv() => {
                if let Ok(json) = msg {
                    if ws_sender.send(Message::Text(json.into())).await.is_err() {
                        break;
                    }
                }
            }
        }
    }

    // 移除客户端

View on GitHub (pinned to 1ed8b77992)

Solutions

  1. Treat it as a disconnect: the server already breaks the loop; have the client detect the drop and reconnect with backoff.
  2. Check the logged tungstenite error: 'ConnectionReset'/'BrokenPipe' means abrupt client termination; 'Protocol(…)' means a malformed frame from the client.
  3. Add client-side keepalive/ping to detect dead connections before proxies or timeouts kill them.
  4. If a proxy/firewall idles out the socket, lower the ping interval or bypass the proxy for 127.0.0.1 traffic.

Example fix

// before: no keepalive, connection silently dies behind a proxy
const ws = new WebSocket("ws://127.0.0.1:9100");
// after: ping periodically and reconnect on close
setInterval(() => ws.readyState === 1 && ws.ping(), 15000);
ws.onclose = () => setTimeout(connect, 1000);
Defensive patterns

Strategy: retry

Validate before calling

// client side: check socket state before each use and reconnect on close
if (ws.readyState !== WebSocket.OPEN) {
  await reconnectWithBackoff(); // e.g. delay = min(30s, 500ms * 2**attempt)
}

Try / catch

match msg {
    Some(Err(e)) => {
        log_error(&format!("[WS] 接收错误 {}: {}", addr, e));
        break; // tear down; client reconnects with backoff
    }
    Some(Ok(_)) => { /* handle frame */ }
    None => break,
}

Prevention

When it happens

Trigger: The client process is killed or the socket is reset (Connection reset by peer) mid-session; the TCP connection times out; the peer sends frames violating the WebSocket protocol; the OS reports a broken pipe while reading.

Common situations: Laptops sleeping / network switches dropping loopback-unrelated links; client crash or force-quit without a Close frame; aggressive proxies or firewalls idling out the connection; mismatched WS protocol extensions (e.g. permessage-deflate negotiation issues).

Related errors


AI-assisted analysis of jlcodes99/cockpit-tools@1ed8b77992 (2026-09-05). Data as JSON: /api/errors/8561a93999faf1d6. Report an issue: GitHub.