使用WebSocket进行大文件上传

40

我正在尝试使用WebSocket API上传大文件(至少500MB,最好能够上传几GB)。问题在于我无法想出如何编写“发送文件的这一部分,释放已使用的资源,然后重复”。我希望能够避免使用Flash/Silverlight等技术。

目前,我正在使用以下代码:

function FileSlicer(file) {
    // randomly picked 1MB slices,
    // I don't think this size is important for this experiment
    this.sliceSize = 1024*1024;  
    this.slices = Math.ceil(file.size / this.sliceSize);

    this.currentSlice = 0;

    this.getNextSlice = function() {
        var start = this.currentSlice * this.sliceSize;
        var end = Math.min((this.currentSlice+1) * this.sliceSize, file.size);
        ++this.currentSlice;

        return file.slice(start, end);
    }
}

接下来,我会使用以下方法上传:
function Uploader(url, file) {
    var fs = new FileSlicer(file);
    var socket = new WebSocket(url);

    socket.onopen = function() {
        for(var i = 0; i < fs.slices; ++i) {
            socket.send(fs.getNextSlice()); // see below
        }
    }
}

基本上这个函数会立即返回,bufferedAmount不会改变(仍然是0),并且它会在尝试发送之前一直迭代并将所有的数据片段添加到队列中。没有socket.afterSend让我能够正确地排队,这就是我卡住的地方。


1
假设我不想依赖Flash/Silverlight,我应该使用什么?XMLHttpRequest?我认为WebSockets的开销更小。 - Vlad Ciobanu
2
Websockets对于双向通信的开销较小,但上传文件只需将文件作为POST请求发送到服务器即可。浏览器在这方面非常擅长,而且大文件的开销几乎可以忽略不计。 - Denys Séguret
1
你最终让它工作了吗? - user1382306
2
是的,但我决定使用简单的Ajax调用而不是WebSockets。实现很简单,你只需要在前一个完成后将下一个send()排队即可。 - Vlad Ciobanu
@dystroy,Websockets的优点在于您拥有更多的控制权。比如下载状态(x%完成)条。 - Pacerier
显示剩余3条评论
6个回答

16

使用Web Workers处理大文件,而不是在主线程中处理,并使用file.slice()上传文件数据的块。

这篇文章可以帮助你在worker中处理大文件。在主线程中将XHR发送改为Websocket。

//Messages from worker
function onmessage(blobOrFile) {
 ws.send(blobOrFile);
}

//construct file on server side based on blob or chunk information.

4
你的解决方案真是妙极了。我试过了,对于1GB以上的大文件大小完美适用。我把它作为websocket的单元测试的一部分来使用,但如果有人想要重用它,源可以在这里找到 https://github.com/drogatkin/TJWS2/tree/master/1.x/test/html-js 目前唯一的缺点是所有发送都是异步执行的,因此你无法控制文件何时完全发送完成。 - Singagirl
WS服务器可以在文件处理完成后简单地发送一条消息。它甚至可以在处理过程中发送消息,以在客户端上实现进度条,因为主线程不会被工作线程阻塞。 - Dominic Cerisano
1
XHR不在主线程上运行(除非明确设置为同步运行),因此线程不是使用Web Worker的理由。区别在于XHR在窗口上下文中,如果选项卡关闭,则会停止运行,而Web Worker可以继续运行直到浏览器进程终止。您可以在Web Worker中使用XHR和WebSocket。 - Daniel F

11

我认为send()方法是异步的,因此它会立即返回。如果要使其排队,则需要服务器在上传每个片段后向客户端发送一条消息;然后客户端可以决定是否需要向服务器发送下一个片段或“上传完成”消息。

使用XMLHttpRequest(2)可能更容易实现此类功能;它内置了回调支持,并且比WebSocket API得到更广泛的支持。


5
为了序列化这个操作,您需要服务器每次接收并写入一个切片(或发生错误)时向您发送信号,这样您可以在响应onmessage事件时发送下一个切片,就像这样:
function Uploader(url, file) {
    var fs = new FileSlicer(file);
    var socket = new WebSocket(url);

    socket.onopen = function() {
       socket.send(fs.getNextSlice());
    }
    socket.onmessage = function(ms){
        if(ms.data=="ok"){
           fs.slices--;
           if(fs.slices>0) socket.send(fs.getNextSlice());
        }else{
           // handle the error code here.
        }
    }
}

7
你把FileSlicer描述为一个标准库,但我在任何地方都找不到它。我猜那应该是你自己创建的东西? - CWSpear
3
@CWSpear,我猜这是来自问题。 - roshnet

3

编辑: 自从这个回答被制作以来,网络世界、浏览器、防火墙、代理等发生了很多变化。现在,使用 WebSocket 发送文件可以高效地完成,特别是在局域网中。


翻译后的文本:

Websockets非常适合双向通信,特别是当你有兴趣从服务器推送信息(最好是小型信息)时。它们充当双向套接字(因此得名)。

在这种情况下,Websockets似乎不是正确的技术选择。特别是考虑到使用它们会导致一些代理、浏览器(如IE)甚至防火墙不兼容。

另一方面,上传文件只需向服务器发送一个带有文件的POST请求即可。浏览器在这方面做得很好,对于大文件的开销几乎可以忽略不计。因此不要使用Websockets完成该任务。


39
您的信息已经过时。标准化WebSocket协议(IETF 6455)支持发送和接收直接的二进制数据(ArrayBuffer和Blob)。您所想到的是旧的Hixie协议,它只支持发送UTF-8数据(这要求对二进制数据进行编码)。此外,WebSocket协议的IETF 6455版本专门设计为与现有代理和防火墙进行互操作。我已经广泛地使用WebSockets,没有看到您所暗示的问题。请举出证据表明存在广泛的问题。 - kanaka
3
我不会说在IETF 6455上你错了(尤其是考虑到有关此主题的搜索结果导致您最近努力与websockify中的这个新规范兼容),这些信息是受欢迎的,但世界并未完全转变。参见此代理问题。此外,在此页面上查找“浏览器支持”。基本上没有理由使用WebSockets上传文件。 - Denys Séguret
5
如果您删除第二段的全部内容,那么我对您的答案没有任何问题,但第二段大部分是错误的。JSON只是一种文本序列化/编码方法,与WebSockets没有直接关系。Base64约比原始数据多33%,但它并不是CPU密集型的(即使在JavaScript中直接执行)。确实存在某些有缺陷的中间件,但这不是一个普遍存在的问题。仅在iOS Safari上仍然使用Hixie协议,而Chrome、Firefox、IE 10和Opera(虽然已禁用)都使用IETF 6455协议。 - kanaka
4
我从未谈论过CPU。我知道你是Websockets新版本的能干推广者,但让OP(只是想上传文件)认为现在没有兼容性问题是不公平的(例如,我提到了代理)。 - Denys Séguret
5
dystroy,请不要替我加上话。你的答案没问题,但是你的推理有误。我没有说过或者暗示了WebSockets是大文件上传的更好选择。如果你解决了这些问题,我会取消我的踩。你的编辑并没有改善情况。而“katana”是谁? - kanaka

3

0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接