Socket.IO连接被拒绝(ERR_CONNECTION_REFUSED)问题排查求助
问题背景
使用Socket.IO和React开发聊天应用时,持续遭遇ERR_CONNECTION_REFUSED错误,无法连接服务器。已尝试的操作包括:
- 在CORS ORIGIN中配置不同IP(含
['*']) - 前端尝试多种连接地址:
io('localhost:4444')、io('配置了反向代理到4444端口的域名') - 仅通过虚拟机的数值IP(
io('195.XXX.XX4:4444'))能成功连接
环境信息:前端部署在3000端口,Socket服务计划运行在4444端口。
相关代码片段
React前端代码
let newSocket; try { newSocket = io.connect('http://localhost:4444'); } catch (e) { console.log(e); window.location.reload(); } setSocket(newSocket); return () => { newSocket.disconnect(); };
Socket.IO服务端代码(注释为葡萄牙语)
const express = require('express'); const mongoose = require('mongoose'); const http = require('http'); const socketIo = require("socket.io"); const Chat = require('./models/Chat.js'); const dotenv = require("dotenv"); const cors = require('cors'); // Importe a biblioteca CORS const app = express(); app.use((req, res, next) => { res.setHeader("Content-Security-Policy", "default-src 'self'; script-src 'self' https://www.googletagmanager.com https://cdnjs.cloudflare.com 'unsafe-inline'; font-src 'self' https://fonts.gstatic.com;"); next(); }); dotenv.config(); const port = process.env.PORT || 3000; const server = http.createServer(app); const io = socketIo(server, { cors: { origin: ['http://localhost:3000'], methods: ['GET', 'POST'], allowedHeaders: ['Content-Type', 'Authorization'], credentials: true, }, debug: true, }); // Use a biblioteca CORS como middleware app.use(cors({ origin: ['http://localhost:3000'], methods: ['GET', 'POST', 'DELETE'], allowedHeaders: ['Content-Type', 'Authorization'], })); mongoose.connect(`mongodb+srv://${process.env.DB_USERNAME}:${process.env.DB_PASSWORD}@yc-cluster.bn5qm0z.mongodb.net/socketio`, { useNewUrlParser: true, useUnifiedTopology: true, }) .then(() => { console.log('Connected to MongoDB Atlas'); }) .catch((err) => { console.error('Error connecting to MongoDB Atlas:', err); }); const chatRoutes = require('./routes/chatRoutes'); app.use(express.json()); app.use('/api', chatRoutes); // SOCKET DE CONEXÃO io.on('connection', (socket) => { socket.on("connect_error", (err) => { console.log(`connect_error due to ${err.message}`); }); console.log('User connected in socket'); socket.on('join chat', (id_do_clube) => { const { id_club } = id_do_clube; const chatRoom = 'chat_' + id_club; socket.join(chatRoom); console.log('User connected in chat room: ' + 'chat_' + id_club); socket.on('disconnect', () => { console.log('User disconnected'); }); // SOCKET DE ENVIO DE MENSAGENS socket.on('chat message', async (message) => { // Salvar a mensagem no banco de dados const chat = new Chat({ sender: message.sender, message: message.message, id_club: message.id_club, id_user: message.id_user, sender: message.sender, sender_image: message.sender_image, announcement: message.announcement, type: message.type, role: message.role, }); try { const newChat = await chat.save(); socket.emit('chat message', newChat); io.to(chatRoom).emit('chat message', newChat); socket.emit('chat message response', "Mensagem enviada com sucesso"); } catch (err) { console.error('Error saving chat message:', err); } }); socket.on('get old messages', async (id_club) => { try { const oldChats = await Chat.find({ id_club }).sort({ timestamp: 'asc' }); socket.emit('old messages', oldChats); } catch (err) { console.error('Error retrieving old messages:', err); } }); // SOCKET DE EDIÇÃO DE MENSAGENS socket.on('edit message', async (editedMessage) => { try { const { messageId, newMessage, id_user } = editedMessage; const chat = await Chat.findById(messageId); if (!chat) { console.error('Mensagem não encontrada'); } if (parseInt(chat.id_user) !== parseInt(id_user)) { console.error('Usuário sem permissão de editar a mensagem'); } const updatedChat = await Chat.findByIdAndUpdate( messageId, { message: newMessage, edited: true }, { new: true } ); // Emitir evento para informar que deu certo para o cliente socket.emit('message edited response', "Mensagem editada com sucesso"); // Obter as mensagens atualizadas para o chat específico const id_club = updatedChat.id_club; const updatedMessages = await Chat.find({ id_club }).sort({ timestamp: 'asc' }); // Emitir evento para atualizar a lista de mensagens para o chat específico io.to(chatRoom).emit('updated message list', { id_club, messages: updatedMessages }); } catch (err) { console.error('Error editing message:', err); } }); // SOCKET DE DELEÇÃO DE MENSAGENS socket.on('delete message', async (infos) => { try { const { messageId, id_user } = infos; const chat = await Chat.findById(messageId); if (!chat) { console.error('Mensagem não encontrada'); return; } // Verificar se o usuário que está tentando deletar a mensagem é o mesmo que a enviou if (parseInt(chat.id_user) !== parseInt(id_user)) { console.error('Usuário sem permissão de deletar a mensagem'); return; } // Salvar a mensagem antiga antes de excluí-la const oldMessage = chat.message; // Atualizar a mensagem para "Esta mensagem foi excluída" e marcar como deletada chat.message = "Esta mensagem foi excluída"; chat.deleted = true; chat.oldMessage = oldMessage; // Salvar a mensagem antiga em um campo separado, se necessário // Salvar as alterações no documento await chat.save(); // Emitir evento para informar que deu certo a atualização da mensagem para o cliente socket.emit('message deleted response', "Mensagem excluída com sucesso"); // Obter as mensagens atualizadas para o chat específico const id_club = chat.id_club; const updatedMessages = await Chat.find({ id_club }).sort({ timestamp: 'asc' }); // Emitir evento para atualizar a lista de mensagens para o chat específico io.to(chatRoom).emit('updated message list', { id_club, messages: updatedMessages }); } catch (err) { console.error('Error deleting message:', err); } }); }); }); server.listen(port, () => { console.log(`Server is running on port ${port}`); });
后续排查/解决方法
修正服务端监听端口:代码中
const port = process.env.PORT || 3000;与你所说的4444端口不符,若未配置PORT环境变量,服务实际运行在3000端口。要么在.env文件中设置PORT=4444,要么直接将代码改为const port = 4444;,确保前端连接端口与服务端监听端口一致。统一CORS配置:Socket.IO的cors配置与express的cors中间件需保持一致,若前端通过域名访问,需将该域名添加到
origin列表,而非仅保留localhost:3000。同时,Socket.IO的cors配置中建议添加OPTIONS方法,以支持跨域预检请求。检查反向代理配置:若使用反向代理(如Nginx),需确保配置支持WebSocket协议。示例Nginx配置:
location /socket.io { proxy_pass http://localhost:4444; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header Host $host; }前端连接时直接使用域名(如
io('https://你的域名')),无需添加端口,由反向代理处理转发。开放服务器端口权限:检查虚拟机防火墙、云服务商安全组,确保服务监听的端口(4444或实际端口)允许入站连接。仅数值IP能连接说明IP端口已开放,但域名解析后的端口可能未配置正确。
强制前端使用WebSocket传输:前端连接时明确指定传输方式,避免轮询可能引发的问题:
newSocket = io.connect('http://localhost:4444', { transports: ['websocket'] });同时确保协议(http/https)完整,不要省略。
验证域名解析与本地hosts:若使用域名连接,检查域名DNS解析是否正确指向虚拟机IP,本地hosts文件是否存在错误的域名映射。
让服务监听所有网络接口:修改服务启动代码,让服务监听
0.0.0.0而非默认的localhost,确保外部请求能访问:server.listen(port, '0.0.0.0', () => { console.log(`Server is running on port ${port}`); });
内容的提问来源于stack exchange,提问作者Guidetti

