Node.js中zlib.inflate解压Gzip响应及GBK编码失败,求TCP分包方案
问题描述
从服务器获取的响应数据格式为[{4字节}{内容数据}]:
- 4字节为不含自身的包长度,且首字节是长度低字节(小端序)
- 内容数据是经Gzip压缩、GBK编码的十六进制数据
收到的Buffer数据示例:
<Buffer 2d 00 00 00 1f 8b 08 00 00 00 00 00 00 ff 32 34 34 37 32 31 33 32 36 32 30 a8 31 ac 31 e2 02 00 00 00 ff ff 01 00 00 ff ff 96 39 e9 8c 10 00 00 00> <Buffer d5 19 0f 00 1f 8b 08 00 00 00 00 00 00 ff a4 bd ff 73 1b d7 b5 27 f8 fb fc 15 5d 35 55 5b 49 6d 3d 4d df db df f3 d3 82 a0 28 29 24 25 0d 21 89 13 67 ... 12986 more bytes> <Buffer ca ee e7 13 20 8a c5 f7 1b 66 68 1f d7 64 13 b3 f7 64 d8 0e 7d a7 fd bb 10 1f 4a 27 53 b9 c9 0b 2c 93 36 3e 94 ef db 9e 88 bc c6 6b 2e 1c ed ea b5 e8 ... 15878 more bytes>
尝试使用zlib.inflate解压时触发错误:TypeError: Cannot read properties of undefined (reading 'toString'),所用代码如下:
client.on("data", (data) => { zlib.inflate(data, (err, data) => { console.log(data.toString('hex')); });
需解决以下问题:
- 正确解码、解压响应数据
- 处理TCP数据包分片问题
解决方案
1. 处理TCP分片:缓存拼接完整数据包
TCP是流式传输,单次data事件拿到的可能是不完整包或多个包的拼接,必须先做粘包/拆包处理:
- 维护全局缓存Buffer,存储未处理的剩余数据
- 每次收到新数据后,追加到缓存中
- 循环检查缓存:
- 读取前4字节,用小端序解析出内容数据的长度(该长度不含4字节头)
- 若缓存总长度 >= 4 + 内容长度,截取出完整的内容数据(跳过前4字节),处理后更新缓存;否则等待下一次数据
2. 正确解压与解码
之前的错误是因为直接将含4字节头的不完整数据传给zlib.inflate,解压失败后data为undefined,调用toString触发报错。正确步骤:
- 从完整包中提取第4字节后的Gzip压缩数据
- 使用
zlib.inflate解压该部分数据 - 用GBK编码转成字符串(Node.js核心不支持GBK,需安装
iconv-lite)
完整代码示例
const net = require('net'); const zlib = require('zlib'); const iconv = require('iconv-lite'); // 全局缓存,存储未处理的TCP数据 let cache = Buffer.from([]); const client = net.createConnection({ port: 你的端口, host: '你的服务器地址' }, () => { console.log('连接成功'); }); client.on('data', (chunk) => { // 追加新数据到缓存 cache = Buffer.concat([cache, chunk]); // 循环解析完整包 while (cache.length >= 4) { // 小端序解析内容长度 const contentLength = cache.readUInt32LE(0); const totalPacketLength = 4 + contentLength; if (cache.length < totalPacketLength) { // 缓存不足,等待下一次数据 break; } // 提取Gzip压缩数据 const compressedData = cache.slice(4, totalPacketLength); // 更新缓存,移除已处理的包 cache = cache.slice(totalPacketLength); // 解压并解码 zlib.inflate(compressedData, (err, decompressed) => { if (err) { console.error('解压失败:', err); return; } const result = iconv.decode(decompressed, 'gbk'); console.log('处理结果:', result); }); } }); client.on('error', (err) => { console.error('连接错误:', err); }); client.on('close', () => { console.log('连接关闭'); });
关键注意点
- 包长度是小端序,必须用
readUInt32LE解析,不能用默认大端序readUInt32BE - 必须确保传给
zlib.inflate的是完整的Gzip压缩数据,否则会解压失败 - 安装
iconv-lite命令:npm install iconv-lite - 要捕获解压异常,避免程序崩溃
内容的提问来源于stack exchange,提问作者TungTung
相关产品推荐
相关产品推荐

