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
- Treat it as a disconnect: the server already breaks the loop; have the client detect the drop and reconnect with backoff.
- Check the logged tungstenite error: 'ConnectionReset'/'BrokenPipe' means abrupt client termination; 'Protocol(…)' means a malformed frame from the client.
- Add client-side keepalive/ping to detect dead connections before proxies or timeouts kill them.
- 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
- Implement exponential-backoff reconnect on the client.
- Send periodic pings so dead connections are detected quickly.
- Avoid idle timeouts by keeping traffic flowing or raising proxy/firewall timeouts.
- Only send a Close frame and await it during clean shutdowns to reduce reset noise.
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.