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

生产环境Socket.IO无法连接,本地正常的排查求助

MERN应用Socket.IO部署GCP VPS后WebSocket连接失败排查

问题概述

本地运行正常的MERN应用,通过Socket.IO实现WebSocket功能,部署到GCP虚拟机实例后,WebSocket连接完全无法建立。已确认GCP防火墙允许HTTP连接,VPS本地用TCP客户端能成功连接WebSocket服务,但通过网站无法建立连接。


服务器端核心代码 (index.js)

// Import required modules
const express = require("express");
const cors = require("cors");
const connectToMongo = require("./mongo/dbConnection");
require("dotenv").config();
const cookieParser = require("cookie-parser");
const socketIo = require("socket.io");
const http = require("http");

// Create Express app
const app = express();
connectToMongo();

// Middleware
app.use(express.json());
app.use(cors());
app.use(express.urlencoded({ extended: true }));
app.use(cookieParser());

// ------- ROUTES -------
app.use('/api/', require('path to file'))

// Serving WS and Express servers together
const server = http.createServer(app);
global.io = socketIo(server, {
  cors: {
    origin: process.env.FRONTEND_DOMAIN,
    methods: ["GET", "POST"],
  },
});

io.on("connection", (socket) => {
  console.log("Client connected:", socket.id);

  socket.on("disconnect", () => {
    console.log("Client disconnected:", socket.id);
  });
});

// Start the server
const PORT = process.env.PORT;

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

关联路由文件代码

const express = require("express");
const router = express.Router();
const User = require("../../mongo/schema/userSchema");
const Request = require("../../mongo/schema/requestSchema");
const fetchUser = require("../userAuth/fetchUserMiddleware");

router.post("/history", fetchUser, async (req, res) => {
  const filterCount = 10;
  const trialFilterCount = 3;
  try {
    const userId = req.user.id;
    const user = await User.findById(userId);

    const historyData = await Request.find({ userId: userId })
      .select(["-_id", "-__v", "-userId"])
      .sort({ timestamp: -1 });

    if (
      historyData.length >= trialFilterCount &&
      user.subscriptionType === "trial"
    ) {
      await user.updateOne({ subscriptionType: "notSubscribed" });

      global.io.emit("subscriptionStatusChange", { subStatus: false });
    }
    const userHistory = historyData.slice(0, filterCount);

    res.status(200).send({ success: true, userHistory });
  } catch (error) {
    console.error("Error in Filter History fetching" + error);
    res.status(500).send("Internal Server Error!");
  }
});

module.exports = router;

Nginx配置

server {
    server_name www.<app>.com;

    # Serve frontend files
    location / {
        root /var/www/<app>/;
        try_files $uri /index.html;
    }

    location /api/ {
        proxy_pass http://localhost:5000/api/;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_cache_bypass $http_upgrade;
    }

    location /socket/ {
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $host;

        proxy_pass http://localhost:5000;

        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }
}

前端Socket.IO客户端代码

import axios from "axios";
import React, { createContext, useEffect, useState } from "react";
import io from "socket.io-client";

const AuthContext = createContext();
const backendHost = import.meta.env.VITE_BACKEND_HOST;

const socket = io(`${import.meta.env.VITE_BACKEND_SOCKET_HOST}`);

socket.on('connect',()=>{
  console.log('Socket Connected')
})

export const AuthProvider = ({ children }) => {

  const [isSubscribed, setIsSubscribed] = useState(false);

  useEffect(() => {

    socket.on("subscriptionStatusChange", ({ subStatus }) => {
      setIsSubscribed(subStatus);
    });

    // Clean up
    // return () => {
    //   socket.off("subscriptionStatusChange");
    // };
  }, []);



  return (
    <AuthContext.Provider
      value={{
        isSubscribed,
        setIsSubscribed
      }}
    >
      {children}
    </AuthContext.Provider>
  );
};

export default AuthContext;

测试用TCP客户端代码

const net = require('net');

const HOST = '127.0.0.1'; // localhost
const PORT = 5000; // Port number to connect to

// Create a TCP socket client
const client = new net.Socket();

// Attempt to connect to the server
client.connect(PORT, HOST, () => {
    console.log(`Connected to ${HOST}:${PORT}`);

    // Once connected, send a message to the server
    client.write('Hello, server! I am a client.');
});

// Handle incoming data from the server
client.on('subscriptionStatusChange', (data) => {
    console.log(`Received data from server: ${data}`);

    // Close the connection after receiving data
    client.end();
});

// Handle connection closed by the server
client.on('close', () => {
    console.log('Connection closed by server');
});

// Handle connection errors
client.on('error', (err) => {
    console.error('Error:', err);
});

排查步骤建议

  1. 修正Nginx Socket路径匹配
    Socket.IO默认使用/socket.io/路径,当前Nginx配置的location /socket/不匹配,需修改为:

    location /socket.io/ {
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $host;
        proxy_pass http://localhost:5000/socket.io/;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }
    

    或在服务端Socket.IO初始化时指定路径:

    global.io = socketIo(server, {
      cors: {
        origin: process.env.FRONTEND_DOMAIN,
        methods: ["GET", "POST"],
      },
      path: "/socket/" // 匹配Nginx的location路径
    });
    
  2. 验证前端连接路径
    确认VITE_BACKEND_SOCKET_HOST的值为https://www.<app>.com/socket.io/(对应默认路径)或https://www.<app>.com/socket/(自定义路径),可在浏览器控制台直接测试:const socket = io('https://www.<app>.com/socket.io/'),查看是否触发connect事件。

  3. 检查GCP防火墙规则
    确认防火墙允许443端口(HTTPS)入站流量,WebSocket依赖HTTP/HTTPS升级,若前端用HTTPS,必须确保443端口开放。同时检查实例的网络标签是否关联正确的防火墙规则。

  4. 查看服务日志

    • 查看Node.js服务日志,确认是否有客户端连接请求到达;
    • 查看Nginx访问日志(/var/log/nginx/access.log)和错误日志(/var/log/nginx/error.log),排查请求是否被拦截或报错。
  5. 确认CORS配置正确性
    检查process.env.FRONTEND_DOMAIN是否为前端完整域名(如https://www.<app>.com),Socket.IO的CORS配置必须允许该域名的连接请求。

  6. 验证HTTPS配置
    若前端使用HTTPS,Socket连接必须使用wss://协议,确保Nginx配置了有效的SSL证书,避免浏览器阻止混合内容。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 12:04:58