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

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

问题原因与修复方案

核心问题

  1. 存错了对象:你把Koa的ctx上下文存入数组,但推送数据需要的是ctx.websocket这个WebSocket实例,Koa上下文在连接结束后会被销毁,导致后续访问时出现属性未定义的错误。
  2. 参数命名冲突:forEach回调里的next是数组索引,和Koa的next函数重名,属于不良写法。
  3. 未处理断开连接:Web客户端断开后,数组里的无效实例没被移除,推送时会报错。
  4. 未解析消息:直接用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.21 14:24:51