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

跨设备访问Socket.io聊天应用遇net::ERR_CONNECTION_REFUSED错误求助

Socket.io跨设备连接失败及CORS问题排查解决

问题概述

测试聊天应用在其他设备无法访问,连接时出现ERR_CONNECTION_REFUSED错误;尝试ngrok、localtunnel端口转发后问题依旧;同时存在strict-origin-when-cross-origin问题,即使已将CORS的origin设为*。使用Socket.io版本4.7.2。

错误信息

localhost:3000/socket.io/?EIO=4&transport=polling&t=Oi2Ko0C:1
Failed to load resource: net::ERR_CONNECTION_REFUSED
localhost:3000/socket.io/?EIO=4&transport=polling&t=Oi2KorJ:1

服务端代码

const express = require('express');
const http = require('http');
const socketIo = require('socket.io');
const mongoose = require('mongoose');
const session = require('express-session');
const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;
const bcrypt = require('bcryptjs');

const app = express();
const server = http.createServer(app);
const io = socketIo(server, {
    cors: {
        origin: "*",
        methods: ["GET", "POST"]
    },
    transports: ['websocket']
});

mongoose.connect('notgonnagiveyouthat', {
    useNewUrlParser: true,
    useUnifiedTopology: true
});

const PORT = 3000;

const userSchema = new mongoose.Schema({
    username: String,
    password: String
});

const User = mongoose.model('User', userSchema);

const messageSchema = new mongoose.Schema({
    username: String,
    content: String,
    timestamp: {
        type: Date,
        default: Date.now
    }
});

const Message = mongoose.model('Message', messageSchema);

passport.use(new LocalStrategy(async (username, password, done) => {
    try {
        const user = await User.findOne({ username: username });
        if (!user) {
            return done(null, false, { message: 'Username not registered' });
        }
        const isMatch = await bcrypt.compare(password, user.password);
        if (isMatch) {
            return done(null, user);
        } else {
            return done(null, false, { message: 'Incorrect password' });
        }
    } catch (err) {
        return done(err);
    }
}));

passport.serializeUser((user, done) => {
    done(null, user.id);
});

passport.deserializeUser(async (id, done) => {
    try {
        const user = await User.findById(id);
        done(null, user);
    } catch (err) {
        done(err);
    }
});

app.use(session({
    secret: 'secret',
    resave: true,
    saveUninitialized: true
}));

app.use(passport.initialize());
app.use(passport.session());
app.use(express.urlencoded({ extended: false }));

app.get('/login', (req, res) => {
    res.sendFile(__dirname + '/login.html');
});

app.post('/login', passport.authenticate('local', {
    successRedirect: '/',
    failureRedirect: '/login'
}));

app.get('/signup', (req, res) => {
    res.sendFile(__dirname + '/signup.html');
});

app.post('/signup', async (req, res) => {
    const { username, password } = req.body;
    const user = await User.findOne({ username });
    if (user) {
        return res.redirect('/signup');
    }
    const hashedPassword = await bcrypt.hash(password, 10);
    const newUser = new User({
        username,
        password: hashedPassword
    });
    await newUser.save();
    res.redirect('/login');
});

app.get('/', (req, res) => {
    if (!req.isAuthenticated()) {
        return res.redirect('/login');
    }
    res.sendFile(__dirname + '/index.html');
});

io.on('connection', async (socket) => {
    console.log('User connected');
    try {
        const messages = await Message.find().sort({ timestamp: -1 }).limit(10).exec();
        socket.emit('previousMessages', messages.reverse());
    } catch (err) {
        console.error("Error fetching messages:", err);
    }
    socket.on('sendMessage', async (data) => {
        const message = new Message({
            username: data.username,
            content: data.content
        });
        try {
            await message.save();
            io.emit('receiveMessage', message);
        } catch (err) {
            console.error("Error saving message:", err);
        }
    });
    socket.on('disconnect', () => {
        console.log('User disconnected');
    });
});

server.listen(PORT, () => {
    console.log(`Server is running on port ${PORT}`);
});

解决方案

1. 修正客户端连接地址

其他设备无法通过localhost:3000访问你的服务,必须替换为:

  • 局域网内:你的电脑局域网IP(如192.168.x.x:3000)
  • 公网环境:ngrok/localtunnel提供的转发地址(如https://abc123.ngrok.io)

确保客户端代码中Socket.io的连接地址使用上述正确地址,而非localhost。

2. 调整服务器监听地址

修改server.listen代码,让服务器监听所有网络接口,而非仅本地:

server.listen(PORT, '0.0.0.0', () => {
    console.log(`Server is running on port ${PORT}`);
});

3. 完善CORS配置

当前CORS配置缺少必要的头部和凭证支持,尤其是结合Passport/session使用时,需补充配置:

const io = socketIo(server, {
    cors: {
        origin: "*", // 若需带凭证,需改为具体域名/IP,如"https://abc123.ngrok.io"或"http://192.168.x.x:3000"
        methods: ["GET", "POST"],
        allowedHeaders: ["*"],
        credentials: true // 开启凭证支持,需配合前端withCredentials: true
    },
    // 建议移除transports强制限制,让Socket.io自动选择合适的传输方式
    // transports: ['websocket']
});

注意:当credentials: true时,origin不能设为*,需指定具体的允许来源。

4. 检查端口转发与防火墙

  • 确保ngrok/localtunnel正确映射端口:执行ngrok http 3000后,使用终端输出的Forwarding地址访问
  • 检查本地防火墙是否允许3000端口的入站连接,临时关闭防火墙测试是否解决问题

5. 前端Socket.io配置配合

若开启了credentials: true,前端连接时需添加对应配置:

const socket = io('你的服务地址', {
    withCredentials: true
});

内容的提问来源于stack exchange,提问作者ali-kingX

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.09 14:25:55