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

