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

How to send JSON object with ArrayBuffer to websocket?

我只是想将文件上传到 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);
}

在我的服务器上(我使用的是 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);
        }
    });  
}

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

而且 Array.from(new Uint8Array(arrayBufer)) 似乎要花很多时间你认为 readAsDataURL 会更快吗?

顺便说一句,我能够通过 websocket 以 binayType="arraybuffer" 自行发送一个数组缓冲区,但是我如何在其中包含文件名??

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

在你的情况下,你可能需要一个更简单的方案,因为你有固定的字段:"name"、"type"、"size"、"data"。你已经知道他们的类型了。所以你可以只用长度值。这个想法是字节流中的每个字段都以包含值长度的一两个字节开始。因此,解析器将知道在下一个值之前要读取多少字节,从而不再需要分隔符。

假设您要对此进行编码:

{
  name: "file.txt",
  type: "text/plain",
  size: 4834,
  data: <an ArrayBuffer of length 4834>
}

"size" 字段实际上会有用,因为所有其他长度都适合一个字节,但内容长度不适合。

所以你用字节创建了一个新的 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

使用客户端 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)

现在你只需要填充缓冲区。

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

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

那么文件大小一定要写成合适的Int类型:

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

最后复制数据:

buffer.set(array, i + sizeLength) // array is your data