多线程与断点续传

引自
http://www.devdiv.com/thread-20925-1-1.html





本帖最后由 wt0731 于 2010-1-15 09:01 编辑


起因:公司有个项目用到了断点续传,之前开发了一套但是由于现场环境的问题导致不能用(原理是用aspnet_isapi.dll来响应PUT和POST请求),后来考虑换个模式,当中要感谢帮助过我的人(pkwsh、egmkang等)
1、断点续传的原理(来自网络)
在了解HTTP断点续传的原理之前,先来说说HTTP协议,HTTP协议是一种基于tcp的简单协议,分为请求和回复两种。
请求协议是由客户机(浏览器)向服务器(WEB SERVER)提交请求时发送报文的协议。回复协议是由服务器(web ser
ver),向客户机(浏览器)回复报文时的协议。请求和回复协议都由头和体组成。头和体之间以一行空行为分隔。
以下是一个请求报文与相应的回复报文的例子:
GET /image/index_r4_c1.jpg HTTP/1.1
Accept: */*
Referer: http://192.168.3.120:8080/
Accept-Language: zh-cn
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0; .NET CLR 1.0.3705)
Host: 192.168.3.120:8080
Connection: Keep-Alive
HTTP/1.1 200 OK
Server: Microsoft-IIS/5.0
Date: Tue, 24 Jun 2003 05:39:40 GMT
Content-Type: image/jpeg
Accept-Ranges: bytes
Last-Modified: Thu, 23 May 2002 03:05:40 GMT
ETag: "bec48eb862c21:934"
Content-Length: 2827
….

下面我们就来说说“断点续传”。
顾名思义,断点续传就是在上一次下载时断开的位置开始继续下载。在HTTP协议中,可以在请求报文头中加入Rang
e段,来表示客户机希望从何处继续下载。
比如说从第1024字节开始下载,请求报文如下:

GET /image/index_r4_c1.jpg HTTP/1.1
Accept: */*
Referer: http://192.168.3.120:8080/
Accept-Language: zh-cn
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0; .NET CLR 1.0.3705)
Host: 192.168.3.120:8080
Range:bytes=1024-
Connection: Keep-Alive

.NET中的相关类
明白了上面的原理,那么,我们来看看.NET FRAMEWORK中为我们提供了哪些类可以来做这些事。

完成HTTP请求
System.Net.HttpWebRequest

HttpWebRequest 类对 WebRequest 中定义的属性和方法提供支持,也对使用户能够直接与使用 HTTP 的服务器交互
的附加属性和方法提供支持。

HttpWebRequest 将发送到 Internet 资源的公共 HTTP 标头值公开为属性,由方法或系统设置。下表包含完整列表
。可以将 Headers 属性中的其他标头设置为名称/值对。但是注意,某些公共标头被视为受限制的,它们或者直接
由 API公开,或者受到系统保护,不能被更改。Range也属于被保护之列,不过,.NET为开发者提供了更方便的操作
,就是 AddRange方法,向请求添加从请求数据的开始处或结束处的特定范围的字节范围标头

完成文件访问
System.IO.FileStream

FileStream 对象支持使用Seek方法对文件进行随机访问, Seek 允许将读取/写入位置移动到文件中的任意位置。这
是通过字节偏移参考点参数完成的。字节偏移量是相对于查找参考点而言的,该参考点可以是基础文件的开始、当
前位置或结尾,分别由SeekOrigin类的三个属性表示。

2、网上的可以跑的示例
代码实现
了解了.NET提供的相关的类,那么,我们就可以方便的实现了。

代码如下:

static void Main(string[] args)
              {
                     string StrFileName="c:\\aa.zip";      //根据实际情况设置
                     string StrUrl="http://www.xxxx.cn/xxxxx.zip";   //根据实际情况设置


                     //打开上次下载的文件或新建文件
                     long lStartPos =0;
                     System.IO.FileStream fs;
                     if (System.IO.File.Exists(StrFileName))
                     {
                            fs= System.IO.File.OpenWrite(StrFileName);
                            lStartPos=fs.Length;
                            fs.Seek(lStartPos,System.IO.SeekOrigin.Current);   //移动文件流中的当前指

                     }
                     else
                     {
                            fs = new System.IO.FileStream(StrFileName,System.IO.FileMode.Create);
                            lStartPos =0;
                     }
                     //打开网络连接
                     try
                     {
                            System.Net.HttpWebRequest request =(System.Net.HttpWebRequest)System.Net
.HttpWebRequest.Create(StrUrl);
                            if ( lStartPos>0)
                                   request.AddRange((int)lStartPos);    //设置Range值
                            //向服务器请求,获得服务器回应数据流
                            System.IO.Stream ns= request.GetResponse().GetResponseStream();
                            byte[] nbytes = new byte[512];
                            int nReadSize=0;
                            nReadSize=ns.Read(nbytes,0,512);
                            while( nReadSize >0)
                            {
                                   fs.Write(nbytes,0,nReadSize);
                                   nReadSize=ns.Read(nbytes,0,512);
                            }
                            fs.Close();
                            ns.Close();
                            Console.WriteLine("下载完成");
                     }
                     catch(Exception ex)
                     {
                            fs.Close();
                            Console.WriteLine("下载过程中出现错误:"+ex.ToString());
                     }
3、服务端的配置

   配置一个站点,并给Asp.Net和NetWorkService用读取并运行权限
4、完善(加上简单逻辑、多线程)
#region 作者:wt0731, 2010-01-02
/*
* 版本 :1.0
* 维护者:
* 最近维护日期:
*
*/
#endregion
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Threading;

namespace DownloadFile
{
    public partial class FormDownLoad : Form
    {
        private Thread threadDownload = null;
        private Thread threadUpload = null;
        /// <summary>
        /// 下载路径
        /// </summary>
        private string DownloadPath = "http://192.168.100.46/HttpHandle/2.rar";//根据实际需要修改

        /// <summary>
        /// 本地存储路径
        /// </summary>
        private string localSourceFile = string.Empty;
        /// <summary>
        /// 暂停
        /// </summary>
        private bool dlThreadContinue = false;
        public FormDownLoad()
        {
            InitializeComponent();
            this.btnStop.Enabled = false;
        }

        private void btnDownLoad_Click(object sender, EventArgs e)
        {
            Cursor.Current = Cursors.WaitCursor;
            this.btnDownLoad.Enabled = false;
            this.dlThreadContinue = true;
            this.btnStop.Enabled = true;
            localSourceFile = System.Environment.GetFolderPath(Environment.SpecialFolder.Personal) + "\\" + this.txtName.Text.ToString(); //根据实际情况设置
            DownloadPath = this.txtUrl.Text.ToString(); //根据实际情况设置
            threadDownload = new Thread(new ThreadStart(DownloadThread));
            threadDownload.Start();           
        }

        /// <summary>
        /// 下载线程的入口函数
        /// </summary>
        void DownloadThread()
        {         
             DownloadFile(this.DownloadPath,this.localSourceFile);        
        }

         /// <summary>
        /// 下载文件,无压缩传输
        /// </summary>
        /// <param name="serverUrl">服务端处理Http请求的地址</param>param>
        /// <param name="fileName">本地存储的文件名</param>
        private void DownloadFile(string serverUrl, string fileName)    
        {
            //打开上次下载的文件或新建文件
            long lStartPos = 0;
            System.IO.FileStream fs;
            if (System.IO.File.Exists(fileName))
            {
                fs = System.IO.File.OpenWrite(fileName);
                lStartPos = fs.Length;
                fs.Seek(lStartPos, System.IO.SeekOrigin.Current); //移动文件流中的当前指针
            }
            else
            {
                fs = new System.IO.FileStream(fileName, System.IO.FileMode.Create);
                lStartPos = 0;
            }
            //打开网络连接
            try
            {
                System.Net.HttpWebRequest request = (System.Net.HttpWebRequest)System.Net.HttpWebRequest.Create(serverUrl);
                request.KeepAlive = true;
                if (lStartPos > 0)
                    request.AddRange((int)lStartPos); //设置Range值 
                System.Text.Encoding utf8 = System.Text.Encoding.Default;
                //向服务器请求,获得服务器回应数据流
                System.IO.Stream ns = request.GetResponse().GetResponseStream();

                byte[] nbytes = new byte[1024];
                int nReadSize = 0;
                nReadSize = ns.Read(nbytes, 0, 1024);
                while (nReadSize > 0 && dlThreadContinue)
                {
                    fs.Write(nbytes, 0, nReadSize);
                    nReadSize = ns.Read(nbytes, 0, 1024);
                }
                //fs.Flush();
                fs.Close();
                ns.Close();
                Cursor.Current = Cursors.Default;
                MessageBox.Show("下载完成", "系统提示");
            }
            catch (Exception ex)
            {
                Cursor.Current = Cursors.Default;
                fs.Close();
                MessageBox.Show("下载过程中出现错误:" + ex.ToString(), "系统提示");
            }
        }
        private void menuItem1_Click(object sender, EventArgs e)
        {
            Application.Exit();
        }

        private void btnStop_Click(object sender, EventArgs e)
        {
            if (this.btnStop.Text == "暂停")
            {
                this.dlThreadContinue = false;
                Cursor.Current = Cursors.Default;
                this.btnDownLoad.Enabled = true;
                this.btnStop.Enabled = false;
            }                     
        }      
    }
}
复制代码
扩展资料:
HttpWebRequest&HttpWebResponse Headers
介绍
这里简要介绍如何使用HttpWebRequest&HttpWebResponse两个对象与HTTP服务器进行直接交互的过程.HttpWebRequest类对WebRequest中定义的属性和方法提供支持,在使用HttpWebRequest对象向HTTP服务器发起请求时请不要使用HttpWebRequest对象的构造函数,而应该使用WebRequest.Create()方法来初始化新的HttpWebRequest对象.如果统一资源标识符方案是"http://"或"https://"时,Create()则返回HttpWebResponse对象.
代码
首先,我们需要创建一个新的HttpWebRequest对象,代码如下:
HttpWebRequest myrequest = (HttpWebRequest)WebRequest.Create(new Uri("urlstring"));
注意:上文中已经提到过不要使用HttpWebRequest的构造函数来创建对象;使用WebRequest.Create()方法初始化HttpWebRequest对象时应该对其进行类型转换.
接下来可以对新初始化的对象进行简单操作,比如可以设置它的标头属性,
下表列出了由属性或方法设置或由系统设置的标头:
________________________________________

标头         设置方法
Accept         由Accept属性设置
Connection         由Connection属性和KeepAlive属性设置
Content-Length         由ContentLength属性设置
Content-Type         由ContentType属性设置
Expect         由Expect属性设置
Date         由系统设置为当前日期
Host         由系统设置为当前主机信息
If-Modified-Since         由IfModifiedSince属性设置
Range         由Range属性设置
Transfer-Encoding         由TransferEncoding属性设置
Referer         由Referer属性设置
User-Agent         由UserAgent属性设置
________________________________________
注意:HttpWebRequest自动注册.使用以"http://"或"https://"开头的URL之前,不需要调用RegisterPrefix方法来注册
源码下载:

你可能感兴趣的:(断点续传)