传统方式上传缺陷

客户端差异

    客户端只提交数据及文件流, 看似应该没有差别;可是IE和firefox有一个最大的不同点,就是IE上传文件不会增加客户端内存占用, 而firefox则要将文件内容不断地读入内存中再发送,比如你使用firefox上传100MB的文件客户端内存就会增加100MB的消耗, 很诧异firefox怎会有如此缺陷!

IIS服务器处理文件上传

   很显然IIS处理文件上传时还是很谨慎的,在上传过程中不会因为提交数据是大文件而引起服务器的内存消耗;所以,猜测IIS是在建立的Pipe读取数据后直接存到临时文件中。但是,很致命的一个问题那就是我们程序员要对上传的文件进行处理,及需要asp.net把真个文件流交给您来处理,最终结果就是 ASP.Net进程还是会把整个文件内容加载内存交给你处理,例如Bitaec框架中就是此种情况。

   另外值得一提的就是ASP.NET中的FileUpload控件,正如我上面分析的情况FileUpload上传文件时IIS不断的从Pipe中读取数据并存入临时文件中,从而不会造成服务器内容增加;当上传完毕时,你直接调用FileUpload里的SaveAs方法将文件直接存入你需要的位置,实际上所做的操作是将收到的临时文件移动你需要的位置,所以是不会增加消耗服务器内存资源。但是,如果你需要对上传的文件流操作的话则会增加内存消耗,因为它需要把临时文件重新加载到内存中。

大文件上传解决方案

利用RFC1867标准处理文件上传的两种方式:
1.一次性得到上传的数据,然后分析处理。
看了N多代码之后发现,目前无组件程序和一些COM组件都是使用Request.BinaryRead方法。一次性得到上传的数据,然后分析处理。这就是为什么上传大文件很慢的原因了,IIS超时不说,就算几百M文件上去了,分析处理也得一阵子。
2.一边接收文件,一边写硬盘。

了解了一下国外的商业组件,比较流行的有Power-Web,AspUpload,ActiveFile,ABCUpload, aspSmartUpload,SA-FileUp。其中比较优秀的是ASPUPLOAD和SA-FILE,他们号称可以处理2G的文件(SA-FILE EE版甚至没有文件大小的限制),而且效率也是非常棒,难道编程语言的效率差这么多?查了一些资料,觉得他们都是直接操作文件流。这样就不受文件大小的制约。但老外的东西也不是绝对完美,ASPUPLOAD处理大文件后,内存占用情况惊人。1G左右都是稀松平常。至于SA-FILE虽然是好东西但是破解难寻。然后发现2款.NET上传组件,Lion.Web.UpLoadModule和AspnetUpload也是操作文件流。但是上传速度和CPU占用率都不如老外的商业组件。
做了个测试,LAN内传1G的文件。ASPUPLOAD上传速度平均是4.4M/s,CPU占用10-15,内存占用 700M。SA-FILE也差不多这样。而AspnetUpload最快也只有1.5M/s,平均是700K/s,CPU占用15-39,测试环境: PIII800,256M内存,100M LAN。我想AspnetUpload速度慢是可能因为一边接收文件,一边写硬盘。资源占用低的代价就是降低传输速度。但也不得不佩服老外的程序,CPU 占用如此之低.....

三、ASP.NET上传文件遇到的问题
我们在用ASP.NET上传大文件时都遇到过这样或那样的问题。设置很大的maxRequestLength值并不能完全解决问题,因为ASP.NET会block直到把整个文件载入内存后,再加以处理。实际上,如果文件很大的话,我们经常会见到Internet Explorer显示 "The page cannot be displayed - Cannot find server or DNS Error",好像是怎么也catch不了这个错误。为什么?因为这是个client side错误,server side端的Application_Error是处理不到的。
四、ASP.NET大文件上传解决方案
解决的方法是利用隐含的HttpWorkerRequest,用它的GetPreloadedEntityBody 和 ReadEntityBody方法从IIS为ASP.NET建立的pipe里分块读取数据。Chris Hynes为我们提供了这样的一个方案(用HttpModule),该方案除了允许你上传大文件外,还能实时显示上传进度。
Lion.Web.UpLoadModule和AspnetUpload 两个.NET组件都是利用的这个方案。

 

解决的方法是利用隐含的HttpWorkerRequest,用它的GetPreloadedEntityBody 和 ReadEntityBody方法从IIS为ASP.NET建立的pipe里分块读取数据

  IServiceProvider provider = (IServiceProvider) HttpContext.Current;
  HttpWorkerRequest wr = (HttpWorkerRequest) provider.GetService(typeof(HttpWorkerRequest));
  byte[] bs = wr.GetPreloadedEntityBody();
  ....
  if (!wr.IsEntireEntityBodyIsPreloaded())
  {
        int n = 1024;
        byte[] bs2 = new byte[n];
        while (wr.ReadEntityBody(bs2,n) >0)
       {
             .....
        }
  }

具体实现代码:slickupload.zip

posted on 2007-04-03 09:33  望天  阅读(2782)  评论(8编辑  收藏  举报