Node.js net模块多次write数据合并的粘包问题如何解决
问题本质说明
TCP是面向字节流的传输协议,本身不存在「消息边界」的概念,内核会根据Nagle算法、缓冲区大小等机制自动合并/拆分数据,这是TCP的固有特性,不是Node.js net模块的问题,也无法通过配置net模块直接实现单次write对应单次data事件的收发,必须自行在应用层定义数据传输协议来解决粘包、拆包问题。
解决方案:自定义应用层帧协议
最常用且稳定的方案是长度前缀法,每发送一条消息时先附加固定长度的消息体长度字段,接收方按长度精准读取对应字节的内容,完全无需依赖分隔符,同时兼容粘包、拆包两种场景。
实现逻辑
- 发送端:每发送一条数据前,先写入4字节的无符号整数(存储当前数据的字节长度),再写入实际数据内容
- 接收端:为每个连接维护独立的接收缓冲区,每次收到新数据就追加到缓冲区,循环执行解析逻辑:
- 判断缓冲区长度是否≥4字节:不足则等待下一批数据
- 读取前4字节得到当前消息的实际长度,再判断缓冲区剩余长度是否≥该消息长度:不足则等待下一批数据
- 截取对应长度作为完整消息处理,剩余内容留在缓冲区等待后续解析
客户端改造代码
const net = require('net'); const client = net.connect({port: 8080}, function() { console.log('connected to server!'); }); const string = "acdefghijklmnopqrstuvwxyz"; // 封装发送函数,自动添加长度前缀 function sendMsg(data) { const dataBuf = Buffer.from(data, 'utf8'); // 4字节存储数据长度,最大支持4GB单条消息 const lenBuf = Buffer.alloc(4); lenBuf.writeUInt32BE(dataBuf.length, 0); client.write(lenBuf); client.write(dataBuf); } setInterval(function(){ sendMsg(string); sendMsg(string); },1); setInterval(function(){ sendMsg(string); sendMsg(string); },1);
服务端改造代码
const net = require('net'); const net_port = 8080; const server = net.createServer(function(connection){ // 每个连接维护独立接收缓冲区 let receiveBuf = Buffer.alloc(0); connection.on('data', function(data){ // 新数据追加到缓冲区 receiveBuf = Buffer.concat([receiveBuf, data]); // 循环解析缓冲区,支持一次解析多条完整消息 while(receiveBuf.length >= 4) { const msgLen = receiveBuf.readUInt32BE(0); // 剩余内容不足一条完整消息,退出等待下一批数据 if(receiveBuf.length < 4 + msgLen) { break; } // 截取完整消息 const msgContent = receiveBuf.subarray(4, 4 + msgLen).toString('utf8'); console.log("Received, Length: "+msgContent.length); // 固定输出25 // 剩余内容留在缓冲区 receiveBuf = receiveBuf.subarray(4 + msgLen); } }); connection.pipe(connection); }); server.listen(net_port, function() { console.log('server is listening'); });
方案优势
- 完全规避分隔符法的缺陷,无需担心业务内容包含特殊字符导致解析错误
- 同时兼容粘包、拆包场景,不管TCP层如何合并/拆分数据,应用层都能精准还原每条消息
- 解析逻辑简单性能高,无额外的字符串扫描开销
内容的提问来源于stack exchange,提问作者Asad Raza
相关产品推荐
相关产品推荐

