前端大文件的分片上传与下载

目前的 web 大文件上传和下载存在的缺陷

  • 文件在上传或者下载过程中遇到网络异常,或者浏览器卡死,下载页面刷新等情况后,只能重新开始上传或者下载,耗费大量时间,也就是不支持断点续传
  • 相同文件曾经上传或者下载过,二次操作还需要同样的上传或者下载,不支持秒传
  • 通常后端会限制单个文件上传大小,遇到超过上传阈值无法进行文件上传

web大文件切片上传

切片上传

切片上传是指将一个大文件切割为若干个小文件,分为多个请求依次上传,后台再将文件碎片拼接为一个完整的文件,即使某个碎片上传失败,也不会影响其它文件碎片,只需要重新上传失败的部分就可以了。而且多个请求一起发送切片文件,提高了传输速度的上限,同时后端在接收到所有切片文件后,再将所有切片合并为原始上传的大文件,这样就达到将一个大文件通过切片上传的方式快速上传到服务器。

要实现切片上传需要前后端一起配合,前后端需要各自解决几个技术难点。

前端:文件如何切片、生成前后端需要识别当前上传文件的唯一识别码hash值(md5计算)

后端:合并所有切片为真实大文件,提供普通文件上传相关接口

大概流程图如下所示

前端具体实现:

  • 文件切片,使用File.slice分割大文件为很多个Blob二进制大文件
  • 生成文件的唯一hash值,使用spark-md5开源库(耗时)
  • 所有切片上传完毕,通知后端合并文件

后端具体实现:主要提供3个接口

  • 提供给前端判断文件是否已经上传过(秒传功能)
  • 提供接收切片文件上传操作并保存碎片
  • 提供对同一个文件的所有切片合并的功能

测试

  • 切片文件上传后,检查是否合并成功?合并文件是否能正常查看?
  • 断点续传场景(文件上传一半,暂停&刷新页面&或后台服务停止),检查是否合并成功?合并文件是否能正常查看?确认是否真的断点续传,查看切片是否有重新上传(传一半,记录已经上传切片创建时间,待续传后,再次查看同一个切片文件的创建时间是否更新)
  • 是否能秒传

思考:切片上传可能存在的问题及优化空间?

1: 上传完所有切片后,在合并之前,某个切片丢失?如何处理?

根据实际对合并后的文件进行md5检测判断切片前后是否一致

2: 能否进一步加快整个大文件上传速度?

目前发现上传切片前文件hash值计算需要花费比较长的时间,hash计算时间长度由上传文件大小决定,因此有人提出了抽样文件hash计算。

原理如下:

  1. 文件切成2M的切片
  2. 第一个和最后一个切片全部内容,其他切片的取 首中尾三个地方各2个字节
  3. 这种抽样 hash的结果,就是文件存在,有小概率误判,但是如果不存在,是100%准的

总结:

通过大文件切片上传确实在实际应用中有一些优势,比如断点续传,秒传等,时间应用开发也不复杂,可以应用在实际业务中。目前社区已经存在一些成熟的大文件上传解决方案,如七牛SDK腾讯云SDK等,也许并不需要我们手动去实现一个简陋的大文件上传库,但是了解其原理还是十分有必要的。

三:web大文件切片下载

  • 服务端切片,前端分片下载
  • 切片如何存储,cookie,localStorage, 存放本地磁盘,内存,websql, indexDb
  • 切片合并
  • 是否支持断点续载,是否支持秒载

解决问题1: 需要客户端分段请求,服务端分段返回,需要使用HTTP Range

1、什么是Range?

当用户在听一首歌的时候,如果听到一半(网络下载了一半),网络断掉了,用户需要继续听的时候,文件服务器不支持断点的话,则用户需要重新下载这个文件。而Range支持的话,客户端应该记录了之前已经读取的文件范围,网络恢复之后,则向服务器发送读取剩余Range的请求,服务端只需要发送客户端请求的那部分内容,而不用整个文件发送回客户端,以此节省网络带宽。

2、HTTP1.1规范的Range是怎样一个约定呢?

HTTP Range 请求允许我们从服务器上只发送HTTP消息的一部分到客户端。

首先客户端会发起一个带有Range: bytes=0-xxx的请求,如果服务端支持 Range,则会在响应头中添加Accept-Ranges: bytes来表示支持 Range 的请求,之后客户端才可能发起带 Range 的请求。

服务端通过请求头中的Range: bytes=0-xxx 来判断是否是进行 Range 处理,如果这个值存在而且有效,则只发回请求的那部分文件内容,响应的状态码变成206,表示Partial Content,并设置Content-Range。如果无效,则返回416状态码,表明Request Range Not Satisfiable。如果请求头中不带 Range,那么服务端则正常响应,也不会设置 Content-Range 等。

Range的格式为:

Range:(unit=first byte pos)-[last byte pos]

即Range: 单位(如bytes)= 开始字节位置-结束字节位置。

我们来举个例子,假设我们开启了多线程下载,需要把一个5000byte的文件分为4个线程进行下载。

  • Range: bytes=0-1199 头1200个字节
  • Range: bytes=1200-2399 第二个1200字节
  • Range: bytes=2400-3599 第三个1200字节
  • Range: bytes=3600-5000 最后的1400字节

服务器给出响应:

第1个响应

  • Content-Length:1200
  • Content-Range:bytes 0-1199/5000

第2个响应

  • Content-Length:1200
  • Content-Range:bytes 1200-2399/5000

第3个响应

  • Content-Length:1200
  • Content-Range:bytes 2400-3599/5000

第4个响应

  • Content-Length:1400
  • Content-Range:bytes 3600-5000/5000

解决第二个问题,切片保存

使用indexDB存储httpRange分段下载的数据,格式blob, ArrayBuffer。

解决第三个问题,切片保存

从indexDB库里面拿到待合并所有ArrayBuffer,由于不能直接操作 ArrayBuffer 对象,所以我们需要先把ArrayBuffer 对象转换为 Uint8Array 对象

Uint8Array 对象是 ArrayBuffer 的一个数据类型(8 位不带符号整数),再将Uint8Array转换为blob,前端再利用blob API创建blob下载链接。

测试

  1. 切片文件下载后,检查是否合并成功?合并文件是否能正常查看?
  2. 断点续传场景(文件下载一半,暂停&刷新页面&或后台服务停止),检查是否合并成功?合并文件是否能正常查看?确认是否真的断点续下载,查看切片是否有重新下载?
  3. 是否能秒下载

讨论:

切片下载和单文件下载速度是否有优势?

四、其他领域应用场景

还有一些领域比如视频直播,使用传统的视频文件直接上传做不到直播效果,而通过下面讲到的切片分段上传就能实现效果,即前端大文件(视频)分片上传,后端通过ffmpeg将视频转为m3u8(多个ts文件),实现视频资源一边录制(一边上传)一边播放

视频切片传递靠的是流媒体传输协议,常见的流媒体传输协议如下:rtp,rtmp,flv,hls等等

hls (HTTP Live Streaming) 是一种基于HTTP的流媒体网络传输协议;工作原理是把视频文件拆分为一段段短小、有序的小文件,浏览器端通过一个特殊的 .m3u8 索引文件来进行视频的请求,而每次只请求其中的某一个小文件,不需要请求一个完整的视频文件。

讲人话就是,把一个大视频,切割成一个个小视频,并用一个小本本把这一个个小视频的名称和顺序给记录下来,浏览器对照着这个小本本上列的记录来发送请求。

举个栗子:假设 test.mp3 的总时长为 60s,我们可以将它切割成 12个小视频,每个小视频的时长为 5s

test.mp3 => [test1.mp3, test2.mp3, testn.mp3, ···, test12.mp3]

我们的小本本 .m3u8 内容为

test1.mp3 00:01~05:00

test2.mp3 05:01~10:00

test3.mp3 10:01~15:00

···

test12.mp3 55:01~60:00

那么,浏览器就会根据你当前视频的播放进度,去 .m3u8 内寻找对应的小视频,然后将它请求下来,再塞到 video 标签内,这样就实现了视频的切片播放。一般情况下,浏览器会提前把下一段小视频也给请求下来。下面是步骤

  1. 视频采用切片上传,通过调用后端切片上传接口进行上传
  2. 切片上传结束后通过合并切片接口进行合并成为完整的视频
  3. 调用ffmpeg工具进行视频转m3u8格式形成ts切片
  4. ts切片多线程上传至云服务器(测试就直接在测试服务器处理)

将 ts 视频切割成一个个小视频,我们需要选择是以时长切割,还是以大小切割,并且,切割完成后,我们会得到一个 .m3u8 的索引文件 ffmpeg -i 预热赛视频.ts -c copy -map 0 -f segment -segment_list chunk/index.m3u8 -segment_time 10 chunk/test-%04d.ts 5. 返回m3u8格式文件地址,前端集成播放器进行播放。

相关推荐
轻口味32 分钟前
【每日学点鸿蒙知识】AVCodec、SmartPerf工具、web组件加载、监听键盘的显示隐藏、Asset Store Kit
前端·华为·harmonyos
alikami35 分钟前
【若依】用 post 请求传 json 格式的数据下载文件
前端·javascript·json
吃杠碰小鸡1 小时前
lodash常用函数
前端·javascript
emoji1111111 小时前
前端对页面数据进行缓存
开发语言·前端·javascript
泰伦闲鱼1 小时前
nestjs:GET REQUEST 缓存问题
服务器·前端·缓存·node.js·nestjs
m0_748250031 小时前
Web 第一次作业 初探html 使用VSCode工具开发
前端·html
一个处女座的程序猿O(∩_∩)O2 小时前
vue3 如何使用 mounted
前端·javascript·vue.js
m0_748235952 小时前
web复习(三)
前端
AiFlutter2 小时前
Flutter-底部分享弹窗(showModalBottomSheet)
java·前端·flutter
麦兜*2 小时前
轮播图带详情插件、uniApp插件
前端·javascript·uni-app·vue