/// <summary> /// 功能簡介:asp.net的下載業務的服務端基類(支持客戶端顯示下載百分比進度,支持併發數控制,支持限速) /// 建立時間:2015-11-20 /// 建立人:pcw /// 博客:https://www.cnblogs.com/taohuadaozhu /// 備註:若是針對大文件下載,則還須要考慮操做系統或iis上最大下載字節數限制。 /// </summary> 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; } } /// <summary> /// 輸入參數 _Request: Page.Request對象, _Response: Page.Response對象, _fileName: 下載文件名, _fullPath: 帶文件名下載路徑, _speed 每秒容許下載的字節數(默認:1024000 B,相似1M/秒) /// </summary> /// <param name="_Request"></param> /// <param name="_Response"></param> /// <param name="_displayFileName"></param> /// <param name="_serverFilefullPath"></param> /// <param name="_speed"></param> /// <returns></returns> protected int ResponseFile(HttpRequest _Request, HttpResponse _Response, string _displayFileName, string _serverFilefullPath, long _speed) { return this.ResponseForDownloadFile(_Request, _Response, _displayFileName, _serverFilefullPath, _speed); } /// <summary> /// 輸入參數 _Request: Page.Request對象, _Response: Page.Response對象, _fileName: 下載文件名, _fullPath: 帶文件名下載路徑, _speed 每秒容許下載的字節數(默認:1024000 B,相似1M/秒) /// </summary> /// <param name="_Request"></param> /// <param name="_Response"></param> /// <param name="_displayFileName"></param> /// <param name="_serverFilefullPath"></param> /// <param name="_speed"></param> /// <returns></returns> 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; } }