在日常工作中,有时候需要到远程服务器上部署新版本的系统,由于远程服务器出于外网,所以每次都要开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()函数触发进度检测动作。
 

源代码

 
 
posted on 2013-11-06 23:28  程序诗人  阅读(1698)  评论(17编辑  收藏  举报