在日常工作中,有时候需要到远程服务器上部署新版本的系统,由于远程服务器出于外网,所以每次都要开QQ连接,非常麻烦。索性就研究了下IHttpasyncHandler,并结合Juqery ProgressBar,打造了一款大文件传送器。其基本原理就是首先在客户端将大文件分段拆分,然后写入内存流,最后发送到服务器上。在上传的同时,会利用异步Handler来获取当前进度并推送到前台显示。图示效果如下(当前缓存设置为5000字节大小):
图示结果
(图1,传送到36%的时候)
(图2,传送到100%的时候)
异步Handler设计
下面来说说具体的实现方式,首先来说说实时通知这块:
using System; using System.Collections.Generic; using System.Web; using AsyncThermometerDeamon.Handlers.Code; namespace AsyncThermometerDeamon.Handlers { public class FileUploadWatcher : IHttpAsyncHandler { public static List<AsyncResultDaemon> asyncResults = new List<AsyncResultDaemon>(); public IAsyncResult BeginProcessRequest(HttpContext context, AsyncCallback cb, object extraData) { AsyncResultDaemon asyncResult = new AsyncResultDaemon(context,cb,extraData); asyncResults.Add(asyncResult); return asyncResult; } public void EndProcessRequest(IAsyncResult result) { asyncResults.Clear(); AsyncResultDaemon aResult = result as AsyncResultDaemon; aResult.Send(); } public bool IsReusable { get { return false; } } public void ProcessRequest(HttpContext context) { throw new NotImplementedException(); } } }
在程序中,首先申明了一个List容器,以便保存当前的请求。然后当有请求进来的时候,BeginProcessRequest会将当前请求进行初始化,然后加入到List容器中,最后将执行结果返回。
而在EndProcessRequest函数中,一旦当前的操作完成,将会触发此函数推送当前的进度数据到前台。
这里我们来看一下AsyncResultDaemon类:
using System; using System.Collections.Generic; using System.Linq; using System.Web; namespace AsyncThermometerDeamon.Handlers.Code { public class AsyncResultDaemon:IAsyncResult { public AsyncResultDaemon(HttpContext context, AsyncCallback cb,object extraData) { this.context = context; this.cb = cb; } private HttpContext context; private AsyncCallback cb; private object extraData; public long percent; public bool isCompleted = false; public void Send() { context.Response.Write(percent); } public void CompleteTask() { if (cb != null) { cb(this); this.isCompleted = true; } } public object AsyncState { get { return null; } } public System.Threading.WaitHandle AsyncWaitHandle { get { return null; } } public bool CompletedSynchronously { get { return false; } } public bool IsCompleted { get { return isCompleted; } } } }
这个类很简单,继承自IAsyncResult接口,主要用来返回异步执行结果的。当异步执行任务完毕后,其他函数可以通过调用CompleteTask方法来抛出任务完成事件,这个抛出的事件将会被EndProcessRequest接住,进而推送实时进度通知。
文件上传Handler设计
说完了异步Handler,再来说一说文件上传Handler:
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.IO; namespace AsyncThermometerDeamon.Handlers { public class FileUpload : IHttpHandler { //设置发送的缓冲大小 private int bufferSize = 5000; public void ProcessRequest(HttpContext context) { //得到文件全路径及文件名称 string filePath = context.Request.QueryString["path"]; string fileName = Path.GetFileName(filePath); byte[] byteToSend; FileStream fs = new FileStream(filePath, FileMode.Open); fs.Position = 0; long totalBytesLength = fs.Length; //文件分多少批发送 long totalBatch = 0; if (totalBytesLength % bufferSize == 0) totalBatch = totalBytesLength / bufferSize; else totalBatch = totalBytesLength / bufferSize + 1; //遍历 for (int i = 0; i < totalBatch; i++) { //设置当前需要获取的流的位置 fs.Position = i * bufferSize; //如果是最后一批流数据 if (i == totalBatch - 1) { long lastBatchLength = totalBytesLength = totalBytesLength - i * bufferSize; byteToSend = new byte[lastBatchLength]; fs.Read(byteToSend, 0, (int)lastBatchLength); } else { byteToSend = new byte[bufferSize]; fs.Read(byteToSend, 0, bufferSize); } //避免闭包 int j = i; //写数据 using (FileStream fsWrite = new FileStream(@"C:\" + fileName, FileMode.Append, FileAccess.Write)) { fsWrite.Write(byteToSend, 0, byteToSend.Length); fsWrite.Flush(); } //预报当前发送状态 long percentage = (j+1)*100/totalBatch; //while循环能够保证最后一批数据顺利推送到前台 while (FileUploadWatcher.asyncResults.Count == 0 && percentage == 100) { } if (FileUploadWatcher.asyncResults.Count != 0) { FileUploadWatcher.asyncResults[0].percent = percentage; FileUploadWatcher.asyncResults[0].CompleteTask(); } } fs.Close(); } public bool IsReusable { get { return false; } } } }
文件上传这块,主要是通过将大文件分割,然后放到一个容积为5000字节的缓冲区中,分段发送,以避免出现OutOfMemory的错误。所以,这种处理机制可以保证发送大数据的文件,比如说1GB大小的文件。在每次写文件的时候,程序会利用long percentage = (j+1)*100/totalBatch;来计算当前的进度,并且通过如下的代码来将当前的进度数据进行推送:
while (FileUploadWatcher.asyncResults.Count == 0 && percentage == 100) { } if (FileUploadWatcher.asyncResults.Count != 0) { FileUploadWatcher.asyncResults[0].percent = percentage; FileUploadWatcher.asyncResults[0].CompleteTask(); }
如果当前有请求,那么就可以调用异步Handler中的CompleteTask来报告本次的进度,CompleteTask将会抛出事件来触发EndProcessRequest,EndProcessRequest则会将当前的进度数据推送至前台。
While循环主要是保证最后一次数据推送能够正常完成,去掉这句,数据一直会显示完成度为99%。
前台设计
最后来看看前台设计吧:
源代码