XMLHttpRequest实现HTTP协议下文件上传断点续传

一、网盘割据的时代

不知大家有没有观察过,在秋季,也就是眼下这个时间,当阵风挂起的时候,地上的落叶就会以一个接一个,翻滚着一同被吹走,这就是“跟风”。老祖宗确实很有智慧,造出来的词语源于生活,又高于生活。

眼下,又是另一波跟风之势——“网盘”,犹如当年团购一样。不过,网盘还是有一定的技术和其他成本,因此,还不像团购那样“落叶漫天飞舞”的状态。但是,各大公司相继介入,可谓又是另外一场群雄之战。

秋风扫落叶 一吹一大片

我并不是专业的产品人,也不是公司的决策者,因此,面对一些纷杂繁芜的现象,自己无心去深入,也不会去评价。立志做个技术人,因此,我所关心的可能就是技术实现,至于孰对孰错,恩怨情仇,公关伎俩,生死天命等一概作云烟从眼前飘过。

网盘中有个很重要的,可以说是核心的功能,就是文件上传,So,本文就来说说这个上传,如何在HTTP协议下实现文件的断点续传呢?

二、文件断点续传的实现

目前从实用技术角度讲,文件上传的断点续传实现主要是借助客户端,例如,我们首次进入微云,会看到下图所示的“控件安装”提示:
微云控件安装的提示

百度网盘似乎有文件上传暂停的功能,这似乎是借助swfUpload实现的。

也就是,这些带续传功能的上传都不是使用HTTP协议实现的,也就是不是传统的网页技术(HTML+CSS+JS)实现的。

然,times are changing, 事物发展,时代变化。以前的一些所谓的“不能”、“不可能”都将成为过去。

上月一篇独苗文章介绍了XMLHttpRequest level 2(下简称Ajax 2.0)中的一些支持的数据格式,如果稍微关注,应该知道,Ajax 2.0中最大的变化之一就是对二进制数据的支持,而且提供了一个可以直接处理二进制数据的方法——slice方法。

JS中的字符串有slice方法,数组也有。Ajax 2.0经过一些变化后,现在也和数组、字符串的slice方法语法完全一致了。于是,我们就可以把二进制数据流想象成一些连续的字符串数据,并对这些二进制数据进行slice处理。

比方说怎样的场合呢?

PHP默认似乎有个最大文件上传的限制post_max_size,我的本地看了下,是64M.
PHP默认最大上传限制

此时,我们想一次性传一个80M的动作片精华片段,就会以失败告终。

但是,有了slice方法,我们可以把文件分割,比方说,每20M作为一个请求发送出去,后台再把这些二进制数据拼合成一个完整文件。

slice(0, 20); slice(20, 40); slice(40, 60); slice(60)

还有一个很重要的场合就是断点续传

文件传输是个具有时间周期的过程,从玩三国杀的离线率可以看出,掉线什么的是常有的事情。显然,传文件必定会存在传着传着就死在99%位置的情况。

你想啊,大鼻孔姐的片子想放到网盘里,随时随地可以欣赏。结果看着进度条等了40分钟,好不容易传到99%,突然断电…………开机后,发现又要重传,是不是小弟弟要气得短小软?显然,后果很严重哈~

因此,对于大文件而言,断电续传功能很重要。有了Blob数据格式的slice方法,一切都变得简单了。

我的思路是这样的,有两条:
1. 浏览器记住(如localStorage)最近一次成功传输的位置;当再次上传这个图片的时候,直接从浏览器存储的位置开始传。
2. 浏览器不做任何事情,在上传之前先去后台走一遍,看看目前此文件是否存在,以及存在的大小,返回给浏览器,然后浏览器再决定上传的起始位置。

理性的分析以及实践的结果表明,第二种思路可行性更高。//zxx: 并不一定是最好的思路,您可以自己想出更精彩的实践方法

如果用文字举例的话就是:
某老师的视频是80*1024*1024B, 我们每次传1024*1024B,也就是1M,假设传了79M了,结果大脚一抖,电源关掉有木有!某老师就这样随风逝去了……

用户重新开机,决定再次传这个80M的视频。当用户选择了这个文件后,我们先去后台走一圈,把当前已经传好的文件大小反馈给客户端(Ajax 1.0就可以),JS拿反馈大小和源文件大小一比对,奶奶的,残缺啊!于是,就从残缺位置slice,这里就是:

file.slice(79*1024*1024)

接着之前的只传了1M就OK啦!于是,断点续传实现。

由于网页本身的局限性,我们没法直接触发本地文件的上传。因此,目前而言,断点文件还是要用户选择(相比客户端上传软件多了这一步)。

三、文件断点续传的实践

Ajax实现的文件断点续传demo截图 张鑫旭-鑫空间-鑫生活

您可以狠狠地点击这里:HTTP协议下Ajax实现的文件断点续传demo

说明:

  1. 上demo可以说是一个比较完善的上传体验,有删除,重传,续传,进度条等常用功能。
  2. 基于Ajax 2.0的二进制文件传输实现,因此,IE10+,Chrome以及Firefox等浏览器支持。另外JS原生,无外部依赖。
  3. 空间的流量月月吃紧,经常溢出不够用,因此,为了节约成本,只允许最大200K的文件。所以,大家想要测试断点续传效果,可以通过工具,把自己的浏览器速度限制到10~20K每秒。
  4. demo页面每100K就会对文件进行一次分割上传,因此,100~200K大小文件会有看到2次上传请求。PS:实际开发时候,应该至少1M一次分割。
  5. 为了直观表现断点上传的功能,我会截一个本地的视频给大家演示,并原声重现。
  6. 后台PHP很简单,就是追加二进制数据,file_put_contents方法有个FILE_APPEND选项,直接追加,很好用的!
  7. 正好研究了下开源协议,这里请容我练个:demo页面中的JS+CSS+HTML源代码遵循GPL协议,即您可以任意复制,修改,但是,只能作为学习或私人项目使用,不能作为商业软件的一部分去出售。如有疑问,可zhangxinxu@zhangxinxu.com联系。

其他一些技术细节,懂的人不说也知道是怎么回事,不懂的人要花很大功夫斟酌表达才可以。然,目前时机不成熟,付出收益比略低,因此,这里不具体叙述,有问题欢迎邮箱联系。

add on 2016-04-13
目前线上demo貌似文件都无法上传成功,感觉可能和服务商的服务器设置有关,安全限制还是什么的,我也没深究,我本地是好的,可以秒传。大家不要太在意这个细节。

add on 2017-09-19
今天偶然看到服务商有个设置,我开启了下,嘿,可以上传了。

下面视频演示下断点续传的效果:
因为半夜三更录的视频,老婆大人已经安然就寝,所以自己声音比较小,发音也不太准,貌似被优酷搞糊了,大家凑合看看吧~~

四、说点题外话

上月本人只更新了一篇文章。有一周是整周的培训。然后除了研究现在这个Ajax断点续传,还在做一个自己的开源项目(已经用了两周了,本周应该可以结束)。

本人最近有在纠结专利与开源的事情。我司申请专利很是方便,而且公司鼓励,且对个人发展有很有帮助,我显然是要积极做这些事情的。但是,个人价值观中很重要的一点就是要留下什么,因此,自己是非常喜欢共享东西的。于是就存在一个矛盾,有些东西,例如一些创新的想法或者实现方式,如果成为的专利,就不好拿出来随意分享了,因为,自己申请的专利不是自己的而是公司的,自己完全没有把控权。头疼~~

不过,今天例会跟组里前辈沟通了下,算是想通了。这样子,自己平时业余时间研究的东西,即使很赞,很具有专利性,我也会及时和大家一起探讨与进步;如果是实际工作发现的创新性想法,则走专利路线,技术封闭。我举得这样的权衡应该是很不错的,不知大家的看法如何呢?

哈哈,今天特地研究了下开源版权的问题,学到了不少,各种License都大致了解了一点,文中好像就有体现了,

博客所有技术文章遵循CC协议,即署名、非商业性使用、相同方式共享、禁止演绎。

一些开源插件等遵循其他协议,回头补上~~

(本篇完)

分享到:

标签: , , , , , , ,

赞助商推荐(我也要赞助)

想学到点真东西? ×
如果你有1~3年前端开发经验,不妨 ×
想高薪入职阿里? ×
想要免费一对一编程辅导? ×


发表评论(目前55 条评论)

  1. xuexi说道:

    张哥 能不能给个源码学习下。

  2. will说道:

    旭哥能不能把源码发给我研究一下,最近面试遇到这个问题了,谢谢~

  3. baojin说道:

    球源码能发我一份吗大神529834149@qq.com万分感谢

  4. 张尧贤说道:

    求客户端,服务端源码,求学一下

  5. 冰封草原说道:

    这种传输方式,与直接使用Socket相比,效率上有没有显著的差别?如果有相关文献是最好的。

  6. hello123说道:

    后台PHP怎么获取二进制数据的,你是怎么实现的

  7. 粗人说道:

    写的很不错~分析也很透彻~~~佩服~

  8. 说道:

    求客户端,服务端源码,求学一下

  9. 路过说道:

    求源码 php服务端接收

  10. 孟霞霞说道:

    求源码,最近项目中想实现断点续传的功能,求大侠指教,多谢

  11. yy说道:

    “silverUpload断点续传上传控件”是基于Http协议的断点续传控件(支持超大文件上传控件) ,该控件区别于swfupload之类的基于flash技术的控件,无需预读文件到内存中,即可实现文件断点续传功能。
    该控件易于程序扩展:只要程序员懂得javascript和任何一种服务器端程序语言(asp,jsp,php,.net)即可对silverUpload进行二次开发,构造自己的上传逻辑业务。
    官网:http://www.silverupload.cn

  12. swzzj说道:

    大神,能不能把源码贴出来呀

  13. wilson说道:

    其實opensource或者community-ware為的都是普及性, 與commercial version相輔相承
    同樣道理你的great idea公開出來會成為影響力, 亦成為你的profile, 以後要接生意你給這個blog人家看就可以了。沒有很大機會賺到錢或者成為專利的東西就公開吧, 否則可能一早有人想到甚至已經實現, 你的idea就連那個好像比人step forward了(說得很抽象, 就是進步哈哈)的價值都沒有了。

  14. 枫叶说道:

    这个可不可以在IE8上运行的啊?求源码看看……739407778@qq.com谢谢……

  15. deses说道:

    鑫哥 您的 filesize.php?filename= 这个是怎么实现 获取已经上传文件大小的? 谢谢

    • 李子祥说道:

      这个应该是通过文件id找到上传的文件,然后php读取文件当前的大小返回的吧。

  16. 鑫哥,求源码研究一下说道:

    鑫哥,求源码研究一下.万分感谢!

  17. cometlj说道:

    问下博主,是否chrome内核的浏览器也可以使用,例如搜狗,QQ浏览器,或者手机端的UC浏览器等等

  18. 雷全说道:

    看到“老婆大人已经安然就寝”这句,瞬间觉得大侠的coding生活还是很惬意的,赞一个!最近也在学这些,感谢!

  19. 小五说道:

    可以弱弱的问一下楼主的QQ吗?想和你交流下

  20. Arjay说道:

    鑫哥,试了下”暂停”,继续上传,上传不了

  21. kazaff说道:

    大鼻孔姐是谁?

  22. 东东说道:

    楼主,谢谢把源码给我参考下撒,谢谢!

  23. 有源码吗 鑫哥说道:

    有源码吗 鑫哥

  24. stone说道:

    Warning: file_get_contents() [function.file-get-contents]: Filename cannot be empty in /data/home/bmu018023/htdocs/study/201310/upload.php on line 18
    这是怎么回事?

  25. 吕大豹说道:

    鑫哥求指导啊。我自己演练了一下断点续传,有个奇怪的问题,js把文件切好了,每片大小是100KB,但上传后文件总是变成23字节大小了。
    我的php代码:
    $filename = $_POST[‘fileName’];
    if ($filename) {
    file_put_contents(‘uploads/’.$filename,$_FILES[“file”][“tmp_name”],FILE_APPEND);
    }
    是php写的不对吗?还是什么问题?
    如果不便公开php代码,能否发到我的邮箱呢,谢谢~lvxiaobao_fbi@163.com

  26. 左眼皮说道:

    博主大人,天朝的浏览器都是老版本,这个断点续传是IE8+,这是在强制用户使用特定版本的浏览器,那不如就直接让他们用谷歌或者其他,用Html5来传就好了。博主,虽然很强大,不过不适用啊

  27. 吕大豹说道:

    受启发不少,感谢~
    但是也发现一点问题:如果一个10M的文件分成了10片上传,这样的话需要发10次请求来完成上传,牺牲也挺大。。不过当前机制貌似无法避免这个问题

  28. wanghl说道:

    楼主写的不错,看了你的代码,因为客户端是断点续传的,是不是progess过程可以由客户端控制的。

    还有现在的情况是每发送一次ajax请求,传送的是当前范围的文件流。在progress过程中服务端是如何知道进度和总大小的呢,这个progess的原理是,或者有其他参考博文,谢谢 还望不吝赐教

  29. bold说道:

    某老师就是苍老师吧

  30. 加油_linda说道:

    有个问题想请教下楼主,你在上传文件之前,要先检查下文件大小,所以肯定是要先获取文件大小,请问你是用什么方法获取到的,是用js还是用其他方法或技术,博主方便告诉我下吗,谢谢

    • 张 鑫旭说道:

      @加油_linda 文章已经提到,1是不稳妥的本地存储,2是Ajax发送到后台,返回地址。demo使用的是后者,视频中有说明。

      • 加油_linda说道:

        谢谢你的回答,我百度了下,好像是说用js获取文件大小不安全,看了下博主demo代码,如果能再结合楼主php代码看就更好了,呵呵!
        博主的博文对我这个初学者学习web有蛮大作用的,永远支持博主!!

  31. 川川哥哥说道:

    不错,深受启发

  32. skway说道:

    能否提供后台php?

  33. 逍遥哥说道:

    slice(0, 20); slice(20, 40); slice(40, 60); slice(60)
    不是
    slice(0, 20); slice(21, 40); slice(41, 60); slice(61)
    ???????

  34. 逍遥哥说道:

    上传没有问题,就是我怎么知道用户选择的文件以前传过,就是要断点续传的文件啊,没有md5_file()函数啊

  35. latimerjie说道:

    太牛了,赞一个!服务器段怎么处理呀?

  36. henry说道:

    请问文中说到的
    “可以通过工具,把自己的浏览器速度限制到10~20K每秒”
    是什么工具?

  37. 路人说道:

    突然让我想到了,三星和苹果的专利战。国外很流行,而且很有必要,不过就像你说的平常发现的新点子当做共享,在工作时间头脑风暴出现的点子就申请。

  38. hyf说道:

    很好的想法啊,回头就用到项目中去^_^

  39. Leniy说道:

    看了下你的about,简介特效好强大

  40. 一丝说道:

    「我们想一次性传一个80M的动作片精华片段」发文不发种,撸管撸到肿,Po 主挽尊~

  41. 猪头说道:

    虽然看不懂 但是还是要顶一下!!

  42. Barret Lee说道:

    关于断点续传已经不是什么新技术了,html5中利用fileAPI读取文件分片传输是比较常见的手段,不过我一直还在纳闷怎么兼容低版本浏览器,回头在看看你的Demo吧,张大哥加油!

  43. fsy0718说道:

    学习啦,但视频挂了!!