Node.js中使用mqtt.js传输文件时Buffer体积过大问题求解
Hey Thomas, 我完全懂你现在的困扰——好好的6MB文件,经过JSON序列化后居然膨胀到21MB,这对MQTT传输来说太不友好了。让我帮你拆解问题根源,再给出几个实用的解决方案:
问题到底出在哪?
你当前的代码里,把fs.readFileSync得到的Buffer直接塞进对象,再用JSON.stringify序列化。这时候JSON会把Buffer转换成字节数组(比如[123,45,67,...]),这种文本格式存储二进制数据的冗余度极高,直接导致体积暴增,这就是核心原因。
最佳解决方案
方案1:用Base64编码压缩体积(简单易上手)
Base64是专门用来把二进制转文本的编码方式,只会让体积增加约33%(6MB→8MB左右),远优于JSON直接转数组的膨胀率。
修改你的genPayload函数即可:
function genPayload() { data = fs.readFileSync('app.rar'); let message = { "filename": "app.rar", "filePath": "C:\\test\\", "data": data.toString('base64') // 将Buffer转成Base64字符串 } console.log('Preparing File') bufferMessage = JSON.stringify(message); }
接收方还原文件时,只需要执行Buffer.from(接收的data字段, 'base64')就能得到原始文件Buffer。
方案2:直接传输二进制Payload(效率最高)
MQTT本身就支持二进制消息,完全没必要把二进制文件转成文本格式。你可以把文件名、路径这些元数据放在**MQTT 5的用户属性(User Properties)**里,或者直接嵌入Topic中,然后直接把文件Buffer作为消息体发送。
修改后的完整代码示例:
var mqtt = require('mqtt'); var fs = require('fs'); let client let fileBuffer let fileMeta = { filename: "app.rar", filePath: "C:\\test\\" } function genPayload() { fileBuffer = fs.readFileSync('app.rar'); console.log('Preparing File') } function Connect() { client = mqtt.connect("mqtt://test.mosquitto.org", { clientId: "vfs001", protocolVersion: 5 // 启用MQTT 5以支持用户属性 }); client.on('connect', function () { console.log('Client Connected') }); } function SendFile() { // 元数据放在userProperties,直接发送二进制Buffer client.publish('TestReplFile', fileBuffer, { properties: { userProperties: fileMeta } }) console.log('File is on the way') }; genPayload(); Connect(); setTimeout(SendFile, 5000);
这种方式完全没有体积膨胀,是效率最高的方案。如果你的MQTT broker不支持MQTT 5,也可以把元数据拼在Topic里,比如TestReplFile/app.rar/C:\\test\\,接收方解析Topic就能拿到元数据。
方案3:用二进制序列化库(兼顾易用性和效率)
如果还是想把元数据和文件数据放在同一个消息里,可以用MessagePack这类二进制序列化库,它能高效处理包含Buffer的对象,体积比JSON小很多,用法还和JSON类似。
先安装依赖:npm install msgpack-lite
修改代码:
var mqtt = require('mqtt'); var fs = require('fs'); var msgpack = require('msgpack-lite'); let client let bufferMessage let data function genPayload() { data = fs.readFileSync('app.rar'); let message = { "filename": "app.rar", "filePath": "C:\\test\\", "data": data } console.log('Preparing File') bufferMessage = msgpack.encode(message); // 用MessagePack序列化对象 } // Connect和SendFile函数无需改动,直接发送bufferMessage即可
接收方只需要用msgpack.decode(收到的消息)就能还原出包含Buffer的原始对象。
额外建议:大文件分片传输
虽然6MB不算特别大,但如果后续要传输更大的文件,建议把文件分成多个小分片(比如每个分片1MB),每个分片带上序号和总片数,接收方收到后再拼接还原。这样可以避开MQTT broker的消息大小限制,还能提升传输可靠性(某一片失败只需要重传该片)。
内容的提问来源于stack exchange,提问作者Thomas Petersen

