Node.js TCP服务端密码认证逻辑重复触发问题排查求助
问题排查与修复方案
重复校验问题根因
- 同一socket重复绑定
data事件:userAuth函数内为socket绑定了密码校验用的data事件监听器,认证通过后newConnectionHandler又为同一socket绑定了业务数据处理的data事件监听器。Node.js 事件监听器是叠加生效的,后续客户端发送的所有数据会同时触发两个监听器,密码校验逻辑会把业务数据当作密码二次校验,匹配失败就销毁连接。 - 连接逻辑仅执行一次:
connection事件仅在客户端首次建连时触发一次,已认证列表判断逻辑仅在建连瞬间执行,后续该连接上的所有数据传输不会再触发该判断,原有依赖该逻辑拦截未认证请求的设计无法生效。 - 列表操作逻辑不匹配:
sock数组存储的是ip:port格式的字符串,close事件中查找下标时使用对象属性匹配,永远无法命中对应记录,客户端下线后已认证记录不会被清除。 - 全局事件重复绑定:
server.on('error', errorHandler)定义在connection回调内部,每次新客户端建连都会重复绑定错误事件,触发错误时会重复执行回调。
服务端回发数据说明
不需要调用server.createConnection相关逻辑,该API用于创建TCP客户端主动连接其他服务。你直接使用connection事件回调中拿到的socket对象,调用socket.write(要发送的内容)即可向当前连接的客户端回发数据,原有代码中 socket.write('Welcome \n')就是正确的实现方式。
修复方案
调整要点
- 将密码校验的
data事件回调改为具名函数,认证通过后主动移除该监听器,避免重复触发 - 密码校验时对输入内容做
trim处理,避免换行符、空白字符导致的校验失败 - 将全局错误事件绑定移到
connection回调外部 - 修正
sock数组的匹配逻辑,确保客户端下线时能正确清除已认证记录
修复后完整代码
const net = require('net'); const port = 3001; const host = '192.168.0.165'; const server = net.createServer() let authenticatedClients = []; // 重命名更语义化,存储已认证的客户端地址字符串 const validPassword = '123'; // 改为字符串类型,避免类型隐式转换问题 // 全局错误事件移到外部绑定 server.on('error', errorHandler); server.on('connection', (socket) => { const clientAddress = `${socket.remoteAddress}:${socket.remotePort}`; console.log(`新客户端接入:${clientAddress}`); if(authenticatedClients.includes(clientAddress)){ console.log('客户端已认证,进入通信逻辑') initCommunicationHandler(socket, clientAddress) } else { console.log('客户端未认证,启动密码校验') handleAuth(socket, clientAddress) } function handleAuth(socket, clientAddress){ socket.write('password : ' ) // 具名函数定义校验回调,方便后续移除 const authHandler = (data) => { const inputPassword = data.toString().trim(); if (inputPassword === validPassword) { // 认证成功,移除密码校验监听器 socket.off('data', authHandler); authenticatedClients.push(clientAddress); console.log(`客户端${clientAddress}认证通过,已认证列表:`, authenticatedClients) initCommunicationHandler(socket, clientAddress) } else { socket.write('Sorry, you cannot access the server \n') console.log(`客户端${clientAddress}认证失败,断开连接`) socket.write('connection closed') socket.destroy() } } socket.on('data', authHandler) } function initCommunicationHandler(socket, clientAddress){ socket.write('Welcome \n') // 业务数据处理 socket.on('data', (data) => { const content = data.toString().trim(); console.log(`收到客户端${clientAddress}发送的业务数据:${content}`); // 示例:回发响应给客户端 socket.write(`Server received: ${content}\n`); }); // 连接关闭处理 socket.once('close', () => { // 修正匹配逻辑,直接匹配地址字符串 const index = authenticatedClients.findIndex(item => item === clientAddress); if (index !== -1) { authenticatedClients.splice(index, 1); } // 广播给其他已认证客户端(如果需要) authenticatedClients.forEach(address => { console.log(`广播下线通知给:${address}`) // 这里如果要广播需要存储所有已认证的socket对象,而不仅仅是地址,当前逻辑仅打印日志 }); console.log(`客户端${clientAddress}断开连接,当前已认证列表:`, authenticatedClients); }); } }) function errorHandler(err){ console.log(`服务端发生错误:${err.message}`); } server.listen(port, host, () => { console.log(`TCP Server is running on ${host}:${port}`); });
内容的提问来源于stack exchange,提问作者Ucin
相关产品推荐
相关产品推荐

