Koa WebSocket数据转发报错:TypeError致Node.js服务崩溃求助
问题描述
我用Node.js搭建了一个服务器,通过WebSocket接收「休眠客户端」的数据,再推送给Web客户端。休眠客户端每隔几分钟唤醒一次,采集完数据推送后就休眠,数据流转路径是:
休眠客户端 → WebSocket → Node.js服务器 → WebSocket → Web客户端
系统只有一个休眠客户端,但支持多个Web客户端。所以有新数据到的时候,要推给所有Web客户端的WebSocket连接。我用数组globalCtx记录所有Web客户端的上下文,用来后续推送。
问题:给Web客户端推送数据时出现上下文错误,导致Node.js服务崩溃。
原始代码
var globalCtx = []; app.ws.use( function( ctx, next ) { ctx.websocket.on('message', function ( message ) { if( "telemetry" in jsonMsg ) { console.log( "Got Telemetry: " ); console.log( message.toString() ); // 收到遥测数据,推送给所有Web客户端的WebSocket globalCtx.forEach( (myCtx, next) => { console.log( "DEBUG: ", util.inspect(myCtx) ); // <----- 这里崩溃 myCtx.send( jsonMsg ); // <----- 不调试时这里崩溃 }); } else if( "webClient" in jsonMsg ) { console.log( "Got WS Client: " ); console.log( message.toString() ); // 收到Web客户端连接消息,保存上下文后续推送用 if( Array.isArray( globalCtx ) && globalCtx.length ) { // 查找是否已存在该IP的记录 for( let idx = 0; idx < globalCtx.length; idx++ ) { if( globalCtx[ idx ].ip == ctx.ip ) { // 已存在该IP,不做处理 console.log("IP already found: ", ctx.ip ); return; } } // 没找到,添加新IP console.log("not found, adding IP: ", ctx.ip ); globalCtx.push( ctx ); } else { // 数组为空,直接添加 console.log("empty array adding IP: ", ctx.ip ); globalCtx.push( ctx ); } } }); return next(ctx); });
报错信息
调试时(执行util.inspect(myCtx))
/home/pi/node_modules/koa/lib/response.js:73 return this.res.statusCode; ^ TypeError: Cannot read property 'statusCode' of undefined at Object.get status [as status] (/home/pi/node_modules/koa/lib/response.js:73:21) at /home/pi/node_modules/only/index.js:6:20 at Array.reduce (<anonymous>) at module.exports (/home/pi/node_modules/only/index.js:5:15) at Object.toJSON (/home/pi/node_modules/koa/lib/response.js:562:12) at Object.toJSON (/home/pi/node_modules/koa/lib/context.js:51:31) at Object.inspect (/home/pi/node_modules/koa/lib/context.js:33:17) at formatValue (internal/util/inspect.js:745:19) at Object.inspect (internal/util/inspect.js:319:10) at /home/pi/koaThermostat/index2.js:46:46
执行myCtx.send(jsonMsg)时
/home/pi/koaThermostat/index2.js:47 myCtx.send( jsonMsg ); ^ TypeError: myCtx.send is not a function at /home/pi/koaThermostat/index2.js:47:23
问题原因与修复方案
核心问题
- 存错了对象:你把Koa的
ctx上下文存入数组,但推送数据需要的是ctx.websocket这个WebSocket实例,Koa上下文在连接结束后会被销毁,导致后续访问时出现属性未定义的错误。 - 参数命名冲突:
forEach回调里的next是数组索引,和Koa的next函数重名,属于不良写法。 - 未处理断开连接:Web客户端断开后,数组里的无效实例没被移除,推送时会报错。
- 未解析消息:直接用
jsonMsg但没把message转成JSON对象,导致判断逻辑失效。
修复后的代码
const globalWsClients = []; // 改名更清晰,专门存WebSocket实例 const util = require('util'); // 确保引入util模块 app.ws.use( function( ctx, next ) { const ws = ctx.websocket; const clientIp = ctx.ip; // 监听消息 ws.on('message', function ( message ) { let jsonMsg; try { jsonMsg = JSON.parse(message.toString()); // 解析消息为JSON,捕获解析错误 } catch (err) { console.error('消息解析失败:', err); return; } if( "telemetry" in jsonMsg ) { console.log( "收到遥测数据: " ); console.log( jsonMsg ); // 推送给所有在线Web客户端 globalWsClients.forEach( (clientWs) => { if (clientWs.readyState === 1) { // 确保连接处于开放状态 clientWs.send(JSON.stringify(jsonMsg)); } }); } else if( "webClient" in jsonMsg ) { console.log( "收到Web客户端连接: ", clientIp ); // 检查是否已存在该客户端的连接 const exists = globalWsClients.some(client => client._clientIp === clientIp); if (!exists) { ws._clientIp = clientIp; // 给WebSocket实例标记IP globalWsClients.push(ws); console.log("添加新客户端IP: ", clientIp ); } else { console.log("IP已存在: ", clientIp ); } } }); // 监听连接关闭,移除无效实例 ws.on('close', function() { const index = globalWsClients.findIndex(client => client === ws); if (index !== -1) { console.log("客户端断开连接,移除IP: ", clientIp ); globalWsClients.splice(index, 1); } }); return next(ctx); });
关键修复点说明
- 存储WebSocket实例:用
ctx.websocket存入数组,推送时调用ws.send()才是正确方法。 - 消息解析处理:添加
JSON.parse并捕获错误,避免非法消息导致服务崩溃。 - 清理无效连接:监听WebSocket的
close事件,从数组中删除断开的连接。 - 检查连接状态:推送前判断
readyState === 1(OPEN状态),确保连接可用。 - 简化IP检查:用
some替代循环查找,代码更简洁。
内容的提问来源于stack exchange,提问作者Aaron
相关产品推荐
相关产品推荐

