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

NodeJS与ReactJS部署后WebSocket反复连接断开问题排查

WebSocket部署后反复连接断开问题排查与解决

问题场景

已在服务器部署React.js PWA前端和NodeJS后端,通过Nginx和PM2运行正常。后端创建的WebSocket在本地环境运行正常,但部署到服务器后,连接反复建立又断开,控制台仅打印“WebSocket connected”和“WebSocket disconnected”,无错误信息。

现有配置信息

NodeJS WebSocket配置

const clients = new Map();

exports.setupWebSocket = (server) => {
  const wss = new WebSocket.Server({ server });

  wss.on("connection", (ws, req) => {
    const cookie = decodeURIComponent(req.headers.cookie);

    const startTokenIndex = cookie.indexOf("token=") + "token=".length;
    const endTokenIndex = cookie.indexOf(";");
    const tokenData = cookie.substring(startTokenIndex, endTokenIndex);

    // Extract the token information
    const decodedToken = decodeURIComponent(tokenData);
    const parsedToken = JSON.parse(decodedToken);

    // Use the user ID
    const userId = parsedToken._id;

    if (!clients.has(userId)) {
      clients.set(userId, new Set());
    }

    clients.get(userId).add(ws);

    ws.on("close", () => {
      const userSet = clients.get(userId);
      if (userSet) {
        userSet.delete(ws); // Remove specific WebSocket connection
        if (userSet.size === 0) {
          clients.delete(userId); // Remove the user if no connections left
        }
      }
    });
  });
};

app.js配置

const express = require("express");
const cors = require("cors");
const config = require("./config/server");
const swaggerUi = require("swagger-ui-express");
const swaggerOutput = require("./doc/swagger.json");
const { setupWebSocket } = require("./controllers/log");
const app = express();

app.use(cors(config.cors.corsOption));

app.use((req, res, next) => {
  res.header(
    "Access-Control-Allow-Headers",
    "x-access-token, Origin, Content-Type, Accept"
  );
  next();
});

app.use(express.json({ limit: "50mb" }));
app.use(express.urlencoded({ limit: "50mb", extended: false }));

app.use("/doc", swaggerUi.serve, swaggerUi.setup(swaggerOutput));

app.get("/", (req, res) => {
  res.send("Welcome");
});

module.exports = {
  app,
  setupWebSocket,
};

server.js配置

const { app, setupWebSocket } = require("../app");
const config = require("../config/server");
const http = require("http");

const server = http.createServer(app);

// Configure the websocket
setupWebSocket(server);

module.exports = (err) => {
  if (err) {
    console.log("Error connecting to the DB");
    console.log(err);
  }

  server.listen(process.env.PORT || config.app.port, (err) => {
    if (err) {
      console.log(err);
    }

    console.log(`Starts in ${config.app.url}:${process.env.PORT || config.app.port}`);
  });
};

Nginx配置

server {
    server_name subdomain.domain.com;
    location / {
      proxy_pass http://MYIP:MYPORT;

      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;
    }
    # SSL CONFIG
    listen 443 ssl http2;
    ssl_certificate /etc/ssl/cert.crt;  
    ssl_certificate_key /etc/ssl/cert.key;  

    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_prefer_server_ciphers off;
 }

React前端连接代码

const ws = new ReconnectingWebSocket(
  `wss://${process.env.REACT_APP_DOMAIN}`
);

ws.onopen = () => {
  console.log("WebSocket connected");
};

ws.onmessage = (event) => {
  const data = JSON.parse(event.data);
};

ws.onclose = () => {
  console.log("WebSocket disconnected");
};

ws.onerror = (error) => {
  console.error("Error:", error);
};

注:process.env.REACT_APP_DOMAIN的值为http://MYIP:MYPORT

问题原因与解决方案

1. 前端WebSocket连接地址不匹配

当前前端使用wss://前缀拼接http://MYIP:MYPORT,这会导致协议冲突:wss对应HTTPS的WebSocket协议,需要通过配置了SSL的域名(即subdomain.domain.com)访问,而非直接使用IP+端口的HTTP地址。

修复方案:
修改前端连接地址为域名的WSS地址,同时更新REACT_APP_DOMAIN环境变量为subdomain.domain.com:

const ws = new ReconnectingWebSocket(
  `wss://${process.env.REACT_APP_DOMAIN}`
);

2. 后端Cookie解析逻辑存在健壮性问题

原代码中如果Cookie里没有分号,cookie.indexOf(";")会返回-1,导致substring(startTokenIndex, endTokenIndex)截取到无效字符串,后续JSON.parse会抛出未捕获异常,直接导致WebSocket连接被断开。

修复方案:
修改WebSocket连接的Cookie解析逻辑,添加错误捕获和正则匹配,避免解析失败:

wss.on("connection", (ws, req) => {
  try {
    const cookie = req.headers.cookie;
    if (!cookie) {
      ws.close(401, "No cookie provided");
      return;
    }
    const decodedCookie = decodeURIComponent(cookie);
    // 用正则匹配token值,兼容有无分号的情况
    const tokenMatch = decodedCookie.match(/token=([^;]+)/);
    if (!tokenMatch) {
      ws.close(401, "Token not found in cookie");
      return;
    }
    const tokenData = tokenMatch[1];
    const decodedToken = decodeURIComponent(tokenData);
    const parsedToken = JSON.parse(decodedToken);
    const userId = parsedToken._id;

    if (!clients.has(userId)) {
      clients.set(userId, new Set());
    }
    clients.get(userId).add(ws);

    ws.on("close", () => {
      const userSet = clients.get(userId);
      if (userSet) {
        userSet.delete(ws);
        if (userSet.size === 0) {
          clients.delete(userId);
        }
      }
    });
  } catch (error) {
    console.error("WebSocket connection error:", error);
    ws.close(400, "Invalid token or cookie");
  }
});

3. Nginx代理超时时间过短

默认Nginx代理的超时时间较短,若WebSocket长时间无数据传输,Nginx会主动断开连接。

修复方案:
在Nginx的location /块中添加超时配置,延长空闲连接存活时间:

location / {
  proxy_pass http://MYIP:MYPORT;

  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;
  
  # 添加超时配置
  proxy_connect_timeout 7d;
  proxy_send_timeout 7d;
  proxy_read_timeout 7d;
}

4. 缺少WebSocket心跳机制

即使延长了Nginx超时时间,服务器防火墙或网络中间件仍可能断开长时间空闲的连接,需要通过心跳机制保持连接活跃。

修复方案:

后端添加心跳发送逻辑

wss.on("connection", (ws, req) => {
  // ... 之前的解析和客户端存储逻辑

  // 每30秒发送一次心跳
  const heartbeatInterval = setInterval(() => {
    if (ws.readyState === WebSocket.OPEN) {
      ws.send(JSON.stringify({ type: "heartbeat" }));
    }
  }, 30000);

  ws.on("close", () => {
    clearInterval(heartbeatInterval);
    // ... 之前的关闭清理逻辑
  });
});

前端添加心跳回复逻辑

ws.onmessage = (event) => {
  const data = JSON.parse(event.data);
  if (data.type === "heartbeat") {
    // 回复心跳,保持连接活跃
    ws.send(JSON.stringify({ type: "heartbeat-ack" }));
  } else {
    // 处理业务消息
  }
};

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 19:29:57