随着互联网技术的不断发展, 通过网络Web进行文件的上传拥有越来越多的应用需求. 其中, 在大容量文件的上传中, 常常因资源过大导致带宽资源紧张、浏览器崩溃或加载超时等问题, 大大降低了用户体验. 针对大文件上传的众多限制问题, 本文设计并实现了基于Node.js的大文件上传系统, 采用自适应分片结合并发上传的方法, 有效地缩短了大文件上传时间. 同时结合element-ui 框架, 利用进度条实时展示上传进度, 具备良好的交互性能.
As Internet technology continues to evolve, the application demand for file uploads via the Web is on the rise. Nevertheless, large file uploads are often faced with bandwidth resource constraints, browser crashes, or loading timeouts due to excessive resources, which greatly reduces user experience. In view of the many limitations on large file uploads, this study designs and implements a large file upload system based on Node.js, combining adaptive slicing with concurrent upload to effectively shorten the upload time of such files. It also integrates the element-ui framework to show the upload progress in real time with a progress bar, and hence comes the favorable interactive performance.
在计算机网络技术迅猛发展的今天, 文件的上传是一个重要的应用交互场景. 对于普通的图片或word文档等几十KB或者几MB的文件上传, 使用Web的组件即可完成流畅的上传功能. 通常的文件上传是一次性获取整个文件上传, 传输过程简单: 第1步获取本地文件, 第2步将文件转换成字节传输, 第3步后端接收按顺序接收字节到内存中, 最后将接收完的字节保存为文件[
针对以上问题, 本文基于Node.js边读边写的流模式传输, 采用HTML5的File API对上传的大文件进行分片处理, 通过上传速率动态调整分片大小, 同时充分利用带宽, 结合多并发上传进一步缩短上传时间, 在服务端检验所有分片文件上传完整后, 再进行文件的合并, 有效的提高了大文件的上传速率, 减少了用户的等待时长.
目前, 基于HTTP协议的文件上传方式有以下几种:
(1)表单上传
这是Web开发中最常见的上传方式, 使用Form表单的input[type=“input”]打开文件选择界面, 通过POST方法向指定资源提交表单数据[
(2)无刷新的Ajax上传
区别于表单上传, 使用Ajax的异步上传, 在提交表单数据不需要刷新和跳转页面. 提交数据时, 可以使用FormData对象模拟表单提交, 发送表单的二进制文件内容, 通过XMLHttpRequest实例将参数提交至服务端[
(3) Flash上传
在传统表单的上传功能基础上, Flash上传方式在不刷新网页的条件下, 支持多个文件批量上传以及显示上传进度等功能. 它采用Flash作为中间代理层与服务端进行通信, 以此为基础的SWFUpload、Plupload及Uploadify等文件上传插件被广泛应用[
(4)第三方组件上传/插件上传
插件技术主要包括ActiveX、Applet 等, 虽然可能受限于浏览器的安全性设置, 但在学校及企业内部网站环境中有一定的使用价值[
Node.js基于事件驱动的非阻塞I/O模型, 旨在支持能够管理大量并发请求的轻量级服务器的简单而快速的开发[
在HTML5中提供了一种通过File API规范与本地文件进行交互的标准方法, 它的主要作用是将本地文件以文件对象的形式提供给 Web 应用程序进行访问, 为浏览器端应用程序的开发提供了无限可能[
(1) FileList是一个由File对象组成的类数组对象.
(2) File是FileList中的一个对象, 包含文件名称(name)、大小(size)、类别(type)、修改时间(lastModified-Date)等基本信息.
(3) FileReader用来读取文件的API, 将文件读取到内存中, 提供将文件读取为文本、base64图片编码、Buffer数据类型、二进制字符串等方法, 可以实现预览图片、计算MD5等等操作.
(4) Blob是一个二进制数据, File对象就继承自Blob对象. 通过slice方法, 可以使二进制数据按照字节分块, 返回的对象中包含了源 Blob 对象中指定范围内的数据[
对分片文件的标识也是整个文件处理过程中必不可少的一部分. 异步提交的数据中必须包含文件的唯一标识来确认文件分片的顺序, 验证是否上传完毕[
Node.js和JavaScript都是单线程编程模型, HTML5的新特性Web worker为浏览器实现多线程操作提供了支持. 在文件上传过程中, 多线程操作显然比单线程更具有优势, 且不容易造成阻塞. Web worker允许在Web程序中并发执行多个JavaScript 脚本, 每个脚本执行过程都作为一个线程, 各个线程之间彼此独立, 由 JavaScript 引擎负责管理[
基于对大文件上传常用方法与关键技术的研究, 本文设计并实现了完整的前后端大文件上传系统. 该系统基于HTTP协议, 利用HTML5的File API对需要上传的目标大文件进行分片处理. 同时, 充分发挥CPU多核的性能, 创建Web worker线程计算和处理分片的文件, 避免主线程阻塞. 通过对分片文件的MD5校验及标记, 增加文件传输的安全性. 在此基础上, 通过自适应分片结合多并发上传进行优化, 提高了传输速率. 在服务端, 服务器接收前端传输的分片文件, 按分片顺序依次存储, 当收到前端的合并请求, 服务端使用流模式将收到的所有文件切片进行合并. 此外, 在上传过的切片列表中进行查询比对, 对已经上传过的相同文件无需再传, 避免重复上传. 整个系统的流程示意图如
为了使服务端对已上传的内容进行识别, 必须要生成文件和切片的 hash作为校验. 这里使用Web worker为JavaScript创造多线程环境, 调用Worker()构造函数, 新建一个名为hash的worker线程. 在主线程调用worker线程, 通过postMessage()函数传入文件内容切片后得到的数组fileChunkList, worker线程利用 FileReader 读取每个切片的 ArrayBuffer 并不断传入 Spark-md5 中, 每计算完一个切片通过 postMessage 向主线程发送一个进度事件. 主线程通过onMessage函数监听子线程消息, 待全部文件读取完成后, 子线程将最终的 hash 发送给主线程. 整个流程如
系统流程图
Web worker示意图
在实际的应用场景中, 所需要上传的文件大小往往是不固定的, 而分块大小对文件传输有较大影响[
本文参照文献[
切片调整部分关键代码摘录如下:
while (cur < fileSize) {
const chunk = file.slice(cur, cur + offset);
cur += offset;
const chunkName = this.container.hash + “-” + count;
const form = new FormData();
form.append(“chunk”, chunk);
form.append(“hash”, chunkName);
form.append(“filename”, this.container.file.name);
form.append(“fileHash”, this.container.hash);
let start = new Date().getTime();
await request({ url: ‘/upload’, data: form })
const now = new Date().getTime();
//获取文件上传过程用时
const time = ((now–start)/1000).toFixed(2);
let
//按当前速率调整切片大小
offset = parseInt(offset/
count++;
}
为充分利用网络带宽, 采用多并发的方式进行文件上传. 并发上传的并发数受浏览器支持的最大并发数限制, 超过这个值, 执行过程中的并发请求需要等待. 文献[
多并发上传结合自适应分片算法的流程示意图如
流程示意图
对前端传递的FormData, 服务端使用multiparty包进行处理, 创建target文件夹作为文件上传的存储目录. 前端在发送每个切片时都携带了唯一标识hash, 服务端将处理后的分片对象从临时路径移动到切片文件夹中.
服务端接收到来自前端的合并请求后, 对切片所在文件夹下的所有切片进行合并. 首先采用sort()方法根据切片的下标进行排序, 避免从目录读取的文件顺序发生错乱[
文件hash值与文件后缀作为目录, 使用fse.existsSync检测文件目录是否存在, 如果存在, 则将标志位置为false, 不需要再次上传. 如果不存在, 则将标志位置为true. 在此基础上, 文件秒传的实现只需要在用户选择上传已存在的相同资源时, 直接提示上传成功. 在前文服务端验证hash的基础上, 如果发现hash相同的文件, 说明该文件资源已经上传, 可以直接返回上传成功.
本文使用文献[
在文件上传的测试过程中, 针对同一文件, 再次上传时, 经过对MD5码的校验, 可以直接实现秒传, 系统弹窗提示上传成功.
实验结果(s)
文件大小 | 原始方法用时 | 平均时间 | 改进方法用时 | 平均时间 |
194 MB | 10.668 | 7.127 | ||
10.011 | 9.90 | 7.703 | 7.46 | |
9.021 | 7.539 | |||
576 MB | 28.273 | 23.828 | ||
26.293 | 28.07 | 21.163 | 22.10 | |
29.640 | 21.302 | |||
1.19 GB | 53.663 | 47.379 | ||
57.858 | 56.35 | 48.341 | 47.95 | |
57.531 | 48.131 |
本文研究并介绍了常用的大文件上传方法以及存在的问题, 对本系统所用到的关键技术Node.js及File API进行了阐述, 通过对前后端上传过程的具体研究, 实现了基于Node.js的大文件上传系统, 通过对多并发上传与自适应切片相结合的算法, 实现了更具有灵活性和更高传输效率的大文件上传. 同时针对大文件的MD5标识计算, 利用Web worker多线程计算的方式有效地避免主线程的卡顿. 该系统灵活度高, 适用性强, 能够在文件上传过程中提高上传效率, 提升用户体验.
张立伟, 李涪帆. 基于java语言的大文件分片传输. 通讯世界, 2020, 27(6): 51, 53.
周明俊. 基于PHP大文件上传的研究和设计. 福建电脑, 2009, 25(4): 147–148, doi: 10.3969/j.issn.1673-2782.2009.04.095.
王建斌, 赵靓. Web上传文件的三种解决方案. 计算机与信息技术, 2011, 19(S1): 65–68.
刘耀钦. 利用HTML5拖放技术实现多文件异步上传. 四川理工学院学报(自然科学版), 2015, 28(1): 17–20, 30, doi: 10.11863/j.suse.2015.01.05.
陈涛, 黄艳峰. Java Web开发中文件上传方法研究与实现. 电脑知识与技术, 2016, 12(11): 48–49, 52, doi: 10.14004/j.cnki.ckt.2016.1265.
阮晓龙, 李朋楠. 基于Web的大文件高效上传方法. 计算机系统应用, 2020, 29(3): 234–239.
刘苡, 吴刚. 基于. Net的Web应用系统中大文件传输方案的研究. 微型电脑应用, 2012, 28(7): 27–30, doi: 10.3969/j.issn.1007-757X.2012.07.008.
任强, 车鹏飞. 基于Node. js平台物联网Web服务的设计与实现. 现代科学仪器, 2020, (1): 29–33.
Tilkov S, Vinoski S. Node. js: Using JavaScript to build high-performance network programs. IEEE Internet Computing, 2010, 14(6): 80–83.
张煜. 一种使用Node. js构建的分布式数据流日志服务系统. 计算机系统应用, 2013, 22(2): 68–71.
胡渝苹. 文件秒传系统在云存储环境下的设计与实现. 计算机应用与软件, 2016, 33(4): 329–333.
王莉敏, 梁正和, 段全锋. 基于HTML5大文件断点续传的实现方案. 计算机与现代化, 2016, (3): 91–95, doi: 10.3969/j.issn.1006-2475.2016.03.018.
叶文全. 基于HTML5、AJAX的文件分割上传与加密存储研究. 三明学院学报, 2018, 35(4): 60–66.
任双君, 周旭, 任勇毛, 等. 基于HTML5的浏览器端多线程下载技术. 计算机系统应用, 2017, 26(11): 11–18, doi: 10.15888/j.cnki.csa.006091.
黎苑文, 程明智, 徐秀花, 等. 断点续传及多线程机制在远程传版中的应用研究. 北京印刷学院学报, 2012, 20(6): 53-56, doi: 10.3969/j.issn.1004-8626.2012.06.019.
邓彬, 成卫青. 基于改进慢启动算法的大文件快速传输. 计算机应用研究, 2020, 37(3): 860–863, doi: 10.19734/j.issn.1001-3695.2018.09.0645.
黎国华. 基于历史连接参数的网络拥塞改进算法研究. 网络安全技术与应用, 2020, (5): 57–58.
邹鹤敏, 黄海于. 大文件分块上传和下载软件的设计与实现. 电子技术应用, 2013, 39(8): 137–139, doi: 10.3969/j.issn.0258-7998.2013.08.040.
刘昊, 杨世平. 基于新型分割技术的文件云存储. 贵州大学学报(自然科学版), 2017, 34(2): 76–79, doi: 10.15958/j.cnki.gdxbzrb.2017.02.16.