基于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%。
 

前台设计

最后来看看前台设计吧:
基于IHttpAsyncHandler的实时大文件传送器

前台代码很简单,就是通过StartFileUpload()函数触发文件上传动作, TriggerAjax()函数触发进度检测动作。
 

源代码

 
 

你可能感兴趣的:(handler)