您的位置:9159金沙官网 > www.9159.com > www.9159.com【转】文件下载之断点续传(客户端与

www.9159.com【转】文件下载之断点续传(客户端与

发布时间:2019-10-23 13:00编辑:www.9159.com浏览(145)

    【转】文件下载之断点续传(客户端与服务端的落实)

    前边讲了文本的上传www.9159.com【转】文件下载之断点续传(客户端与服务端的实现)。,今日来聊天文件的下载。

    惯例,依旧从最简单易行残酷的开首。那么多轻易算轻松?多阴毒算严酷?小编告诉您能够不写一句代码,你信吗?直接把一个文书往IIS服务器上风姿罗曼蒂克扔,就匡助下载。还TM么能够断点续传(IIS服务端暗许援救)。

    在贴代码在此之前先来打探下怎么是断点续传(此间说的是下载断点续传)?怎么贯彻的断点续传?
    断点续传就是下载了大意上断网也许暂停了,然后能够随着下载。不用从头初叶下载。

    超级漂亮妙啊,其实简单得很,我们想想也是足以想到的。
    首先客商端向服务端发送三个诉求(下载文件)。然后服务端响应央求,消息包涵文件总大小、文件流开首和终止地方、内容大小等。那现实是怎么贯彻的啊?
    HTTP/1.1有身形属性Range。比方你发送央求的时候带上Range:0-199,等于你是央浼0到199里面包车型地铁多少。然后服务器响应诉求Content-Range: bytes 0-199/250 ,表示您取得了0到199里头的多寡,总大小是250。(也便是告诉您还会有多少没有下载完)。

    咱俩来画个图吧。
    www.9159.com 1

    是否很简短?这么奇妙的事物也正是个“约定”而已,也等于所谓的HTTP协议。
    www.9159.com【转】文件下载之断点续传(客户端与服务端的实现)。然而,公约这东西你遵循它就存在,不服从它就不设有。就如民国时期的钱我们都信它,它就有用。假如大多数人不相信它,也就没卵用了。
    那一个断点续传也是那样。你服务端固守就支持,不固守也就不扶植断点续传。所以大家写下载工具的时候要求看清响应报文里有未有Content-Range,来分明是不是扶植断点续传。
    废话够多了,上边撸起袖子开干。

    文件下载-服务端

    采纳a标签提供文件下载

    利用a标签来下载文件,相当于我们前边说的不写代码就足以兑现下载。直接把文件往iis服务器上意气风发扔,然后把链接贴到a标签上,完事。

    <a href="/新建文件夹2.rar">下载</a>
    

    差不离、狂暴不用说了。如真得这么好那大家也不会讨厌去写别的下载逻辑了。这里有个致命的缺欠。这种办法提供的下载非常不够安全。哪个人都可以下载,未有权力决定,说不定还恐怕会被人文件扫描(好像csdn就出过那档子事)。

    www.9159.com【转】文件下载之断点续传(客户端与服务端的实现)。利用Response.TransmitFile提供文件下载

    地点说直接a标签提供下载远远不足安全。那大家怎么提供相对安全的下载呢。asp.net暗许App_Data文本夹是不可能被直接待上访谈的,那我们把下载文件放那其间。然后下载的时候大家读取文件在回去到响应流。

    //文件下载
    public void FileDownload5()
    {          
        //前面可以做用户登录验证、用户权限验证等。
    
        string filename = "大数据.rar";   //客户端保存的文件名  
        string filePath = Server.MapPath("/App_Data/大数据.rar");//要被下载的文件路径 
    
        Response.ContentType = "application/octet-stream";  //二进制流
        Response.AddHeader("Content-Disposition", "attachment;filename=" + filename);
        Response.TransmitFile(filePath); //将指定文件写入 HTTP 响应输出流
    }
    

    别的措施文件下载

    在网络寻找C#文本下载常常都会搜到所谓的“各样格局”。其实那些代码并不可能拿来一向利用,有坑的。
    第一种:(Response.BinaryWrite)

     public void FileDownload2()
     {
         string fileName = "新建文件夹2.rar";//客户端保存的文件名  
         string filePath = Server.MapPath("/App_Data/新建文件夹2.rar");//要被下载的文件路径   
    
         Response.ContentType = "application/octet-stream";//二进制流
         //通知浏览器下载文件而不是打开  
         Response.AddHeader("Content-Disposition", "attachment;  filename=" + HttpUtility.UrlEncode(fileName, System.Text.Encoding.UTF8));
    
         //以字符流的形式下载文件  
         using (FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read))
         {
             Response.AddHeader("Content-Length", fs.Length.ToString());
             //这里容易内存溢出
             //理论上数组最大长度 int.MaxValue 2147483647 
             //(实际分不到这么多,不同的程序能分到值也不同,本人机器,winfrom( 2147483591 相差56)、iis(也差不多2G)、iis Express(只有100多MB))
             byte[] bytes = new byte[(int)fs.Length];
             fs.Read(bytes, 0, bytes.Length);
             Response.BinaryWrite(bytes);
         }
         Response.Flush();
         Response.End();
     }
    

    第黄金年代数组最大尺寸为int.MaxValue,然后不奇怪程序是不会分这么大内部存储器,相当轻松搞挂服务器。(也就是能够下载的公文,极限值最多也就2G不到。)【不推荐】

    第二种:(Response.WriteFile)

    public void FileDownload3()
    {
        string fileName = "新建文件夹2.rar";//客户端保存的文件名  
        string filePath = Server.MapPath("/App_Data/新建文件夹2.rar");//要被下载的文件路径  
        FileInfo fileInfo = new FileInfo(filePath);
        Response.Clear();
        Response.ClearContent();
        Response.ClearHeaders();
        Response.AddHeader("Content-Disposition", "attachment;filename="" + HttpUtility.UrlEncode(fileName, System.Text.Encoding.UTF8) + """);
        Response.AddHeader("Content-Length", fileInfo.Length.ToString());//文件大小
        Response.AddHeader("Content-Transfer-Encoding", "binary");
        Response.ContentType = "application/octet-stream";
        Response.WriteFile(fileInfo.FullName);//大小参数必须介于零和最大的 Int32 值之间(也就是最大2G,不过这个操作非常耗内存)
        //这里容易内存溢出
        Response.Flush();
        Response.End();
    }
    

    主题材料和率先连串似,也是无法下载当先2G的文件。然后下载差不离2G文本时,机器也是地处被挂的边缘,十分恐怖。【不引入】

    第三种:(Response.OutputStream.Write)

    public void FileDownload4()
    {
        string fileName = "大数据.rar";//客户端保存的文件名  
        string filePath = Server.MapPath("/App_Data/大数据.rar");//要被下载的文件路径   
    
        if (System.IO.File.Exists(filePath))
        {
            const long ChunkSize = 102400; //100K 每次读取文件,只读取100K,这样可以缓解服务器的压力  
            byte[] buffer = new byte[ChunkSize];
    
            Response.Clear();
            using (FileStream fileStream = System.IO.File.OpenRead(filePath))
            {
                long fileSize = fileStream.Length; //文件大小  
                Response.ContentType = "application/octet-stream"; //二进制流
                Response.AddHeader("Content-Disposition", "attachment; filename=" + HttpUtility.UrlEncode(fileName, System.Text.Encoding.UTF8));
                Response.AddHeader("Content-Length", fileStream.Length.ToString());//文件总大小
                while (fileSize > 0 && Response.IsClientConnected)//判断客户端是否还连接了服务器
                {
                    //实际读取的大小  
                    int readSize = fileStream.Read(buffer, 0, Convert.ToInt32(ChunkSize));
                    Response.OutputStream.Write(buffer, 0, readSize);
                    Response.Flush();//如果客户端 暂停下载时,这里会阻塞。
                    fileSize = fileSize - readSize;//文件剩余大小
                }
            }
            Response.Close();
        }
    }
    

    此间分明看出了是在循环读取输出,相比敏感。下载大文件时未尝压力。【推荐】

    第四种:(Response.TransmitFile)
    也就上开始比方说的那种,下载大文件也未曾压力。【推荐】

    public void FileDownload5()
    {          
        //前面可以做用户登录验证、用户权限验证等。
    
        string filename = "大数据.rar";   //客户端保存的文件名  
        string filePath = Server.MapPath("/App_Data/大数据.rar");//要被下载的文件路径 
    
        Response.ContentType = "application/octet-stream";  //二进制流
        Response.AddHeader("Content-Disposition", "attachment;filename=" + filename);
        Response.TransmitFile(filePath); //将指定文件写入 HTTP 响应输出流
    }
    

    文本下载-客商端

    下边达成了文本下载的服务端完结,接下去我们得以达成公文下载的顾客端完毕。客商端的下载能够直接是浏览器提供的下载,也足以是迅雷恐怕我们自个儿写的下载程序。这里为了更加好的深入分析,大家来用winfrom程序自身写个下载顾客端。

    直接下载

    private async void button1_ClickAsync(object sender, EventArgs e)
    {
        using (HttpClient http = new HttpClient())
        {
            var httpResponseMessage = await http.GetAsync("http://localhost:813/新建文件夹2.rar");//发送请求 (链接是a标签提供的)
            var contentLength = httpResponseMessage.Content.Headers.ContentLength;//读取文件大小
            using (var stream = await httpResponseMessage.Content.ReadAsStreamAsync())//读取文件流
            {
                var readLength = 1024000;//1000K  每次读取大小
                byte[] bytes = new byte[readLength];
                int writeLength;
                while ((writeLength = stream.Read(bytes, 0, readLength)) > 0)//分块读取文件流
                {
                    using (FileStream fs = new FileStream(Application.StartupPath + "/temp.rar", FileMode.Append, FileAccess.Write))//使用追加方式打开一个文件流
                    {
                        fs.Write(bytes, 0, writeLength);//追加写入文件
                        contentLength -= writeLength;
                        if (contentLength == 0)//如果写入完成 给出提示
                            MessageBox.Show("下载完成");
                    }
                }
            }
        } 
    }
    

    望着这么漂亮的代码,好像没难点。可实际往往不尽人意。
    www.9159.com 2

    咱俩来看了一个那些“System.Net.Http.HttpRequestException:“不能够向缓冲区写入比所安排最大缓冲区大小 2147483647 更多的字节。”,什么鬼,又是2147483647以此数字。因为大家下载的文件大小超越了2G,无法缓冲下载。
    但是“缓冲下载”下又是何等鬼。小编也不晓得。那我们试试能够关掉这一个东东啊?答案是早晚的。

    var httpResponseMessage = await http.GetAsync("http://localhost:813/新建文件夹2.rar");//发送请求
    

    改成上面就能够了

    var httpResponseMessage = await http.GetAsync("http://localhost:813/新建文件夹2.rar",HttpCompletionOption.ResponseHeadersRead);//响应一可用且标题可读时即应完成的操作。 (尚未读取的内容。)
    

    www.9159.com 3
    我们来看枚举HttpCompletionOption的多少个值。三个是响应读取内容,二个是响应读取标题(也便是Headers里的内容)。

    异步下载

    我们开掘在下载大文件的时候会招致界面假死。那是UI单线程程序的劣点。当然,这么差的顾客体验是大家不可能隐忍的。下边大家为下载开多少个线程,幸免产生UI线程的隔膜。

    /// <summary>
    /// 异步下载
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private async void button2_ClickAsync(object sender, EventArgs e)
    {
        //开启一个异步线程
        await Task.Run(async () =>
        {
            //异步操作UI元素
            label1.Invoke((Action)(() =>
                    {
                        label1.Text = "准备下载...";
                    }));
    
            long downloadSize = 0;//已经下载大小
            long downloadSpeed = 0;//下载速度
            using (HttpClient http = new HttpClient())
            {
                var httpResponseMessage = await http.GetAsync("http://localhost:813/新建文件夹2.rar", HttpCompletionOption.ResponseHeadersRead);//发送请求
                var contentLength = httpResponseMessage.Content.Headers.ContentLength;   //文件大小                
                using (var stream = await httpResponseMessage.Content.ReadAsStreamAsync())
                {
                    var readLength = 1024000;//1000K
                    byte[] bytes = new byte[readLength];
                    int writeLength;
                    var beginSecond = DateTime.Now.Second;//当前时间秒
                    while ((writeLength = stream.Read(bytes, 0, readLength)) > 0)
                    {
                        //使用追加方式打开一个文件流
                        using (FileStream fs = new FileStream(Application.StartupPath + "/temp.rar", FileMode.Append, FileAccess.Write))
                        {
                            fs.Write(bytes, 0, writeLength);
                        }
                        downloadSize += writeLength;
                        downloadSpeed += writeLength;
                        progressBar1.Invoke((Action)(() =>
                        {
                            var endSecond = DateTime.Now.Second;
                            if (beginSecond != endSecond)//计算速度
                            {
                                downloadSpeed = downloadSpeed / (endSecond - beginSecond);
                                label1.Text = "下载速度" + downloadSpeed / 1024 + "KB/S";
    
                                beginSecond = DateTime.Now.Second;
                                downloadSpeed = 0;//清空
                            }
                            progressBar1.Value = Math.Max((int)(downloadSize * 100 / contentLength), 1);
                        }));
                    }
    
                    label1.Invoke((Action)(() =>
                    {
                        label1.Text = "下载完成";
                    }));
                }
            }
        });
    }
    

    效果图:
    www.9159.com 4

    断点续传

    上边的办法我们发掘,要是下载到二个半断网了后一次会重头早先下载。那和大家明日的核心显明不合嘛。上边大家初步专门的工作步向正题文件下载之断点续传。把后边大家提及的头属性Range用起来。

    var request = new HttpRequestMessage { RequestUri = new Uri(url) };
    request.Headers.Range = new RangeHeaderValue(rangeBegin, null); //【关键点】全局变量记录已经下载了多少,然后下次从这个位置开始下载。
    var httpResponseMessage = await http.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
    

    旭日东升体化代码跋山涉水的近义词

    /// <summary>
    /// 是否暂停
    /// </summary>
    static bool isPause = true;
    /// <summary>
    /// 下载开始位置(也就是已经下载了的位置)
    /// </summary>
    static long rangeBegin = 0; //(当然,这个值也可以存为持久化。如文本、数据库等)
    
    private async void button3_ClickAsync(object sender, EventArgs e)
    {
        isPause = !isPause;
        if (!isPause)//点击下载
        {
            button3.Text = "暂停";
    
            await Task.Run(async () =>
            {
                //异步操作UI元素
                label1.Invoke((Action)(() =>
               {
                   label1.Text = "准备下载...";
               }));
    
                long downloadSpeed = 0;//下载速度
                using (HttpClient http = new HttpClient())
                {
                    var url = "http://localhost:813/新建文件夹2.rar";
                    var request = new HttpRequestMessage { RequestUri = new Uri(url) };
                    request.Headers.Range = new RangeHeaderValue(rangeBegin, null); //【关键点】全局变量记录已经下载了多少,然后下次从这个位置开始下载。
                    var httpResponseMessage = await http.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
                    var contentLength = httpResponseMessage.Content.Headers.ContentLength;//本次请求的内容大小
                    if (httpResponseMessage.Content.Headers.ContentRange != null) //如果为空,则说明服务器不支持断点续传
                    {
                        contentLength = httpResponseMessage.Content.Headers.ContentRange.Length;//服务器上的文件大小
                    }
    
                    using (var stream = await httpResponseMessage.Content.ReadAsStreamAsync())
                    {
                        var readLength = 1024000;//1000K
                        byte[] bytes = new byte[readLength];
                        int writeLength;
                        var beginSecond = DateTime.Now.Second;//当前时间秒
                        while ((writeLength = stream.Read(bytes, 0, readLength)) > 0 && !isPause)
                        {
                            //使用追加方式打开一个文件流
                            using (FileStream fs = new FileStream(Application.StartupPath + "/temp.rar", FileMode.Append, FileAccess.Write))
                            {
                                fs.Write(bytes, 0, writeLength);
                            }
                            downloadSpeed += writeLength;
                            rangeBegin += writeLength;
                            progressBar1.Invoke((Action)(() =>
                            {
                                var endSecond = DateTime.Now.Second;
                                if (beginSecond != endSecond)//计算速度
                                {
                                    downloadSpeed = downloadSpeed / (endSecond - beginSecond);
                                    label1.Text = "下载速度" + downloadSpeed / 1024 + "KB/S";
    
                                    beginSecond = DateTime.Now.Second;
                                    downloadSpeed = 0;//清空
                                }
                                progressBar1.Value = Math.Max((int)((rangeBegin) * 100 / contentLength), 1);
                            }));
                        }
    
                        if (rangeBegin == contentLength)
                        {
                            label1.Invoke((Action)(() =>
                            {
                                label1.Text = "下载完成";
                            }));
                        }
                    }
                }
            });
        }
        else//点击暂停
        {
            button3.Text = "继续下载";
            label1.Text = "暂停下载";
        }
    }
    

    效果图:
    www.9159.com 5

    到今日得了,你认为大家的断点续传就完了了呢?
    错,你有未有察觉大家采用的下载链接是a标签的。相当于我们团结写服务端提供的下载链接是还是不是也足以支撑断点续传呢?下边作者换个下载链接试试便知。

    断点续传(服务端的支撑)

    测验结果如下跋山涉水的近义词
    www.9159.com 6

    意识并不帮助断点续传。为何a标签链接能够一向辅助,大家写的下载却不援助啊。
    a标签的链接指向的第一手是iis上的公文(iis暗中同意扶植),而大家写的却未曾做响应报文表头Range的拍卖。(没想象中的那么智能嘛 >_<)

    前段时间我们说过,断线续传是HTTP的三个商业事务。大家服从它,它就存在,大家不遵守它也就不设有。
    那下边我们改良前面包车型大巴文书下载代码(服务端)跋山涉水的近义词

    public void FileDownload5()
    {          
        //前面可以做用户登录验证、用户权限验证等。
    
        string filename = "大数据.rar";   //客户端保存的文件名  
        string filePath = Server.MapPath("/App_Data/大数据.rar");//要被下载的文件路径 
    
        var range = Request.Headers["Range"];
        if (!string.IsNullOrWhiteSpace(range))//如果遵守协议,支持断点续传
        {
            var fileLength = new FileInfo(filePath).Length;//文件的总大小
            long begin;//文件的开始位置
            long end;//文件的结束位置
            long.TryParse(range.Split('=')[1].Split('-')[0], out begin);
            long.TryParse(range.Split('-')[1], out end);
            end = end - begin > 0 ? end : (fileLength - 1);// 如果没有结束位置,那我们读剩下的全部
    
            //表头 表明  下载文件的开始、结束位置 和文件总大小
            Response.AddHeader("Content-Range", "bytes " + begin + "-" + end + "/" + fileLength);
            Response.ContentType = "application/octet-stream";
            Response.AddHeader("Content-Disposition", "attachment;filename=" + filename);
            Response.TransmitFile(filePath, begin, (end - begin));//发送 文件开始位置读取的大小
        }
        else
        {
            Response.ContentType = "application/octet-stream";
            Response.AddHeader("Content-Disposition", "attachment;filename=" + filename);
            Response.TransmitFile(filePath);
        }
    }
    

    下一场再测量试验断点续传,完美扶持。

    八线程同临时候下载(分片下载)

    文件的断点续传已经解析完了。不过中间有个别细节的东西你能够依据实际必要去完善。如跋山涉水的近义词文件命名、断点续传的文件是还是不是发生了更改、下载完成后证实文件和服务器上的是或不是相通。
    再有大家得以依据表头属性Range来落成四线程下载,可是这里就不贴代码了,贴个职能图吧。和上神采奕奕篇文本上传里的二十四线程上传同理。您也得以依附提供的demo代码下载查看,内有完全兑现。
    www.9159.com 7

     

    参谋资料

    demo

    • https://github.com/zhaopeiym/Blog德姆oCode/tree/master/上传下载

     

    本文由9159金沙官网发布于www.9159.com,转载请注明出处:www.9159.com【转】文件下载之断点续传(客户端与

    关键词: