如何在Node.js中实现TCP套接字移交?游戏分布式服务器场景
Node.js 中 TCP 套接字移交至游戏服务器的实现方案
方案一:Unix/Linux 环境下的文件描述符传递(无缝接管)
这是最接近"移交套接字"原生需求的方案,依赖Unix系统的文件描述符传递能力,无需客户端重新连接。
核心逻辑:认证服务器完成校验后,将客户端TCP套接字的文件描述符通过IPC通道(如Unix域套接字)发送给游戏服务器,游戏服务器用该描述符重建套接字实例,接管连接。
实现步骤:
- 认证服务器获取客户端套接字的文件描述符:Node.js 10+ 可直接使用
socket.fd,低版本可访问内部属性socket._handle.fd(注意内部属性可能随版本变化)。 - 通过Unix域套接字向游戏服务器传递描述符:利用
net.Socket的sendFD方法(需确保IPC通道支持)。 - 游戏服务器接收描述符后,用
new net.Socket({ fd: 接收的描述符 })创建套接字,恢复数据监听。
- 认证服务器获取客户端套接字的文件描述符:Node.js 10+ 可直接使用
简化代码示例:
认证服务器端:const net = require('net'); // 启动IPC服务,用于和游戏服务器通信 const ipcServer = net.createServer(ipcSocket => { // 处理客户端认证连接 const authServer = net.createServer(clientSocket => { clientSocket.on('data', authData => { // 模拟认证成功逻辑 const authSuccess = true; if (authSuccess) { // 发送移交信号+文件描述符 ipcSocket.write(Buffer.from('TRANSFER'), err => { if (!err) { ipcSocket.sendFD(clientSocket.fd); clientSocket.unref(); // 认证服务器不再持有该套接字引用 } }); } }); }); authServer.listen(3000); }); ipcServer.listen('/tmp/game_transfer.sock');游戏服务器端:
const net = require('net'); // 连接到认证服务器的IPC通道 const ipcClient = net.connect('/tmp/game_transfer.sock'); ipcClient.on('fd', fd => { // 用接收的文件描述符重建套接字 const gameSocket = new net.Socket({ fd }); gameSocket.on('data', gameData => { // 处理游戏业务逻辑 gameSocket.write('已接入游戏服务器,开始交互'); }); gameSocket.resume(); // 恢复数据流动 });
方案二:跨平台中转代理模式(兼容性优先)
如果需要支持Windows等不支持文件描述符传递的平台,推荐这种客户端重连方案,实现简单且跨平台。
核心逻辑:认证完成后,认证服务器向客户端返回游戏服务器地址+临时会话令牌,客户端断开认证连接后,携带令牌连接游戏服务器,游戏服务器验证令牌有效性后建立游戏连接。
优势:无平台限制,实现成本低;缺点是客户端需要重新发起连接,存在少量延迟。
简化代码示例:
认证服务器端(依赖Redis存储会话):const net = require('net'); const redis = require('redis'); const redisClient = redis.createClient(); const authServer = net.createServer(async clientSocket => { clientSocket.on('data', async data => { const { username, password } = JSON.parse(data.toString()); // 模拟认证校验 if (username === 'player' && password === 'game123') { // 生成临时会话令牌,5分钟过期 const sessionToken = `game_token_${Date.now()}`; await redisClient.setEx(sessionToken, 300, JSON.stringify({ username })); // 向客户端发送游戏服务器信息和令牌 clientSocket.write(JSON.stringify({ gameServer: { host: '127.0.0.1', port: 3001 }, token: sessionToken })); clientSocket.end(); } }); }); authServer.listen(3000);游戏服务器端:
const net = require('net'); const redis = require('redis'); const redisClient = redis.createClient(); const gameServer = net.createServer(async clientSocket => { clientSocket.on('data', async data => { const { token } = JSON.parse(data.toString()); const userInfo = await redisClient.get(token); if (userInfo) { // 验证通过,删除令牌防止重复使用 await redisClient.del(token); clientSocket.write('认证通过,进入游戏'); // 处理游戏逻辑 } else { clientSocket.write('无效会话令牌'); clientSocket.end(); } }); }); gameServer.listen(3001);
方案三:临时管道中转(过渡方案)
如果需要客户端无感知过渡,又不想依赖平台特性,可以用双向管道中转数据,直到游戏服务器确认接管。
核心逻辑:认证服务器完成校验后,主动连接游戏服务器,将客户端套接字与游戏服务器的连接双向管道,实现数据中转,后续可根据业务逻辑决定何时断开中转。
缺点:认证服务器会承担中转压力,不适合高并发场景。
简化代码示例:
// 认证服务器端 const net = require('net'); const authServer = net.createServer(clientSocket => { clientSocket.on('data', authData => { // 模拟认证成功 if (true) { // 连接游戏服务器 const gameConn = net.connect(3001, '127.0.0.1'); // 双向管道数据 clientSocket.pipe(gameConn); gameConn.pipe(clientSocket); // 告知游戏服务器客户端身份 gameConn.write(JSON.stringify({ userId: 'player_1001' })); } }); }); authServer.listen(3000);
内容的提问来源于stack exchange,提问作者양성철
相关产品推荐
相关产品推荐

