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

Posted

技术标签:

【中文标题】如何将带有 ArrayBuffer 的 JSON 对象发送到 websocket?【英文标题】:How to send JSON object with ArrayBuffer to websocket? 【发布时间】:2019-07-16 03:57:43 【问题描述】:

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

为此,我使用了文件 API 和 readAsArrayBuffer。这是触发输入文件“更改”事件时调用的代码,以及一些 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?我想将arraybuffer 连同文件的名称和其他数据一起发送,所以我想将它包含在一个JSON对象中,但是当我 JSON.stringify(/an ArrayBuffer em>/) 结果总是[],IDK怎么用我自己的数据发送一个ArrayBuffer???

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

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

【问题讨论】:

【参考方案1】:

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

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

假设您想对此进行编码:


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

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

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

【讨论】:

您好,感谢您的回答,但您能给我一些示例代码,说明如何使用特定数据创建新的 ArrayBuffer 吗?我什至不确定如何修改现有的 所以最终它实际上存储在 Uint8Array 变量中,而不是实际的数组缓冲区中?您可以输入代码以通过套接字发送它,尤其是部分吗?那部分我真的很想知道,如何分部分发送文件,以及如何使用 readAsArrayBuffer 或 readAsBinaryText 或其他方式获取缓冲区数据?如果这一切都是实时完成的,这种方法是否会减慢发送时间? 1. Uint8Array 只是 ArrayBuffer 的一个薄包装器,它允许您读取和写入其值(您不能直接使用 ArrayBuffer 执行此操作,请参阅我必须如何使用 DataView)。您不能在浏览器中创建裸 ArrayBuffer,@Yaakov5777。 2.您应该一次将问题限制在一个问题上。老实说,我认为 WebSocket 是向服务器发送文件的错误工具(使用简单的fetch 和 File 对象作为主体)但是如果你真的想降低层次,你应该拆分你的问题并有一个结构化的二进制编码和一种用于通过 WebSocket 进行流式传输的编码。 我的问题是一个问题:如何最好地使用 websockets 上传文件。为了做到这一点,我正在尝试使用 ArrayBuffer,因为它似乎是 file.readAs 的 4 种方式中最快的方法...... 拆分 arraylist 的问题只是如何发送文件的细节在使用arraylist的不同部分,它是同一个问题的一部分,我只需要使用arraylist分部分发送文件。您认为我应该如何将多个文件上传到 node.js 服务器?我怎么能准确地使用 fetch 呢,我没有从服务器上获取文件,我是从客户端上传的

以上是关于如何将带有 ArrayBuffer 的 JSON 对象发送到 websocket?的主要内容,如果未能解决你的问题,请参考以下文章

axios 如何将 blob 与 arraybuffer 处理为 responseType?

如何在ArrayBuffer对象与字符串之间进行互相转换

在JavaScript中如何将ArrayBuffer转换为Float64Array?

如何通过Websocket将arraybuffer作为二进制发送?

js 怎么解析ArrayBuffer

如何在 JS 中从 ArrayBuffer 写入文件