socket.io v4如何防范恶意滥发连接导致多人游戏服务器崩溃
解决方案
1. Socket.IO v4 获取客户端IP的方法
Socket.IO v4 中客户端IP可以直接从 socket.handshake.address 属性获取,如果你将服务端部署在Nginx等反向代理后,需要先配置代理传递真实IP,此时可以从 socket.handshake.headers['x-forwarded-for'] 中获取真实客户端IP。
示例代码:
io.on('connection', (socket) => { // 直接获取IP(无反向代理场景) const clientIP = socket.handshake.address; // 反向代理场景下获取真实IP // const clientIP = socket.handshake.headers['x-forwarded-for']?.split(',')[0] || socket.handshake.address; console.log(`客户端 ${clientIP} 已连接`); // 其余原有逻辑 })
2. 基于IP的速率限制实现
你可以通过内存对象/Map来统计每个IP的连接次数、事件触发频率,超出阈值直接拒绝连接或者忽略请求即可,示例实现如下:
// 存储IP速率限制统计,key为IP,value为统计对象 const ipRateLimit = new Map(); // 速率限制配置 const RATE_CONFIG = { // 统计窗口时间,单位毫秒,这里设为10秒 WINDOW_MS: 10 * 1000, // 单IP最大连接数 per 窗口 MAX_CONNECTIONS: 5, // 单IP最大go事件触发次数 per 窗口 MAX_GO_REQUESTS: 2 }; // 定时清理过期的统计数据,避免内存泄漏 setInterval(() => { const now = Date.now(); for (const [ip, stats] of ipRateLimit.entries()) { if (now > stats.resetTime) { ipRateLimit.delete(ip); } } }, RATE_CONFIG.WINDOW_MS); io.on('connection', (socket) => { const clientIP = socket.handshake.address; const now = Date.now(); // 初始化该IP的统计数据 if (!ipRateLimit.has(clientIP)) { ipRateLimit.set(clientIP, { connections: 0, goRequests: 0, resetTime: now + RATE_CONFIG.WINDOW_MS }); } const ipStats = ipRateLimit.get(clientIP); // 连接数超出阈值,直接断开连接 if (ipStats.connections >= RATE_CONFIG.MAX_CONNECTIONS) { socket.disconnect(); return; } ipStats.connections += 1; socket.on("go", (name) => { // go事件触发超出阈值,忽略请求,也可以选择直接断开连接 if (ipStats.goRequests >= RATE_CONFIG.MAX_GO_REQUESTS) { return; } ipStats.goRequests += 1; addPlayer(name); }) socket.on("move", (angle) => { // 原有移动逻辑,也可以按需增加速率限制 }) // 连接断开时减少连接计数 socket.on('disconnect', () => { if (ipStats.connections > 0) { ipStats.connections -= 1; } }) })
3. 生产环境优化建议
- 如果是多实例部署的Socket.IO服务,将速率限制统计数据存在Redis中即可实现跨实例的速率限制
- 可以根据业务需求调整阈值,正常用户不会在短时间内多次创建连接、多次触发新建玩家的go事件,阈值设置为远高于正常用户操作频率即可
- 对于确认是恶意攻击的IP,可以直接加入黑名单长期拒绝连接
内容的提问来源于stack exchange,提问作者Coder Gautam YT
相关产品推荐
相关产品推荐

