在日常工作中,有時候需要到遠程服務器上部署新版本的系統,由於遠程服務器出於外網,所以每次都要開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%。
前台設計
最后來看看前台設計吧:
前台代碼很簡單,就是通過StartFileUpload()函數觸發文件上傳動作, TriggerAjax()函數觸發進度檢測動作。
源代碼


