You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

连接WebSocket服务器出现Error: connect ECONNREFUSED错误的解决

问题

我搭建了一个OSC服务器,负责接收消息并转发给其他客户端,其中一个客户端是控制计时器的WebSocket服务器。以下是实现脚本:

const osc = require('osc');
const WebSocket = require('ws');

let ws = undefined;

// web socket, timer
function webSocket() {
    ws = new WebSocket('ws:192.168.1.118:4000');

    ws.on('open', function open() {
        console.log('WebSocket connected');
    });

    ws.on('close', function close() {
        console.log('WebSocket disconnected, attempting to reconnect...');
        setTimeout(webSocket, 1000);
    });

    ws.on('error', function error(err) {
        console.error('WebSocket encountered an error: ', err.message, 'Closing socket');
        ws.close();
    });
}

webSocket();

var udp = new osc.UDPPort({
    localAddress: "0.0.0.0",
    localPort: 8000, 
});

udp.on('ready', function () {
    console.log('OSC Server is listening');
    console.log(JSON.stringify(udp));
});

// Receive osc message
udp.on('message', function (msg, timeTage, info) {
    console.log('An OSC message received: ', msg);

    // Send exact message to movie player program
    if(info.address !== '10.4.0.95') {
        udp.send(msg, '10.4.0.95', 9004);
    }

    // Send message to tablet
    if (msg.address.startsWith('/mov/moviePlayhead') && info.address !== '10.4.0.94') {
    udp.send(msg, '10.4.0.94', 9003)
    }

    // Send message to web socket, timer
    if (msg.address === '/bank/1/trig/3') {
        if (ws.readyState === WebSocket.OPEN) {
            ws.send(JSON.stringify({event:'kaleidoscope', action: 'play', type: '3'}));
        }
    }
    else if (msg.address === '/bank/1/trig/2') {
        if (ws.readyState === WebSocket.OPEN) {
            ws.send(JSON.stringify({event:'kaleidoscope', action: 'play', type: '5'}));
        }
    }
    else if (msg.address === '/bank/1/trig/1') {
        if (ws.readyState === WebSocket.OPEN) {
            ws.send(JSON.stringify({event:'kaleidoscope', action: 'play', type: '7'}));
        }
    }

});

udp.open();

脚本多数时候运行正常,但偶尔会出现如下错误:

Error: connect ECONNREFUSED 192.168.1.118:4000 at TCPConnectWrap.afterConnect [as oncomplete] {
  errno: -111,
  code: 'ECONNREFUSED',
  syscall: 'connect',
  address: '192.168.1.118',
  port 4000
}

出现错误时,192.168.1.118:4000的WebSocket服务器是正常运行的,目前只能通过重启OSC服务器解决,但希望避免这种操作。我已经添加了重连逻辑,但并未生效,请问问题出在哪里?如何实现有效的重连?


问题分析与解决方案

问题根源

  1. 连接阶段错误未触发重连:当WebSocket在连接建立前出现ECONNREFUSED错误时,此时ws实例还未进入open状态,调用ws.close()不会触发close事件,导致重连逻辑无法执行。
  2. 无效的ws实例引用:重连时直接覆盖全局ws变量,但旧实例的事件监听可能残留,导致状态判断混乱。
  3. 重连策略单一:固定1秒重试间隔,未考虑网络波动的情况,可能导致短时间内大量重试,反而加剧问题。

修复方案

1. 调整错误事件处理逻辑

连接阶段的错误无需调用close(),直接触发重连;仅在连接建立后的错误才执行关闭并重连。

2. 增加连接超时机制

避免因网络延迟导致的长时间等待,超时后主动触发重连。

3. 优化重连策略

使用指数退避(每次重试间隔翻倍,直到最大间隔),减少无效重试频率。

4. 严谨判断ws状态

发送消息时,不仅要检查readyState,还要确保当前ws实例是最新的。

修改后的完整代码

const osc = require('osc');
const WebSocket = require('ws');

let ws = undefined;
let reconnectInterval = 1000; // 初始重连间隔
const maxReconnectInterval = 30000; // 最大重连间隔

function webSocket() {
    // 创建新实例前,先销毁旧实例的事件监听
    if (ws) {
        ws.removeAllListeners();
        ws = undefined;
    }

    ws = new WebSocket('ws://192.168.1.118:4000'); // 补全ws协议前缀

    // 连接超时处理:3秒未连接成功则触发重连
    const timeout = setTimeout(() => {
        console.log('WebSocket connection timed out');
        ws.close();
    }, 3000);

    ws.on('open', function open() {
        console.log('WebSocket connected');
        reconnectInterval = 1000; // 连接成功后重置重连间隔
        clearTimeout(timeout);
    });

    ws.on('close', function close(code, reason) {
        console.log(`WebSocket disconnected (code: ${code}, reason: ${reason}), attempting to reconnect...`);
        // 指数退避计算下一次重连间隔
        reconnectInterval = Math.min(reconnectInterval * 2, maxReconnectInterval);
        setTimeout(webSocket, reconnectInterval);
    });

    ws.on('error', function error(err) {
        console.error('WebSocket encountered an error: ', err.message);
        // 连接阶段的错误直接触发重连,无需调用close
        if (ws.readyState === WebSocket.CONNECTING) {
            clearTimeout(timeout);
            setTimeout(webSocket, reconnectInterval);
        } else {
            // 已连接后的错误,关闭后触发close事件重连
            ws.close();
        }
    });
}

webSocket();

const udp = new osc.UDPPort({
    localAddress: "0.0.0.0",
    localPort: 8000, 
});

udp.on('ready', function () {
    console.log('OSC Server is listening');
});

// Receive osc message
udp.on('message', function (msg, timeTage, info) {
    console.log('An OSC message received: ', msg);

    // Send exact message to movie player program
    if(info.address !== '10.4.0.95') {
        udp.send(msg, '10.4.0.95', 9004);
    }

    // Send message to tablet
    if (msg.address.startsWith('/mov/moviePlayhead') && info.address !== '10.4.0.94') {
        udp.send(msg, '10.4.0.94', 9003);
    }

    // Send message to web socket, timer
    const sendWsMsg = (type) => {
        // 确保ws实例存在且处于OPEN状态
        if (ws && ws.readyState === WebSocket.OPEN) {
            ws.send(JSON.stringify({event:'kaleidoscope', action: 'play', type}));
        } else {
            console.log('WebSocket not connected, message dropped:', type);
        }
    };

    switch(msg.address) {
        case '/bank/1/trig/3':
            sendWsMsg('3');
            break;
        case '/bank/1/trig/2':
            sendWsMsg('5');
            break;
        case '/bank/1/trig/1':
            sendWsMsg('7');
            break;
    }
});

udp.open();

额外说明

  • 补全了WebSocket URL的协议前缀ws://,避免部分环境下的解析问题。
  • 使用switch语句替代多个if-else,提升代码可读性。
  • 添加了消息发送失败的日志,便于排查问题。

内容的提问来源于stack exchange,提问作者ksatione

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.30 21:14:51