////// 功能简介:asp.net的下载业务的服务端基类(支持客户端显示下载百分比进度,支持并发数控制,支持限速) /// 创建时间:2015-11-20 /// 创建人:pcw /// 博客:https://www.cnblogs.com/taohuadaozhu /// 备注:如果针对大文件下载,则还需要考虑操作系统或iis上最大下载字节数限制。 /// public abstract class DownLoadAbs : IHttpHandler { private static StatusDataDict currStatuDataDict = new StatusDataDict(300); protected object lockObj = new object(); public virtual void ProcessRequest(HttpContext context) { string sDiplayFileName = this.GetDisplayFileName(context); string sServerFileFullPath = this.GetServerFileFullPath(context); int iDownload = 0; iDownload = this.ResponseFile(context.Request, context.Response, sDiplayFileName, sServerFileFullPath, this.BytesCountPerSecond); if (iDownload != 1) { Utils.SaveErrorLog(string.Format("下载文件【{0}】失败(2015-12-15v1),返回值={1}", sServerFileFullPath, iDownload)); if (iDownload == -202) { context.Response.Write(RuntimeContext.GetResponseJson("系统检测到重复的并发下载请求,请稍后再点击下载", -1, null)); } else if (iDownload == -203) { context.Response.Write(RuntimeContext.GetResponseJson("并发下载人数超过最大连接数,请稍后再点击下载", -2, null)); } context.Response.End(); } } protected abstract string GetDisplayFileName(HttpContext hcontext); protected abstract string GetServerFileFullPath(HttpContext hcontext); protected virtual int GetMaxConnectCount() { return 3; } protected virtual long BytesCountPerSecond { get { return 1024000; } } public bool IsReusable { get { return false; } } /// /// 输入参数 _Request: Page.Request对象, _Response: Page.Response对象, _fileName: 下载文件名, _fullPath: 带文件名下载路径, _speed 每秒允许下载的字节数(默认:1024000 B,类似1M/秒) /// /// /// /// /// /// /// protected int ResponseFile(HttpRequest _Request, HttpResponse _Response, string _displayFileName, string _serverFilefullPath, long _speed) { return this.ResponseForDownloadFile(_Request, _Response, _displayFileName, _serverFilefullPath, _speed); } /// /// 输入参数 _Request: Page.Request对象, _Response: Page.Response对象, _fileName: 下载文件名, _fullPath: 带文件名下载路径, _speed 每秒允许下载的字节数(默认:1024000 B,类似1M/秒) /// /// /// /// /// /// /// protected virtual int ResponseForDownloadFile(HttpRequest _Request, HttpResponse _Response, string _displayFileName, string _serverFilefullPath, long _speed) { bool bSuccess = true; if (string.IsNullOrEmpty(_serverFilefullPath)) return -101; if (string.IsNullOrEmpty(_displayFileName)) return -102; if (_speed < 1) return -103; if (_Request == null) return -104; if (_Response == null) return -105; if (File.Exists(_serverFilefullPath) == false) return -201; if (currStatuDataDict.ExistsStatus(_serverFilefullPath)) { return -202; } if (currStatuDataDict.GetStatusCount() >= this.GetMaxConnectCount()) { return -203; } currStatuDataDict.AddStatusData(_serverFilefullPath); FileStream targetFile = new FileStream(_serverFilefullPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); BinaryReader br = new BinaryReader(targetFile); try { _Response.AddHeader("Accept-Ranges", "bytes"); _Response.Buffer = false; long fileTotalLength = targetFile.Length; long startBytes = 0; int packForBlock = 10240; //10K bytes //int sleep = 200; //每秒5次 即5*10K bytes每秒 decimal dSleep = Convert.ToDecimal(1000 * packForBlock / _speed); decimal dMaxCount = 0; int sleep = (int)Math.Floor(dSleep) + 1; if (_Request.Headers["Range"] != null) //这里是客户端返回来的,已下载的进度 { _Response.StatusCode = 206; string[] range = _Request.Headers["Range"].Split(new char[] { '=', '-' }); startBytes = Convert.ToInt64(range[1]); } _Response.AddHeader("Content-Length", (fileTotalLength - startBytes).ToString());//这是此次下载文件的总字节长度 if (startBytes != 0)//如果客户端支持,否则不会添加进度相关的信息 { _Response.AddHeader("Content-Range", string.Format(" bytes {0}-{1}/{2}", startBytes, fileTotalLength - 1, fileTotalLength));//这是本次下载后重新定位的进度 } /* _Response.AddHeader("Connection", "Keep-Alive"); _Response.AddHeader("Keep-Alive", "timeout=600, max=4"); */ _Response.ContentType = "application/octet-stream"; _Response.AddHeader("Content-Disposition", "attachment;filename=" + HttpUtility.UrlEncode(_displayFileName, System.Text.Encoding.UTF8)); br.BaseStream.Seek(startBytes, SeekOrigin.Begin); dMaxCount = (fileTotalLength - startBytes) / packForBlock; int maxCount = (int)Math.Floor(dMaxCount) + 1; byte[] bytesRead = new byte[packForBlock]; for (int i = 0; i < maxCount; i++) { if (_Response != null && _Response.IsClientConnected) { if (File.Exists(_serverFilefullPath)) { bytesRead = br.ReadBytes(packForBlock); if (bytesRead != null) { _Response.BinaryWrite(bytesRead); //_Response.Flush();//add by pcw Thread.Sleep(sleep);//需要注意响应的最大时间设置 } } } else { i = maxCount; } } } catch (Exception error) { bSuccess = false; Utils.SaveErrorLog(string .Format("输出文件【{0}】的文件流过程出现异常:{1},调试信息:{2}", _serverFilefullPath, error.Message, error.StackTrace)); } finally { currStatuDataDict.RemoveStatuData(_serverFilefullPath); if (br != null) { br.Close(); br.Dispose(); br = null; } if (targetFile != null) { targetFile.Close(); targetFile.Dispose(); targetFile = null; } if (_Response != null) { if (bSuccess) { Utils.SaveLog(string.Format("已成功提供客户端下载文件【{0}】", _serverFilefullPath)); } //_Response.End(); HttpContext.Current.Response.SuppressContent = true; // Gets or sets a value indicating whether to send HTTP content to the client. HttpContext.Current.ApplicationInstance.CompleteRequest(); // Causes ASP.NET to bypass all events and filtering in the HTTP pipeline chain of execution and directly execute the EndRequest event. } } return 1; } }