连接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
相关产品推荐
相关产品推荐

