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

连接WebSocket客户端遇ECONNREFUSED错误,MP4转MP3服务故障排查

问题分析与解决方案

核心问题:ECONNREFUSED 错误根源

你遇到的ECONNREFUSED错误本质是服务端尝试连接的端口并非客户端用于接收文件的TCP服务端口,而是Socket.io连接的临时端口——这个端口是客户端随机分配、仅用于和服务端建立Socket.io会话的临时端口,客户端并没有在该端口上启动TCP监听服务,因此连接必然被拒绝。

代码中的具体问题及修复方案

1. 错误获取客户端监听端口

当前代码中socket.request.connection._peername.port是客户端与服务端建立Socket.io连接时的临时端口,不是客户端专门用于接收文件的TCP服务端口。

修复方法:
客户端需要主动向服务端上报自己监听的TCP服务端口,服务端在Socket.io连接时接收并存储该端口:

  • 客户端补充代码示例:
    // 客户端启动TCP服务,监听自定义端口
    const net = require('net');
    const clientTcpPort = 3001; // 客户端自定义的文件接收端口
    const tcpServer = net.createServer((socket) => {
      socket.on('data', (fileBuffer) => {
        // 处理MP4转MP3逻辑
        const mp3Buffer = convertMp4ToMp3(fileBuffer);
        // 将转换结果返回给服务端
        socket.write(mp3Buffer);
      });
    });
    tcpServer.listen(clientTcpPort, () => {
      console.log(`客户端TCP服务监听端口 ${clientTcpPort}`);
    });
    
    // 连接服务端Socket.io时,主动上报监听端口
    const io = require('socket.io-client');
    const socket = io('http://localhost:80');
    socket.emit('register-client', { port: clientTcpPort });
    
  • 服务端修改Socket.io连接逻辑:
    io.on('connection', (socket) => {
        console.log(`Socket connected: ${socket.id}`);
        const address = socket.request.connection.remoteAddress;
        // 接收客户端上报的TCP服务端口,替换原临时端口逻辑
        socket.on('register-client', (clientInfo) => {
            const port = clientInfo.port;
            clients.push({ host: address, port, socketId: socket.id });
            console.log(`客户端注册:${address}:${port}`);
        });
    
        socket.on('disconnect', () => {
            console.log(`Socket disconnected: ${socket.id}`);
            // 通过socketId精准过滤,避免host/端口重复导致的删除错误
            clients = clients.filter((client) => client.socketId !== socket.id);
        });
    });
    

2. 客户端列表过滤逻辑错误

原代码用&&作为过滤条件,会导致只有当host和端口都不匹配时才保留客户端,正确逻辑应该是通过socketId精准匹配移除,否则无法正确清理断开连接的客户端。

3. 硬编码客户端Host为127.0.0.1

如果客户端不在本地,硬写127.0.0.1会导致无法连接,应使用客户端的实际IP(client.host);公网环境下还需确保客户端配置了端口映射。同时原超时时间60毫秒过短,建议改为60000毫秒(60秒):

const stream = net.createConnection({ 
    port: client.port, 
    host: client.host, // 使用客户端实际IP
    timeout: 60000 
}, () => {
    stream.write(file.buffer);
});

4. 过早关闭TCP连接

原代码发送数据后立即stream.end(),会导致大文件未完全发送,且无法接收客户端返回的转换结果,需等待客户端响应后再关闭连接:

stream.on('data', (mp3Data) => {
    // 接收转换后的MP3数据,返回给前端
    res.setHeader('Content-Type', 'audio/mpeg');
    res.send(mp3Data);
    stream.end();
});

修复后的服务端核心代码片段

const express = require('express');
const app = express();
const http = require('http').createServer(app);
const { Server } = require("socket.io");
const net = require('net');
const multer = require('multer');
const io = new Server(http);

const PORT = 80;

app.use(express.static('html'));
app.use(express.urlencoded({ extended: true }));

const storage = multer.memoryStorage();
const upload = multer({ storage: storage });

let clients = [];

app.post('/upload', upload.single('myFile'), (req, res) => {
    const file = req.file;
    if(!file){
        res.status(404).send('File not found.');
        return;
    }
    const client = getRandomClient();
    if(!client){
        res.send('No clients are currently connected.');
        return;
    }
    console.log(`连接客户端:${client.host}:${client.port}`);
    const stream = net.createConnection({ 
        port: client.port, 
        host: client.host, 
        timeout: 60000 
    }, () => {
        stream.write(file.buffer);
    });
    
    stream.on('data', (mp3Data) => {
        res.setHeader('Content-Type', 'audio/mpeg');
        res.send(mp3Data);
        stream.end();
    });

    stream.on('error', (err) => {
        console.error(`Connection error: ${err}`);
        res.status(500).send(`连接客户端失败:${err.message}`);
    });

    stream.on('timeout', () => {
        console.error('连接超时');
        res.status(504).send('连接客户端超时');
        stream.destroy();
    });
});

http.listen(PORT, () => {
    console.log(`Server listening on port ${PORT}`);
});

io.on('connection', (socket) => {
    console.log(`Socket connected: ${socket.id}`);
    const address = socket.request.connection.remoteAddress;

    socket.on('register-client', (clientInfo) => {
        const port = clientInfo.port;
        clients.push({ host: address, port, socketId: socket.id });
        console.log(`客户端注册成功:${address}:${port}`);
    });

    socket.on('disconnect', () => {
        console.log(`Socket disconnected: ${socket.id}`);
        clients = clients.filter((client) => client.socketId !== socket.id);
    });
});

function getRandomClient() {
    if (clients.length === 0) return null;
    const index = Math.floor(Math.random() * clients.length);
    return clients[index];
}

额外注意事项

  • 本地测试需关闭防火墙,公网环境需为客户端配置端口映射。
  • 大文件传输建议分块发送,避免内存溢出。
  • 可增加客户端心跳机制,实时检测在线状态,避免选择已离线的客户端。

内容的提问来源于stack exchange,提问作者Tuna On Air

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.27 19:43:16