在網頁中直接上傳大檔案一直是個比較頭痛的問題,主要面臨的問題一般都包含兩類:一是上傳時間長中途一旦出錯會導致前功盡棄;二是服務端配置複雜,要考慮接收超大表單和超時問題,如果是託管主機沒準還改不了配置,預設只能接收小於4MB的附件。
比較理想的方案是能夠把大檔案分片,一片一片的傳到服務端,再由服務端合併。這麼做的好處是一旦上傳失敗只是損失一個分片而已,不用整個文件重傳,而且每個分片的大小可以控制在4MB以內,服務端不用做任何設定就可適應。
常用的解決方案是RIA,以flex為例,通常是利用FileReference.load方法載入檔案得到ByteArray,然後分片建構表單(flash的高版本不允許直接存取檔案)。不過這個load方法只能載入較小的文件,大約不超過300MB,因此適用性不是很強。
好在現在有了HTML5,我們可以直接構造分片了,這是一個非常喜人的進步,只可惜目前適用面不廣(IE啊IE,真是恨你恨得牙癢癢)。
言歸正傳,來看一個DEMO吧,基於ASP.Net MVC3,只是範例,很多問題做了簡化處理。
主要是客戶端,新特性都體現在這裡:
<%@ Page Language="C#" Inherits="System.Web.Mvc.ViewPage<dynamic>" %> <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="utf-8"> <title>HTML5大文件分片上传示例</title> <script src="../Scripts/jquery-1.11.1.min.js"></script> <script> var page = { init: function(){ $("#upload").click($.proxy(this.upload, this)); }, upload: function(){ var file = $("#file")[0].files[0], //文件对象 name = file.name, //文件名 size = file.size, //总大小 succeed = 0; var shardSize = 2 * 1024 * 1024, //以2MB为一个分片 shardCount = Math.ceil(size / shardSize); //总片数 for(var i = 0;i < shardCount;++i){ //计算每一片的起始与结束位置 var start = i * shardSize, end = Math.min(size, start + shardSize); //构造一个表单,FormData是HTML5新增的 var form = new FormData(); form.append("data", file.slice(start,end)); //slice方法用于切出文件的一部分 form.append("name", name); form.append("total", shardCount); //总片数 form.append("index", i + 1); //当前是第几片 //Ajax提交 $.ajax({ url: "../File/Upload", type: "POST", data: form, async: true, //异步 processData: false, //很重要,告诉jquery不要对form进行处理 contentType: false, //很重要,指定为false才能形成正确的Content-Type success: function(){ ++succeed; $("#output").text(succeed + " / " + shardCount); } }); } } }; $(function(){ page.init(); }); </script> </head> <body> <input type="file" id="file" /> <button id="upload">上传</button> <span id="output" style="font-size:12px">等待</span> </body> </html>
這裡的slice方法和FormData都是html5之前不存在的。透過這樣的方法,我們的表單建構出來是這樣的,抓包看看:
可以看到建構出來的Content-Type是multipart/form-data,也就是符合RFC標準的那個最傳統的檔案上傳表單。另外我們同時傳輸的name、total等屬性也都在表單裡。
然後是服務端,沒什麼新鮮的,完全是在接收一個普通的檔案:
[HttpPost] public ActionResult Upload() { //从Request中取参数,注意上传的文件在Requst.Files中 string name = Request["name"]; int total = Convert.ToInt32(Request["total"]); int index = Convert.ToInt32(Request["index"]); var data = Request.Files["data"]; //保存一个分片到磁盘上 string dir = Server.MapPath("~/Upload"); string file = Path.Combine(dir, name + "_" + index); data.SaveAs(file); //如果已经是最后一个分片,组合 //当然你也可以用其它方法比如接收每个分片时直接写到最终文件的相应位置上,但要控制好并发防止文件锁冲突 if(index == total) { file = Path.Combine(dir, name); var fs = new FileStream(file, FileMode.Create); for(int i = 1;i <= total;++i) { string part = Path.Combine(dir, name + "_" + i); var bytes = System.IO.File.ReadAllBytes(part); fs.Write(bytes, 0, bytes.Length); bytes = null; System.IO.File.Delete(part); } fs.Close(); } //返回是否成功,此处做了简化处理 return Json(new { Error = 0 }); }
上面的DEMO很多問題是簡化處理的,例如沒做什麼異常處理,客戶端也沒有判斷服務端是否出錯重試一類的,各位可以自己完善。
在上面的基礎上,我們可以做很多功能上的擴展,例如我們可以控制所有分片是順序上傳還是並發上傳,以適用不同應用。再例如我們可以在整體檔案上傳前以及分片上傳前都先計算一下對應的HASH,發個請求詢問伺服器檔案是否已存在,如果存在就不要重複上傳了,這樣就實現了「極速上傳」以及「斷點續傳」
【相關推薦】
#1. 免費h5線上影片教學
##2 . 3.以上是h5在網頁上傳超大文件教學實例的詳細內容。更多資訊請關注PHP中文網其他相關文章!