Node.js net.socket仅接收首条消息问题(WoW认证服务器场景)
问题:Node.js实现魔兽认证服务器无法接收后续数据包
开发《魔兽世界》(World of Warcraft)客户端的认证服务器时遇到异常:Node.js的net.socket仅能接收客户端首条消息,后续数据包在Wireshark中可见,但代码无法捕获,程序停滞在处理首个数据包阶段。
用Go语言实现相同逻辑的服务器可正常接收所有数据包,运行符合预期。此外,Node.js模拟客户端与该服务器交互正常,推测问题与《魔兽世界》客户端和Node.js socket实现特性相关。
原Node.js实现代码
const net = require('net') const server = net.createServer((socket) => { console.log('Client connected'); socket.removeAllListeners('data'); socket.on('end', () => console.log('Client disconnected!')); socket.on('close', () => console.log('Client closed connection to server.')); socket.on('drain', () => console.log('Buffer drain!')); socket.on('data', function(data) { console.log(data) onData(socket, data) }); socket.pipe(socket); }).on('error', (err) => { console.log('Server error:' + err) }); function onData(socket, data) { console.log(`data length`, data.length) if (data.length === 44) { socket.write(Buffer.from('0000007cdc3e6e9cabc0cb57332ee5086df1b1ccc491d685996af531b4ec807ed2c667010720b79b3e2a87823cab8f5ebfbf8eb10108535006298b5badbd5b53e1895e644b891f97f4daa96fca449ea98bd3c8da6963012888a89b8e572abff4294e05c4fc98baa31e99a00b2157fc373fb369cdd2f100', 'hex'), (err) => { if (err) console.log(err); console.log(`Write data end`); socket.end(); //<-- also tried without FIN, since response packet is 0x0 terminated }) } console.log('onData::End'); } server.listen(3724, '127.0.0.1', function() { console.log('Server is listening'); });
运行输出
Server is listening Client connected <Buffer 00 08 28 00 57 6f 57 00 03 03 05 34 30 36 38 78 00 6e 69 57 00 55 52 75 72 b4 00 00 00 7f 00 00 01 0a 53 49 4c 56 45 52 4c 45 58 58> data length 44 onData::End Write data end
问题定位与解决方案
核心问题点
socket.pipe(socket)的错误使用:这行代码会将socket的输入直接回写到输出,形成无效的数据回环,干扰正常的协议交互逻辑,导致后续数据包被异常处理。- 未处理TCP粘包/拆包:Node.js的
data事件是基于TCP流触发的,无法保证每次事件对应一个完整的魔兽认证数据包,必须手动维护缓冲区来解析完整数据包。 - 不必要的连接关闭:
socket.end()会主动关闭TCP连接,即使注释掉,回环逻辑也会导致客户端无法正常发送后续数据包。 - 多余的监听器移除:
socket.removeAllListeners('data')完全多余,刚创建的socket未绑定任何data监听器,这行代码无意义。
修改后的代码实现
const net = require('net') const server = net.createServer((socket) => { console.log('Client connected'); socket.on('end', () => console.log('Client disconnected!')); socket.on('close', () => console.log('Client closed connection to server.')); socket.on('drain', () => console.log('Buffer drain!')); // 维护缓冲区处理TCP粘包/拆包问题 let buffer = Buffer.alloc(0); socket.on('data', function(data) { buffer = Buffer.concat([buffer, data]); console.log(`当前缓冲区总长度: ${buffer.length}`); // 魔兽认证协议以4字节大端长度前缀开头,需根据实际协议调整字节序 while (buffer.length >= 4) { const packetLength = buffer.readUInt32BE(0); if (buffer.length >= packetLength) { // 提取完整数据包 const packet = buffer.slice(0, packetLength); buffer = buffer.slice(packetLength); onData(socket, packet); } else { // 数据不足,等待下一次data事件 break; } } }); }).on('error', (err) => { console.log('Server error:' + err) }); function onData(socket, data) { console.log(`数据包长度`, data.length) if (data.length === 44) { const response = Buffer.from('0000007cdc3e6e9cabc0cb57332ee5086df1b1ccc491d685996af531b4ec807ed2c667010720b79b3e2a87823cab8f5ebfbf8eb10108535006298b5badbd5b53e1895e644b891f97f4daa96fca449ea98bd3c8da6963012888a89b8e572abff4294e05c4fc98baa31e99a00b2157fc373fb369cdd2f100', 'hex'); socket.write(response, (err) => { if (err) console.log(err); console.log(`响应发送完成`); // 保持连接,等待客户端后续数据包 }); } console.log('onData::End'); } server.listen(3724, '127.0.0.1', function() { console.log('Server is listening'); });
关键修改说明
- 移除
socket.pipe(socket),消除数据回环对协议交互的干扰。 - 添加缓冲区逻辑,按魔兽认证协议的长度前缀解析完整数据包,解决TCP流的粘包/拆包问题。
- 移除
socket.end(),保持TCP连接以接收客户端后续的认证数据包。 - 删除多余的
socket.removeAllListeners('data')代码。
额外注意事项
- 确认魔兽认证协议的长度前缀字节序(大端/小端),如果是小端需将
readUInt32BE替换为readUInt32LE。 - 验证响应数据包的格式和内容是否完全符合协议要求,错误的响应会导致客户端停止后续交互。
内容的提问来源于stack exchange,提问作者Silverlay
相关产品推荐
相关产品推荐

