如何将带有ArrayBuffer的JSON对象发送到websocket?

Yaa*_*777 5 javascript arrays json node.js fileapi

我正在尝试简单地将文件上传到node.js服务器。

为此,我使用了文件API和readAsArrayBuffer。这是在触发输入文件“ change”事件以及一些hepler函数时调用的代码(并且我正在使用COBY库进行套接字发送和其他事件设置,binaryType设置为arraybuffer):

COBY.events = {
  "file": (e) => {
       files = Array.from(e.target.files);
       startReadingFile(files[0]);
   }
};

function startReadingFile(file) {
   readFileFrom(file, 0, chunkSize, (array, r) => {
       COBY.socketSend({"start uploading a file": {
           name:file.name,
           type:file.type,
           size:file.size,
           data:(array)
       }});
       console.log("didnt something?", r, Array.from(r));
   });
}

function readFileFrom(file, start, end, callback) {
   var sliced = file.slice(start, end);
   var reader = new FileReader();
   reader.onload = (event) => {
       result = (event.target.result);
       var arr = Array.from(new Uint8Array(result));
       if(callback && callback.constructor == Function) {
           currentPosition = end;
           callback(arr, result);
       }
   }
   reader.readAsArrayBuffer(sliced);
}
Run Code Online (Sandbox Code Playgroud)

在我的服务器上(我使用的是coby-node库,它是COBY客户端库的node.js版本):

var coby = require("coby-node");
var fs = require("fs");
var files = {};
var kilobyte = 1024;

function makeBigFile(name, number) {
    var test = fs.createWriteStream("./" + name, {flags: "w+"});
    console.log("OK?",name);
    [...Array(number)].forEach((x, i) => test.write(i+"\n"));
}

//makeBigFile("OKthere.txt", 12356);
coby.startAdanServer({
    onOpen:(cs) => {
        console.log("something just connected! Let's send it something");
     //   cs.send({"Whoa man !":1234});
        cs.send({asdf :3456789});
    },

    onAdanMessage: (cs, msg) => {
     //   console.log("HMM weird just got this message...", msg);
    },

    adanFunctions: {
        "do something important": (cs, data) => {
            console.log("I just got some message:", data);
            console.log(cs.server.broadcast);
            cs.server.broadcast({"look out":"here I am"}, {
                current: cs
            });

            cs.send({message:"OK I did it I think"});
        },
        "start uploading a file": (cs, data) => {
            if(data.data && data.data.constructor == Array) {
                var name = data["name"]
                files[name] = {
                    totalSize:data.size,
                    downloadedSize:0
                };

                files[name]["handler"] = fs.createWriteStream("./" + data.name, {
                    flags: "w+"
                });

                files[name]["handler"].on("error", (err) => {
                    console.log("OY vay", err);
                });
                cs.send({"ok dude I need more": {
                    name:name,
                    bytePositionToWriteTo:0,
                    totalLength:files[name]["totalSize"]
                }});
            }
        },
        "continue uploading file": (cs, data) => {

            var name = data.name;
            if(files[name]) {
                var handler = files[name]["handler"];

                var uint = Uint8Array.from(data.bufferArray);
                var myBuffer = Buffer.from(uint.buffer);
                var start = data.startPosition || 0,
                    end = myBuffer.byteLength + start;

                files[name].downloadedSize += myBuffer.byteLength;


                if(files[name].downloadedSize < files[name]["totalSize"]) {

                    cs.send({"ok dude I need more": {
                        name:name,
                        bytePositionToWriteTo:files[name].downloadedSize,
                        totalLength:files[name]["totalSize"]
                    }});
                    try {
                        handler.write(myBuffer);
                    } catch(e) {
                        console.log("writing error: ", e);
                    }
                } else {
                    end = files[name]["totalSize"];
                    handler.write(myBuffer);
                    console.log("finished, I think?");
                    console.log(files[name].downloadedSize, "total: ", files[name]["totalSize"]);
                    console.log("   start: ", start, "end: ", end);
                }


            }
        }
    },
    intervalLength:1000
});

function startUnity() {
    coby.cmd(`./MyUnity/Editor/Unity.exe -batchmode -quit -projectPath "./MyUnity/totally empty" -executeMethod COBY.Start -logfile ./new123folder/wow.txt`, {
        onData:(data) => {
            console.log(data);
        },
        onError:(data) => {
            console.log(data);
        },
        onExit:(exitCode) => {
            console.log("exitted with code: " + exitCode);
        },
        onFail:(msg) => {
            console.log(msg);
        }
    });  
}
Run Code Online (Sandbox Code Playgroud)

到目前为止,实际上已经上传了一个文件,您可以使用进行测试npm install coby-node,但是它要花更多的时间,因为我是JSON.stringifing an Array.from(new Uint8Array(/* the ArrayBuffer result */)),然后在服务器端我要重新进行JSON解析,但是我该如何发送实际的ArrayBuffer到websocket吗?我想将arraybuffer 以及文件名和其他数据一起发送,所以我想将其包含在JSON对象中,但是当我使用JSON.stringify(/ ArrayBuffer /)时,结果始终是[],而IDK如何发送带有我自己的数据的ArrayBuffer?

您似乎还需要花费很多时间使用Array.from(new Uint8Array(arrayBufer)),您认为readAsDataURL会更快吗?

我可以通过ITSELF通过websocket使用binayType =“ arraybuffer”发送一个arraybuffer,但是我如何在其中包含文件名?

Tou*_*ffy 5

所以你想发送结构化的二进制数据。大多数通用二进制格式使用类型-长度-值编码(ASN.1Nimn就是很好的例子)。

就您而言,您可能需要一个更简单的方案,因为您有固定字段:“名称”、“类型”、“大小”、“数据”。您已经知道它们的类型。所以你只需要长度值就可以得到。这个想法是,字节流中的每个字段都以包含值长度的一个或两个字节开始。因此,解析器将知道在下一个值之前要读取多少字节,从而无需使用分隔符。

假设您想对此进行编码:

{
  name: "file.txt",
  type: "text/plain",
  size: 4834,
  data: <an ArrayBuffer of length 4834>
}
Run Code Online (Sandbox Code Playgroud)

“大小”字段实际上很有用,因为所有其他长度都适合单个字节,但内容长度不适合。

所以你用字节创建一个新的ArrayBuffer:

08 (length of the file name)
66 69 6c 65 2e 74 78 74 (the string "file.txt")
0a (length of the content type)
74 65 78 74 2f 70 6c 61 69 6e (the string "text/plain")
02 (you need two bytes to represent the size)
12 e2 (the size, 4834 as an unsigned int16)
... and finally the bytes of the content
Run Code Online (Sandbox Code Playgroud)

使用客户端 JavaScript 实现这一点仅比使用 Node.js 缓冲区稍微困难一些。首先,您需要计算需要发送的 ArrayBuffer 的总长度。

// this gives you how many bytes are needed to represent the size
let sizeLength = 1
if (file.size > 0xffff)
  sizeLength = 4
else if (file.size > 0xff)
  sizeLength = 2

const utf8 = new TextEncoder()
const nameBuffer = utf8.encode(file.name)
const typeBuffer = utf8.encode(type)

const length = file.size + sizeLength
  + nameBuffer.length + typeBuffer.length + 3

const buffer = new Uint8Array(length)
Run Code Online (Sandbox Code Playgroud)

现在您只需填充缓冲区即可。

让我们从长度开始并复制字符串:

let i = 0
buffer[i] = nameBuffer.length
buffer.set(i += 1, nameBuffer)
buffer[i += nameBuffer.length] = typeBuffer.length
buffer.set(i += 1, typeBuffer)
buffer[i += typeBuffer.length] = sizeLength
Run Code Online (Sandbox Code Playgroud)

那么文件大小必须写成适当的Int类型:

const sizeView = new DataView(buffer)
sizeView[`setUInt${sizeLength*8}`](i += 1, file.size)
Run Code Online (Sandbox Code Playgroud)

最后复制数据:

buffer.set(array, i + sizeLength) // array is your data
Run Code Online (Sandbox Code Playgroud)