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

Node.js TCP服务端:为客户端创建隔离VM上下文或专属全局会话变量

嘿,我来帮你搞定这两个方案的实现,两种思路都给你捋得明明白白的:

方案一:通过参数传递Session给Core子类

这个方案更符合模块化设计的思路,依赖关系清晰,调试维护起来也方便。核心就是让所有继承自Core的类在构造时接收session,并传递给父类,后续所有子类实例都能直接访问session。

步骤1:定义Core基类

先写Core.js,让它在构造时保存传入的session,子类继承后就能直接用:

// Core.js
class Core {
  constructor(session) {
    this.session = session; // 将session绑定到实例上,所有子类共享
  }

  // 可以添加Core的通用方法,比如获取sessionID
  getSessionId() {
    return this.session.sessionId;
  }
}

module.exports = Core;

步骤2:实现Client子类(继承Core)

Client作为核心客户端类,继承Core后,在构造时把session传给父类,同时实例化其他Core子类时也要传递session:

// Client.js
const Core = require('./Core');
const SomeBusinessModule = require('./SomeBusinessModule'); // 另一个Core子类

class Client extends Core {
  constructor(session) {
    super(session); // 把session传递给父类Core
    this.businessModule = new SomeBusinessModule(session); // 实例化其他子类时传入session
  }

  start() {
    // 直接访问session或调用父类方法
    console.log(`当前客户端Session ID: ${this.getSessionId()}`);
    this.businessModule.handleBusiness();
  }
}

module.exports = Client;

步骤3:服务器端创建Session并传递

每个客户端连接过来时,创建专属session,再实例化Client并传入session:

// server.js
const net = require('net');
const Client = require('./Client');

const server = net.createServer((socket) => {
  // 为每个连接生成专属Session,存储登录信息、socket、SessionID等
  const session = {
    sessionId: `sid_${Date.now()}_${Math.random().toString(36).slice(2)}`,
    socket: socket,
    loginInfo: null, // 登录后填充
    remoteAddress: `${socket.remoteAddress}:${socket.remotePort}`
  };

  // 创建Client实例并传入session
  const clientInstance = new Client(session);
  clientInstance.start();

  // 存储客户端实例
  server.clients.push({
    clientAddr: session.remoteAddress,
    clientInst: clientInstance
  });
});

server.clients = [];
server.listen(3000, () => {
  console.log('TCP服务器已启动,监听端口3000');
});

这个方案的好处是依赖明确,没有隐式的全局变量,每个模块的session来源清晰,出问题时容易定位。


方案二:为每个Client实例创建专属全局变量glob_ses

如果不想到处传递session,希望模块里能直接访问专属的glob_ses,可以用两种方式实现:Node.js的vm模块做上下文隔离,或者用cls-hooked实现异步本地存储。

方式A:利用vm模块创建隔离上下文

这种方式和你之前的代码思路一致,为每个客户端创建独立的VM上下文,把glob_ses注入进去,让所有相关模块都在这个上下文里运行:

服务器端代码

// server.js
const net = require('net');
const vm = require('vm');
const path = require('path');

const server = net.createServer((socket) => {
  // 创建专属Session
  const session = {
    sessionId: `sid_${Date.now()}_${Math.random().toString(36).slice(2)}`,
    socket: socket,
    loginInfo: null,
    getLogin() {
      return this.loginInfo;
    },
    setLogin(info) {
      this.loginInfo = info;
    }
  };

  // 创建VM上下文,注入glob_ses、自定义require(解决模块路径问题)和console
  const context = vm.createContext({
    glob_ses: session,
    require: (modulePath) => {
      // 解析绝对路径,确保模块能正确加载
      const absPath = path.isAbsolute(modulePath) ? modulePath : path.join(__dirname, modulePath);
      return require(absPath);
    },
    console: console,
    server: server // 注入服务器实例,方便存储客户端
  });

  // 在隔离上下文里初始化Client
  vm.runInContext(`
    const Client = require('./Client');
    const clientInstance = new Client(glob_ses.socket);
    clientInstance.start();
    
    // 存储客户端实例
    server.clients.push({
      clientAddr: \`\${glob_ses.socket.remoteAddress}:\${glob_ses.socket.remotePort}\`,
      clientInst: clientInstance
    });
  `, context, { filename: 'client-context.js' });
});

server.clients = [];
server.listen(3000, () => {
  console.log('TCP服务器已启动,监听端口3000');
});

Client模块(直接访问glob_ses)

// Client.js
const SthModule = require('./SthModule');

class Client {
  constructor(socket) {
    glob_ses.socket = socket; // 这里可以直接用glob_ses
    this.sthModule = new SthModule();
  }

  start() {
    console.log(`当前客户端Session ID: ${glob_ses.sessionId}`);
    this.sthModule.doSomething();
  }
}

module.exports = Client;

依赖模块SthModule(直接访问glob_ses)

// SthModule.js
class SthModule {
  doSomething() {
    // 直接调用glob_ses的方法
    console.log(`当前登录信息: ${JSON.stringify(glob_ses.getLogin())}`);
  }
}

module.exports = SthModule;

方式B:用cls-hooked实现异步本地存储

如果你的项目异步操作比较多,用cls-hooked(基于Node.js的async_hooks)可以更优雅地实现上下文追踪,不需要VM隔离:

步骤1:安装依赖

npm install cls-hooked

步骤2:创建Session命名空间

// session-namespace.js
const cls = require('cls-hooked');
// 创建专属命名空间,用于存储每个客户端的session
const sessionNamespace = cls.createNamespace('client-session');

module.exports = sessionNamespace;

步骤3:服务器端绑定Session到命名空间

// server.js
const net = require('net');
const sessionNamespace = require('./session-namespace');
const Client = require('./Client');

const server = net.createServer((socket) => {
  const session = {
    sessionId: `sid_${Date.now()}_${Math.random().toString(36).slice(2)}`,
    socket: socket,
    loginInfo: null,
    getLogin() {
      return this.loginInfo;
    },
    setLogin(info) {
      this.loginInfo = info;
    }
  };

  // 在命名空间的运行上下文里绑定session
  sessionNamespace.run(() => {
    sessionNamespace.set('glob_ses', session);
    const clientInstance = new Client(socket);
    clientInstance.start();

    server.clients.push({
      clientAddr: `${socket.remoteAddress}:${socket.remotePort}`,
      clientInst: clientInstance
    });
  });
});

server.clients = [];
server.listen(3000, () => {
  console.log('TCP服务器已启动,监听端口3000');
});

Client模块(从命名空间获取glob_ses)

// Client.js
const sessionNamespace = require('./session-namespace');
const SthModule = require('./SthModule');

class Client {
  constructor(socket) {
    const glob_ses = sessionNamespace.get('glob_ses');
    glob_ses.socket = socket;
    this.sthModule = new SthModule();
  }

  start() {
    const glob_ses = sessionNamespace.get('glob_ses');
    console.log(`当前客户端Session ID: ${glob_ses.sessionId}`);
    this.sthModule.doSomething();
  }
}

module.exports = Client;

SthModule模块(从命名空间获取glob_ses)

// SthModule.js
const sessionNamespace = require('./session-namespace');

class SthModule {
  doSomething() {
    const glob_ses = sessionNamespace.get('glob_ses');
    console.log(`当前登录信息: ${JSON.stringify(glob_ses.getLogin())}`);
  }
}

module.exports = SthModule;

这种方式的好处是不需要VM,能自动追踪异步上下文,同一个客户端的所有异步操作都能拿到专属的glob_ses。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.28 07:31:38