如何在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
相关产品推荐
相关产品推荐

