zoukankan      html  css  js  c++  java
  • C#编程断点续传

    C#编程总结(十二)断点续传

    Posted on 2014-02-16 10:56 停留的风 阅读(384) 评论(3编辑 收藏

    C#编程总结(十二)断点续传  

    我们经常使用下载工具,如bit精灵、迅雷、FlashGet,这些软件都支持断点续传。

    断点续传即下载任务暂停后可以继续,而无需重新下载,即下载时需要通知服务器的起始位置。如果允许多线程进行分片下载,必须提供起始-截止位置。说到底就是可以选择下载某个片段,整个文件的字节流,可以截取流的片段,也能实现流的累积,最终完成文件下载。

    一、原理

    在 HTTP/1.1里新增的一个头属性:Range,也是现在众多号称多线程下载工具(如 FlashGet、迅雷等)实现多线程下载的核心所在。老版本的HTTP协议不支持,所以一些老的服务器还不支持断点续传。

    Range(请求参数)

    用于请求头中,指定第一个字节的位置和最后一个字节的位置,一般格式:

    Range:(unit=first byte pos)-[last byte pos] 

    例如:Range:100-199,取文件流的100至199之间的字节。

    Range:100,取位置为100后的所有字节。如果range 为正值,服务器应该开始发送从指定的 range 参数到 HTTP 实体中数据的末尾之间的数据。

    Range:-99,取开始的100个字节。如果range 为负值,服务器应该开始发送从 HTTP 实体中数据的开头到指定的 range 参数之间的数据。

    Content-Range (响应参数)

    用于响应头,指定整个实体中的一部分的插入位置,他也指示了整个实体的长度。在服务器向客户返回一个部分响应,它必须描述响应覆盖的范围和整个实体长度。

    一般格式:   

    Content-Range: bytes (unit first byte pos) - [last byte pos]/[entity legth] 

    例如:Content-Range: bytes 1024000-1126399/7421120

    HTTP协议:http://www.w3.org/Protocols/rfc2616/rfc2616.html

    二、C#中实现

    在C#中使用AddRange方法向请求添加指定范围的字节范围标头

    System.Net.HttpWebRequest

    所有的方法:

    常用的方法为例:

    void AddRange(long from, long to);
    指定范围起始、终止位置,来请求该片段的数据。
    复制代码
            // 摘要:
            //     向请求添加指定范围的字节范围标头。
            //
            // 参数:
            //   from:
            //     开始发送数据的位置。
            //
            //   to:
            //     停止发送数据的位置。
            public void AddRange(long from, long to);
    复制代码

     

    我们通过该方法,基于HTTP协议实现了断点续传,支持暂停、继续下载功能,为了更清晰显示效果,提供了进度条显示。效果图:

    Request

     请求的Range参数,我们可以清晰看到起具体值,这就是请求的片段。在这里可以清晰的看到HTTP协议版本、请求方法、请求地址等信息

    Response

    服务器根据请求的Range参数,只返回该片段的数据。我们可以清晰看到Content-Range的具体值。

    注意:返回的StatusCode变为了PartialContent,说明是部分数据。

    代码 含义
    200 OK     请求成功返回
    206 Partial Content     部分数据

     下载的核心代码:

    复制代码
            /// <summary>
            /// 下载
            /// </summary>
            public void Download()
            {
                //从0计数,需要减一
                long from = this.currentSize;
                if (from < 0)
                {
                    from = 0;
                }
    
                long to = this.currentSize + this.step - 1;
                if (to >= this.totalSize && this.totalSize > 0)
                {
                    to = this.totalSize - 1;
                }
                this.Download(from, to);
            }
            /// <summary>
            /// 下载
            /// </summary>
            /// <param name="url"></param>
            /// <param name="range"></param>
            public void Download(long from,long to)
            {
                if (this.totalSize == 0)
                {
                    GetTotalSize();
                }
                if (from >= this.totalSize || this.currentSize >= this.totalSize)
                {
                    this.isFinished = true;
                    return;
                }
    
                HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(url);
                //request.Method = "GET";            
                request.AddRange("bytes", from, to);
    
                HttpWebResponse response = (HttpWebResponse)request.GetResponse();
                string result = string.Empty;
                if (response != null)
                {
                    byte[] buffer = this.Buffer;
                    using (Stream stream = response.GetResponseStream())
                    {
                        int readTotalSize = 0;
                        int size = stream.Read(buffer, 0, buffer.Length);
                        while (size > 0)
                        {
                            //只将读出的字节写入文件
                            fs.Write(buffer, 0, size);
                            readTotalSize += size;
                            size = stream.Read(buffer, 0, buffer.Length);
                        }
    
                        //更新当前进度
                        this.currentSize += readTotalSize;
    
                        //如果返回的response头中Content-Range值为空,说明服务器不支持Range属性,不支持断点续传,返回的是所有数据
                        if (response.Headers["Content-Range"] == null)
                        {
                            this.isFinished = true;
                        }
                    }
                }
            }
    复制代码

      

    项目源码:

    http://files.cnblogs.com/yank/DownloadSample.rar

    三、多线程下载

    上述例子提供了简单的断点续传功能,如果想再进一步实现多线程下载。原理很简单,我们只需根据所要下载的文件大小,进行分块,没块启动一个线程进行下载,线程只负责下载自己负责的片段,一定要严格设置Range的值。具体实现这里不再介绍,如有兴趣,下来可以继续研究。

    提醒一下:多线程下载的字节流如何保存为文件,是否必须按照先后顺序?

  • 相关阅读:
    BZOJ1001 BJOI2006 狼抓兔子
    NOI2015 程序自动分析
    高斯消元法
    [POJ] 3666 Making the Grade
    [CODEVS] 2185 最长公共上升子序列
    [模板] LIS
    [CF] 219D Choosing Capital for Treeland
    [POJ] 2823 Sliding Window
    [BZOJ] 1072 [SCOI2007]排列perm
    [POJ] 1094 Sorting It All Out
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/3551790.html
Copyright © 2011-2022 走看看