利用HTML5分片上传超大文件

原理:把大文件分片,一片一片的传到服务端,再由服务端合并

好处:一旦上传失败只是损失一个分片而已,不用整个文件重传,而且每个分片的大小可以控制在4MB以内,服务端不用做任何设置就可适应

解决方案:RIA,以flex为例,通常是利用FileReference.load方法加载文件得到ByteArray,然后分片构造表单(flash的高版本不允许直接访问文件)。不过这个load方法只能加载较小的文件,大约不超过300MB,因此适用性不是很强,现在可以用html5直接构造分片

代码:

 <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>

 

 <input type="file" id="file" />

<button id="upload">上传</button>

<span id="output" style="font-size:12px">等待</span>

这里的slice方法和FormData都是html5之前不存在的。通过这样的方法,我们的表单构造出来是这样的,抓包看看:

利用HTML5分片上传超大文件

可以看到构造出来的Content-Type是multipart/form-data,也就是符合RFC标准的那个最传统的文件上传表单。另外我们同时传输的name、total等属性也都在表单里。

然后是服务端,没什么新鲜的,完全是在接收一个普通的文件:

 //从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();

}

在上面的基础上,我们可以做很多功能上的扩展,比如我们可以控制所有分片是顺序上传还是并发上传,以适用不同应用。再比如我们可以在整体文件上传前以及分 片上传前都先计算一下相应的HASH,发个请求询问服务器文件是否已存在,如果存在就不要重复上传了,这样就实现了“极速上传”以及“断点续传”。

 

posted on 2016-04-20 11:16  罗曼飞羽  阅读(587)  评论(0)    收藏  举报

导航