分块上传多个大文件,这里记录一下我的开发历程。
前端用 websocket , websocket server用的是php的swoole扩展。
几经探索,发现 简单、靠谱、占内存少 的大致流程是这样的:
- 读取数据块->发送数据块->等待服务端保存完毕->读取下一个数据块->发送数据块->等待服务端保存完毕->…
- 文件a, 读取最后一个数据块->发送数据块->等待服务端保存完毕。
- 承上启下的重头戏:
- 浏览器端, 文件b, 读取第1个数据块->发送数据块->等待服务端保存完毕->读取下一个数据块->发送数据块->等待服务端保存完毕->…
- 服务端,worker主进程派发任务给taskWorker进程,由该进程将文件a的各个数据块按顺序合并为大文件。
每一个文件的准备工作【读取数据块之前】:
- 算出:需要切成几块;
- 算出:每一块的大小及其序号;
- 加上文件名、文件大小、文件类型,以json格式一并发给服务端,这个是为了
- 保存的是第几块的数据,还多少M需要上传;
- 接收到的二进制数据,是否跟当时读取的大小一样,万一,由于网络原因,没有传全,可以丢弃,避免文件“变质”
服务端保存完这些json数据,告诉浏览器, “hi, 我保存好啦,你可以开始发送这个文件的数据块啦”,浏览器才开始吭吭哧哧地读取第1个文件的第1个数据块。
服务端保存了这个数据块成功了,更新redis中的信息,包括:
- 已上传的大小,
- 剩余大小
- 下一个数据块的序号
- 小文件的路径+文件名(如果是放在磁盘中)或者文件的fileId(如果用的MongoDB gridFs), 这是日后合并小文件的依据。
- 这个文件是否传了全部的数据块, isFinish,
$isFinish = $fileSize === $totalUploadSize;
如果为false, 则:
- 告诉浏览器, :(例句)”hi, 我保存的是第20块的数据,还有57M需要上传”。
- 然后,浏览器开始读取这个文件的下一个数据块。
否则:
- 告诉浏览器, :例句)”hi, 我保存的是第29块的数据,还有0Bit需要上传”。
- 然后,浏览器开始欢天喜地读取下一个文件的第1个数据块。
为了测试分块上传后,文件是否还是原来的样子,我特意挑选视频(至少100M的那种)作为测试材料。
使用html api提供的 File reader 对象 , 读取文件的方法:
- readAsText 方法 用于读取文本文件—限制了文件类型,没有尝试,直接放弃。
- readAsDataURL 方法 返回一个data URL,它的作用基本上是将文件数据进行Base64编码。你可以将返回值设为图像的src属性。—-没有尝试,直接放弃
- readAsBinaryString 方法 可以读取任意类型的文件,而不仅仅是文本文件。这个方法与XMLHttpRequest.sendAsBinary方法结合使用,就可以使用JavaScript上传任意文件到服务器。—–尝试了,踩到坑了。。。
- readAsArrayBuffer 方法读取文件,返回一个类型化数组(ArrayBuffer),即固定长度的二进制缓存数据。—尝试了,切多少,读取到的数据就是多少,服务端按顺序合并为大文件后,体积不变,可以播放。
一开始,也不懂,跟着网上的大佬使用readAsArrayBuffer。
var reader = new FileReader(); var blob = reader.slice(startSize, endSize); reader.readAsArrayBuffer(blob); reader.onload = function(e) { var arrayBuffer = e.target.result; }
如上,读取是异步非阻塞的,好有诱惑力。
我禁不住诱惑,在循环计算每一个数据块的startSize和endSize时,就运行读取操作,由于读取是异步非阻塞的->读取数据块是争先恐后的->哪一块先读取成功是不确定的。
结果是,
- 我使用谷歌浏览器开发者版,标签页还常常卡死,控制台中显示javascript占用了几百兆的内存!!!
- 错上加错: 先发送json, 紧接着发送二进制数据,居然可以还原大文件,其实是因为,我的websocket server是以同步阻塞(即便使用协程也不过是半阻塞)的方式处理数据!
为了同时发json和文件数据,我尝试了readAsBinaryString….我发现,切 5M 的数据,读取到的数据不止5M。服务端按顺序合并为大文件后,体积大了好多,无法播放。。。唉,都是泪呀。
又辗转回readAsArrayBuffer。
下一篇说说, 如何用js获取文件的md5值 , 这样,可以避免上传名称不同、文件内容相同的文件。
关于 断点续传 ,我还在路上。。。