基於IHttpAsyncHandler的實時大文件傳送器


在日常工作中,有時候需要到遠程服務器上部署新版本的系統,由於遠程服務器出於外網,所以每次都要開QQ連接,非常麻煩。索性就研究了下IHttpasyncHandler,並結合Juqery ProgressBar,打造了一款大文件傳送器。其基本原理就是首先在客戶端將大文件分段拆分,然后寫入內存流,最后發送到服務器上。在上傳的同時,會利用異步Handler來獲取當前進度並推送到前台顯示。圖示效果如下(當前緩存設置為5000字節大小):

圖示結果

QQ截圖20131106231102

(圖1,傳送到36%的時候)

QQ截圖20131106231112

(圖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()函數觸發進度檢測動作。
 

源代碼

 
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM