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

如何在TypeScript中同时运行WebSocket与HTTP服务且互不阻塞?

解决方案:同端口部署Express+WebSocket且避免阻塞

1. 正确的服务架构(基础版)

先把Express和WebSocket挂载到同一个HTTP服务器上,结构更清晰,避免初始化时的隐含问题:

import express from 'express';
import http from 'http';
import WebSocket from 'ws';
import indexRouter from './routes/indexRouter';

const PORT = 3000;
const app = express();
// 创建独立的HTTP服务器,同时承载Express和WebSocket
const server = http.createServer(app);

// 挂载Express路由及必要中间件
app.use(express.json());
app.use(indexRouter);

// 初始化WebSocket服务
const wss = new WebSocket.Server({ server });

wss.on('connection', (ws) => {
  console.log('新的WebSocket连接');
  
  ws.on('message', async (data) => {
    // 所有消息处理必须异步,避免阻塞事件循环
    await handleWsMessage(ws, data);
  });

  ws.on('close', () => {
    console.log('WebSocket连接关闭');
  });
});

// 启动服务器
server.listen(PORT, () => {
  console.log(`HTTP服务运行于 http://localhost:${PORT}`);
  console.log(`WebSocket服务运行于 ws://localhost:${PORT}`);
});

// 示例:异步处理WebSocket消息
async function handleWsMessage(ws: WebSocket, data: WebSocket.Data) {
  try {
    // 替换为你的业务逻辑,必须用异步(比如数据库查询、远程API调用)
    const processedData = await asyncBusinessLogic(data.toString());
    ws.send(JSON.stringify({ success: true, data: processedData }));
  } catch (err) {
    ws.send(JSON.stringify({ success: false, error: '消息处理失败' }));
  }
}

// 模拟异步耗时操作
async function asyncBusinessLogic(input: string) {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve(`处理完成:${input}`);
    }, 1000);
  });
}

2. 核心:消除阻塞的关键操作

阻塞的本质是Node.js单线程事件循环被同步耗时代码卡住,不管是Express路由还是WebSocket消息处理,都要遵守以下规则:

(1)Express路由全异步化

禁止在路由回调里写同步耗时代码(比如大循环、fs.readFileSync、同步加密计算等):

// 错误示例(同步阻塞)
router.post('/auth/login', (req, res) => {
  // 同步耗时操作会卡住整个服务
  const loginResult = syncLoginValidation(req.body);
  res.send(loginResult);
});

// 正确示例(异步非阻塞)
router.post('/auth/login', async (req, res) => {
  try {
    // 用异步方法替代同步逻辑
    const loginResult = await asyncLoginValidation(req.body);
    res.status(200).send(loginResult);
  } catch (err) {
    res.status(401).send({ error: '登录失败' });
  }
});

(2)WebSocket消息处理避免同步逻辑

大量WebSocket数据包涌入时,同步处理会导致消息排队、整个服务卡顿,必须保证每个消息的处理逻辑都是异步的。如果涉及CPU密集型任务(比如大数据计算),不要放在主线程,改用集群模式或轻量进程池(比如piscina)。

3. 进阶:集群模式利用多核CPU

如果服务面临高并发或CPU密集型任务,单线程撑不住,用Node.js内置的cluster模块启动多个工作进程,每个进程独立处理HTTP请求和WebSocket连接,自动利用多核CPU:

import cluster from 'cluster';
import os from 'os';
import express from 'express';
import http from 'http';
import WebSocket from 'ws';
import indexRouter from './routes/indexRouter';

const PORT = 3000;
const numCPUs = os.cpus().length;

// 主进程负责管理工作进程
if (cluster.isPrimary) {
  console.log(`主进程 ${process.pid} 启动`);

  // 按CPU核心数启动工作进程
  for (let i = 0; i < numCPUs; i++) {
    cluster.fork();
  }

  // 工作进程崩溃时自动重启
  cluster.on('exit', (worker) => {
    console.log(`工作进程 ${worker.process.pid} 退出,重启中...`);
    cluster.fork();
  });
} else {
  // 每个工作进程独立运行Express+WebSocket服务
  const app = express();
  const server = http.createServer(app);
  
  app.use(express.json());
  app.use(indexRouter);

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

  wss.on('connection', (ws) => {
    ws.on('message', async (data) => {
      await handleWsMessage(ws, data);
    });
  });

  server.listen(PORT, () => {
    console.log(`工作进程 ${process.pid} 监听端口 ${PORT}`);
  });
}

// 异步处理函数同上...

4. 为什么你之前的方案会阻塞?

你的原始代码虽然实现了同端口共存,但如果Express路由或WebSocket消息处理中存在同步耗时代码,会直接阻塞Node.js的事件循环——所有后续的HTTP请求、WebSocket消息都会排队等待同步代码执行完成,导致服务卡顿。

5. 关于worker_threads的替代方案

worker_threads适合处理单个CPU密集型任务,但模块兼容问题较多(比如部分第三方库不支持在worker线程中运行)。相比之下,cluster模式更简单:每个工作进程都是独立的Node.js进程,模块导入、资源使用(比如数据库连接)和普通服务完全一致,不会出现worker_threads的兼容性问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.02 06:45:36