asp.net支持断点下载

翅膀的初衷

发表于2013-10-08 15:12:53

  当文件比较大时,下载可能需要历时数小时,万一线路中断,我们不得不重新开始,而断点续传可以让用户从上次断开的地方接着下载,但是断点续传功能不仅仅需要客户端的支持,也需要服务端的支持,本文演示如何让asp.net支持断点下载!

  这是我整理文件时,找出来的4年前的代码,原理很清晰,就是代码有点乱,大家将就看看!

using System; using System.IO; using System.Web; using System.Text; using System.Threading; namespace FuncUtility { /* * 0 未开始下载 * 1 正在下载中 * 2 下载完成 * 3 下载失败 * 4 文件不存在 * 5 文件过大 * 6 文件已更新 与上次下载不同 * 7 续传错误 * 8 程序错误 * 9 未初始化 */ public class DownLoad { private HttpRequest _httpRequest; private HttpResponse _httpResponse; private string _filePath; private long _speed; private int _status; private int _packSize; /// <summary> /// HttpRequest /// </summary> public HttpRequest httpRequest { set { _httpRequest = value; } } /// <summary> /// HttpResponse /// </summary> public HttpResponse httpResponse { set { _httpResponse = value; } } /// <summary> /// 文件路径(物理路径) /// </summary> public string FilePath { set { _filePath = value; } } /// <summary> /// 每秒充许下载的字节数 /// </summary> public long Speed { set { _speed = value; } } /// <summary> /// 状态 /// </summary> public int Status { get { return _status; } } /// <summary> /// 分块下载大小 /// </summary> public int PackSize { set { _packSize = value; } } public DownLoad() { _status = 0; //_httpContext = null; //_filePath = null; _speed = 20480; _packSize = 10240; } public DownLoad(HttpRequest httpreques, HttpResponse httpresponse, string path) { //默认下载速度20KB/秒 每块10KB _status = 0; _httpResponse = httpresponse; _httpRequest = httpreques; _filePath = path; _speed = 20480; _packSize = 10240; } public DownLoad(HttpRequest httpreques, HttpResponse httpresponse, string path, long speed, int packSize) { _status = 0; _httpResponse = httpresponse; _httpRequest = httpreques; _filePath = path; _speed = speed; _packSize = packSize; } public void Start() { if (!string.IsNullOrEmpty(_filePath) && _httpRequest != null && _httpResponse!=null) DownloadFile(); else _status = 9; } private void DownloadFile() { if (!File.Exists(_filePath)) { _status = 4; _httpResponse.StatusCode = 404; } else { //开始下载 _status = 1; //开始字节数 long startBytes = 0; //获取文件名 string fileName = Path.GetFileName(_filePath); //以只读方式读取文件流 FileStream fso = new FileStream(_filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); BinaryReader br = new BinaryReader(fso); long fileLength = fso.Length; //毫秒数:读取下一数据块的时间间隔 int sleep = (int)Math.Ceiling(1000.0 * _packSize / _speed); //文件修改时间 UTC string lastUpdateTiemStr = File.GetLastWriteTimeUtc(_filePath).ToString("r"); //便于恢复下载时提取请求头; string eTag = HttpUtility.UrlEncode(fileName, Encoding.UTF8) + lastUpdateTiemStr; //判断文件是否太大 if (fso.Length > Int32.MaxValue) { _httpResponse.StatusCode = 413;//请求实体太大 _status = 5; } else { bool _chang = true; //对应响应头ETag:文件名+文件最后修改时间 if (_httpRequest.Headers["If-Range"] != null) { //上次被请求的日期之后被修改过 if (_httpRequest.Headers["If-Range"].Replace("\"", "") != eTag) { //文件修改过 无法下载 _chang = false; _status = 6; //预处理失败 _httpResponse.StatusCode = 412; } } if (_chang) { try { _httpResponse.Clear(); _httpResponse.Buffer = false; //_httpResponse.AddHeader("Content-MD5", GetMD5Hash(myFile));//用于验证文件 _httpResponse.AddHeader("Accept-Ranges", "bytes");//重要:续传必须 _httpResponse.AppendHeader("ETag", string.Concat("\"",eTag,"\""));//重要:续传必须 _httpResponse.AppendHeader("Last-Modified", lastUpdateTiemStr);//把最后修改日期写入响应 _httpResponse.ContentType = "application/octet-stream";//MIME类型:匹配任意文件类型 _httpResponse.AddHeader("Content-Disposition",string.Concat("attachment;filename=",HttpUtility.UrlEncode(fileName, Encoding.UTF8).Replace("+", "%20"))); _httpResponse.AddHeader("Content-Length", (fileLength - startBytes).ToString()); _httpResponse.AddHeader("Connection", "Keep-Alive"); _httpResponse.ContentEncoding = Encoding.UTF8; //如果是续传 if (_httpRequest.Headers["Range"] != null) { //重要:续传必须,表示局部范围响应。初始下载时默认为200 _httpResponse.StatusCode = 206; string[] range = _httpRequest.Headers["Range"].Split(new char[] { '=', '-' }); //已下载字符串,续传起始位置 startBytes = Convert.ToInt64(range[1]); //判断起始位置是否有误 if (startBytes < 0 || startBytes >= fileLength) { _status = 7; _chang = false; } } if (_chang) { //如果是续传请求,告诉客户端本次的开始字节数,总长度,以便客户端将续传数据追加到startBytes位置后 if (startBytes > 0) _httpResponse.AddHeader("Content-Range", string.Format(" bytes {0}-{1}/{2}", startBytes, fileLength - 1, fileLength)); //向用户发送数据 br.BaseStream.Seek(startBytes, SeekOrigin.Begin); //分块下载,剩余部分可分成的块数 int maxCount = (int)Math.Ceiling((fileLength - startBytes + 0.0) / _packSize); //发送数据 for (int i = 0; i < maxCount && _httpResponse.IsClientConnected; i++) { _httpResponse.BinaryWrite(br.ReadBytes(_packSize)); _httpResponse.Flush(); if (sleep > 1) Thread.Sleep(sleep); } } } catch { _status = 8; } finally { br.Close(); fso.Close(); fso.Dispose(); } } } } } } }