asp.net支持断点下载

作者:翅膀的初衷 来源:本站原创 发布时间:2013-10-08 查看数:62052

当文件比较大时,下载可能需要历时数小时,万一线路中断,我们不得不重新开始,而断点续传可以让用户从上次断开的地方接着下载,但是断点续传功能不仅仅需要客户端的支持,也需要服务端的支持,本文演示如何让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();
                        }
                    }
                }
            }
        }
    }
}