使用IHttpAsyncHandler实现带进度条的文件上传

asp.net中实现带进度显示的文件上传功能有很多选择,比如:Html5 XMLHttpRequest2、第三方控件、flash插件等等。本文介绍使用IHttpAsyncHandler对象实现进度显示。

其实采用这种方式并非最佳方案,但可以通过这个案例让我们了解IHttpAsyncHandler的工作原理,了解了它的工作原理其实我们还可以通过它来模拟长连接,实现即时通讯功能。


我们一般用HttpHandler来处理请求,处理过程是同步的,和它对应的就是IHttpAsyncHandler,它是一个异步的http请求处理对象。当请求到达时可以将任务交给另一线程执行,而不阻塞当前代码,从而达到异步效果,当需要返回结果时调用回调方法。好处是可以快速回收工作线程,使得整个站点可以提高并发处理量。


这里我们用到三个handler:guidGenerate.ashx(用来生成客户端唯一标示)、uploadFileHandler.ashx(用来上传文件)、requestProgressAsyncHandler.ashx(用来处理上传进度请求)


首先通过guidGenerate.ashx生成客户端唯一标示sessionId,上传时同时启动上传和接收进度请求,并带上生成的sessionId。进度没有变化时hold住,不返回请求,直到进度变化时【减少无谓的请求次数,避免增加服务器负担】返回客户端进度信息,并重新启动一个请求进度http get请求,依次循环就能实现实时进度显示。


这里有两种思路:


一、直接将处理的进度传给当前进度请求对象,并返回客户端。即上传线程和进度处理再同一个线程完成。

二、将处理的进度信息存在一个静态对象中,然后额外启动一个线程专门处理请求进度响应。

建议采用第二种方法,上传和进度请求分离,互不影响。


1、uploadFileHandler.ashx代码实现:

public class uploadFileHandler : IHttpHandler
    {
        //private static object _syncobject = new object();
        public void ProcessRequest(HttpContext context)
        {
            object sessionId = context.Request.Form["sessionId"];
            uploadFile(context);
            
        }

        private void uploadFile(object httpContext)
        {
            HttpContext context = httpContext as HttpContext;
            object sessionId = context.Request.Form["sessionId"];
            //如果提交的文件名是空,则不处理  
            if (context.Request.Files.Count == 0 || string.IsNullOrWhiteSpace(context.Request.Files[0].FileName))
                return;
            //获取文件流  
            Stream stream = context.Request.Files[0].InputStream;
            //获取文件名称  
            string fileName = Path.GetFileName(context.Request.Files[0].FileName);
            //声明字节数组  
            byte[] buffer;
            //为什么是4096呢?这是操作系统中最小的分配空间,如果你的文件只有100个字节,其实它占用的空间是4096个字节  
            int bufferSize = 4096;
            //获取上传文件流的总长度  
            long totalLength = stream.Length;
            //已经写入的字节数,用于做上传的百分比  
            long writtenSize = 0;
            long lastPercent = 0;//存储上一次进度
            //创建文件  
            using (FileStream fs = new FileStream(context.Server.MapPath("Upload") + "\\" + fileName, FileMode.Create, FileAccess.Write))
            {
                //如果写入文件的字节数小于上传的总字节数,就一直写,直到写完为止  
                int count=0;
                while (writtenSize < totalLength)
                {
                    System.Threading.Thread.Sleep(100);
                    //如果剩余的字节数不小于最小分配空间  
                    if (totalLength - writtenSize >= bufferSize)
                    {
                        //用最小分配空间创建新的字节数组  
                        buffer = new byte[bufferSize];
                    }
                    else
                        //用剩余的字节数创建字节数组  
                        buffer = new byte[totalLength - writtenSize];
                    //读取上传的文件到字节数组  
                    stream.Read(buffer, 0, buffer.Length);
                    //将读取的字节数组写入到新建的文件流中  
                    fs.Write(buffer, 0, buffer.Length);
                    //增加写入的字节数  
                    writtenSize += buffer.Length;
                    long percent = writtenSize * 100 / totalLength;
                    //计算当前上传文件的百分比  
                    if (count == 0)
                    {
                        count++;
                        lastPercent = percent;
                        continue;
                    }
                    else
                    {
                        if (lastPercent != percent)//如果进度有变化则推送到前台显示
                        {

                            try
                            {                                
                                lastPercent = percent;

                                #region 方法1、当前线程处理进度
                                //SendPercentToClient(percent, sessionId.ToString());
                                #endregion

                                #region 方法2、另起一个线程集中处理所有请求进度
                                if (!requestProgressAsyncHandler.initThread)
                                {
                                    requestProgressAsyncHandler.initThread = true;
                                    Thread thread = new Thread(new ThreadStart(SendPercentToClient));
                                    thread.Start();
                                }
                                if (requestProgressAsyncHandler.dictionPercent.ContainsKey(sessionId.ToString()))
                                {
                                    requestProgressAsyncHandler.dictionPercent[sessionId.ToString()] = percent;
                                }
                                else
                                {
                                    requestProgressAsyncHandler.dictionPercent.Add(sessionId.ToString(), percent);
                                }
                                #endregion
                            }
                            catch (Exception ex)
                            {
                                writeInLog("SessionId:" + sessionId.ToString() + ",PercentNumber:" + percent.ToString() + ex.Message + " " + ex.StackTrace);
                            }
                        }
                    }                                                                             
                }
            }  
        }

        public bool IsReusable
        {
            get
            {
                return false;
            }
        }
       
        private void Process(string[] keys)
        {
            foreach (string key in keys)
            {
                if (requestProgressAsyncHandler.dictionRequest.ContainsKey(key))
                {
                    AsyncResult result = requestProgressAsyncHandler.dictionRequest[key];
                    if (requestProgressAsyncHandler.dictionPercent.ContainsKey(key))
                    {
                        long currentPercent=requestProgressAsyncHandler.dictionPercent[key];
                        result.PercentNumber = currentPercent;
                        result.DoCompleteTask();
                        lock (requestProgressAsyncHandler._syncobject)
                        {
                            if (requestProgressAsyncHandler.dictionRequest.ContainsValue(result))
                            {
                                requestProgressAsyncHandler.dictionRequest.Remove(key);
                            }
                            if (currentPercent == 100)
                            {
                                requestProgressAsyncHandler.dictionPercent.Remove(key);
                            }
                        }
                    }
                }
            }
        }

        private void SendPercentToClient()
        {
           
            while (true)
            {
                string[] keys;
                lock (requestProgressAsyncHandler._syncobject)
                {
                    keys = requestProgressAsyncHandler.dictionRequest.Keys.ToArray();
                }

                if (keys.Length == 0)
                {
                    requestProgressAsyncHandler.autoEvent.WaitOne();
                    requestProgressAsyncHandler.autoEvent.Reset();
                }
                else
                {
                    Process(keys);
                }
            }
            
        }  

        private  void SendPercentToClient(long percent, string sessionId)
        {
           
            try            
            {           
                bool flag=true;
                while (flag)
                {

                    if (requestProgressAsyncHandler.dictionRequest.ContainsKey(sessionId))
                    {
                        AsyncResult ar = requestProgressAsyncHandler.dictionRequest[sessionId];
                        if (ar != null && ar.context != null)
                        {
                            if (ar.context.Response.IsClientConnected)
                            {
                                ar.PercentNumber = percent;
                                ar.DoCompleteTask();
                                lock (requestProgressAsyncHandler._syncobject)
                                {
                                    if (requestProgressAsyncHandler.dictionRequest.ContainsValue(ar))
                                    {
                                        requestProgressAsyncHandler.dictionRequest.Remove(sessionId);
                                    }
                                }

                            }
                        }
                        flag = false;
                    }
                    else
                    {

                        if (percent == 100)//如果上传完成后当前未有进度请求则一直等待
                        {

                            requestProgressAsyncHandler.autoEvent.WaitOne();
                            requestProgressAsyncHandler.autoEvent.Reset();
                        }
                        else
                            flag = false;

                    }                      
                    
                }             
               
            }
            catch (Exception ex)
            {
                writeInLog(ex.Message+" "+ex.StackTrace);
            }
        }

        
    }

2、requestProgressAsyncHandler.ashx代码实现:

public class requestProgressAsyncHandler : IHttpAsyncHandler
    {
        //存储所有进度请求对象
        public static Dictionary<string, AsyncResult> dictionRequest = new Dictionary<string, AsyncResult>();
        //存储处理进度信息
        public static Dictionary<string, long> dictionPercent = new Dictionary<string, long>();
        public static readonly object _syncobject = new object();
        public static bool initThread = false;
        public static AutoResetEvent autoEvent = new AutoResetEvent(false);
        public void ProcessRequest(HttpContext context)
        {
        }
        public bool IsReusable
        {
            get
            {
                return false;
            }
        }
        #region IHttpAsyncHandler 成员

        public IAsyncResult BeginProcessRequest(HttpContext context, AsyncCallback cb, object extraData)
        {
            //添加请求
            AsyncResult result = new AsyncResult(context, cb);
            string sessionId = context.Request.QueryString["sessionId"].ToString();
            
            try
            {
                lock (_syncobject)
                {
                    dictionRequest.Add(sessionId, result);
                }
                autoEvent.Set();//通知线程工作
            }
            catch (Exception ex)
            {
                writeInLog(ex.Message + " " + ex.StackTrace);
            }
            
            return result;
        }

        
        public void EndProcessRequest(IAsyncResult result)
        {
            //返回上传进度
            AsyncResult ar = (AsyncResult)result;
            ar.Send();
        }



        #endregion
        
    }  

    

    public class AsyncResult : IAsyncResult
    {
        // 标示异步处理的状态  
        private bool isComplete = false;

        //保存异步处理程序中的Http上下文  
        public HttpContext context;

        //异步回调的委托  
        private AsyncCallback callback;
        /// <summary>  
        /// 获取或设置保存下载文件的百分比数值部分  
        /// </summary>  
        public long PercentNumber;
        //public string SessionId;
        public AsyncResult(HttpContext context, AsyncCallback callback)
        {
            this.context = context;
            this.callback = callback;
            //this.SessionId = context.Request.QueryString["sessionId"].ToString();
        }
        /// <summary>  
        /// 向客户端写入信息  
        /// </summary>  
        public void Send()
        {
            this.context.Response.Write(PercentNumber);
        }
        /// <summary>  
        /// 完成异步处理,结束请求  
        /// </summary>  
        public void DoCompleteTask()
        {
            if (callback != null)
                callback(this);//会触发处理程序中的EndProcessRequest函数,结束请求  
            this.isComplete = true;
        }
        #region IAsyncResult 成员

        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 isComplete; }
        }

        #endregion

    }  

3、运行效果如下:



源代码可以在http://download.csdn.net/detail/taoerchun/9481759地址下载,有疑问的朋友可以随时向我留言。另外感谢程序诗人的一遍文章给我的启示。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值