利用HTML5分片上传超大文件

本文介绍一种利用HTML5特性实现的大文件分片上传方案。该方案将大文件分割成多个小片段分别上传,提高了上传效率并降低了因网络波动导致的上传失败风险。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在网页中直接上传大文件一直是个比较头疼的问题,主要面临的问题一般包括两类:一是上传时间长中途一旦出错会导致前功尽弃;二是服务端配置复杂,要考虑接收超大表单和超时问题,如果是托管主机没准还改不了配置,默认只能接收小于4MB的附件。


比较理想的方案是能够把大文件分片,一片一片的传到服务端,再由服务端合并。这么做的好处在于一旦上传失败只是损失一个分片而已,不用整个文件重传,而且每个分片的大小可以控制在4MB以内,服务端不用做任何设置就可适应。


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


好在现在有了HTML5,我们可以直接构造分片了,这是一个非常喜人的进步,只可惜目前适用面不广(IE啊IE,真是恨你恨得牙痒痒)。


言归正传,来看一个DEMO吧,基于ASP.Net MVC3,只是示例,很多问题做了简化处理。


主要是客户端,新特性都体现在这里:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
<%@ 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之前不存在的。通过这样的方法,我们的表单构造出来是这样的,抓包看看:

wKiom1QWUtCQfZ-UAALOCP0nEBw324.jpg

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


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
[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
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值