用 C# 實現 HTTP 協議多線程下載文件


本文內容

  • 環境
  • Internet 請求
  • 演示
  • 參考資料
  • 修改記錄

 

環境


  • 開發工具:VS 2010/.NET Framework 4.0
  • 系統環境:Microsoft Windows 7

 

Internet 請求


應用程序通過 WebRequest.Create 方法創建 WebRequest 實例。該方法是靜態方法,基於傳遞的 URI 創建從 WebRequest 派生的類。

NET Framework 提供 HttpWebRequest 類,它派生自 WebRequest,來處理 HTTP 和 HTTPS 請求。在大多數情況下,WebRequest 類提供了你發出請求的所有屬性,但是,如果需要的話,你可以把 WebRequest 對象強制類型轉換成 HttpWebRequest,以訪問請求的 HTTP 屬性。類似地,HttpWebResponse 對象來處理 HTTP 和 HTTPS 請求的響應。若訪問 HttpWebResponse 對象的屬性,需要把 WebResponse 對象強制類型轉換成 HttpWebResponse。

.NET Framework 還提供了 FileWebRequestFileWebResponse 類,來處理使用 "file:" 資源的請求。類似地,FtpWebRequestFtpWebResponse 類用戶 "ftp:"。

若處理使用應用程序協議的請求,需要實現從 WebRequest 和 WebResponse 派生的特定協議類。

說明:不能混淆 HttpWebResponse 和 HttpResponse 類;后者用於 ASP.NET 應用程序,而且它的方法和屬性是通過 ASP.NET 的內部 Response 對象公開的。

 

演示


下面演示利用 HTTP 協議編寫一個多線程下載本文源代碼的程序,源代碼地址為 http://files.cnblogs.com/liuning8023/HttpDownload.rar

1,新建項目 "HttpDownload";

2,Form1.Desginer.cs 代碼如下所示:

namespace HttpDownload
{
    partial class Form1
    {
        /// <summary>
        /// 必需的設計器變量。
        /// </summary>
        private System.ComponentModel.IContainer components = null;
 
        /// <summary>
        /// 清理所有正在使用的資源。
        /// </summary>
        /// <param name="disposing">如果應釋放托管資源,為 true;否則為 false。</param>
        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }
 
        #region Windows 窗體設計器生成的代碼
 
        /// <summary>
        /// 設計器支持所需的方法 - 不要
        /// 使用代碼編輯器修改此方法的內容。
        /// </summary>
        private void InitializeComponent()
        {
            this.lst_processing = new System.Windows.Forms.ListBox();
            this.lbl_url = new System.Windows.Forms.Label();
            this.lbl_localFile = new System.Windows.Forms.Label();
            this.lbl_threadNum = new System.Windows.Forms.Label();
            this.txt_url = new System.Windows.Forms.TextBox();
            this.txt_localFile = new System.Windows.Forms.TextBox();
            this.txt_threadNum = new System.Windows.Forms.TextBox();
            this.btn_rec = new System.Windows.Forms.Button();
            this.txt_overTime = new System.Windows.Forms.TextBox();
            this.lbl_overTime = new System.Windows.Forms.Label();
            this.SuspendLayout();
            // 
            // lst_processing
            // 
            this.lst_processing.FormattingEnabled = true;
            this.lst_processing.ItemHeight = 12;
            this.lst_processing.Location = new System.Drawing.Point(12, 12);
            this.lst_processing.Name = "lst_processing";
            this.lst_processing.Size = new System.Drawing.Size(342, 364);
            this.lst_processing.TabIndex = 0;
            // 
            // lbl_url
            // 
            this.lbl_url.AutoSize = true;
            this.lbl_url.Location = new System.Drawing.Point(377, 17);
            this.lbl_url.Name = "lbl_url";
            this.lbl_url.Size = new System.Drawing.Size(47, 12);
            this.lbl_url.TabIndex = 1;
            this.lbl_url.Text = "文件URL";
            // 
            // lbl_localFile
            // 
            this.lbl_localFile.AutoSize = true;
            this.lbl_localFile.Location = new System.Drawing.Point(377, 42);
            this.lbl_localFile.Name = "lbl_localFile";
            this.lbl_localFile.Size = new System.Drawing.Size(53, 12);
            this.lbl_localFile.TabIndex = 2;
            this.lbl_localFile.Text = "本地地址";
            // 
            // lbl_threadNum
            // 
            this.lbl_threadNum.AutoSize = true;
            this.lbl_threadNum.Location = new System.Drawing.Point(377, 74);
            this.lbl_threadNum.Name = "lbl_threadNum";
            this.lbl_threadNum.Size = new System.Drawing.Size(41, 12);
            this.lbl_threadNum.TabIndex = 3;
            this.lbl_threadNum.Text = "線程數";
            // 
            // txt_url
            // 
            this.txt_url.Location = new System.Drawing.Point(450, 17);
            this.txt_url.Name = "txt_url";
            this.txt_url.Size = new System.Drawing.Size(353, 21);
            this.txt_url.TabIndex = 4;
            this.txt_url.Text = "http://files.cnblogs.com/liuning8023/HttpDownload.rar";
            // 
            // txt_localFile
            // 
            this.txt_localFile.Location = new System.Drawing.Point(450, 44);
            this.txt_localFile.Name = "txt_localFile";
            this.txt_localFile.Size = new System.Drawing.Size(232, 21);
            this.txt_localFile.TabIndex = 5;
            this.txt_localFile.Text = "c:////download.rar";
            // 
            // txt_threadNum
            // 
            this.txt_threadNum.Location = new System.Drawing.Point(450, 71);
            this.txt_threadNum.Name = "txt_threadNum";
            this.txt_threadNum.Size = new System.Drawing.Size(232, 21);
            this.txt_threadNum.TabIndex = 6;
            this.txt_threadNum.Text = "5";
            // 
            // btn_rec
            // 
            this.btn_rec.Location = new System.Drawing.Point(607, 137);
            this.btn_rec.Name = "btn_rec";
            this.btn_rec.Size = new System.Drawing.Size(75, 23);
            this.btn_rec.TabIndex = 7;
            this.btn_rec.Text = "接收";
            this.btn_rec.UseVisualStyleBackColor = true;
            this.btn_rec.Click += new System.EventHandler(this.btn_rec_Click);
            // 
            // txt_overTime
            // 
            this.txt_overTime.Location = new System.Drawing.Point(450, 99);
            this.txt_overTime.Name = "txt_overTime";
            this.txt_overTime.Size = new System.Drawing.Size(232, 21);
            this.txt_overTime.TabIndex = 8;
            // 
            // lbl_overTime
            // 
            this.lbl_overTime.AutoSize = true;
            this.lbl_overTime.Location = new System.Drawing.Point(377, 102);
            this.lbl_overTime.Name = "lbl_overTime";
            this.lbl_overTime.Size = new System.Drawing.Size(53, 12);
            this.lbl_overTime.TabIndex = 9;
            this.lbl_overTime.Text = "結束時間";
            // 
            // Form1
            // 
            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F);
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            this.ClientSize = new System.Drawing.Size(815, 384);
            this.Controls.Add(this.lbl_overTime);
            this.Controls.Add(this.txt_overTime);
            this.Controls.Add(this.btn_rec);
            this.Controls.Add(this.txt_threadNum);
            this.Controls.Add(this.txt_localFile);
            this.Controls.Add(this.txt_url);
            this.Controls.Add(this.lbl_threadNum);
            this.Controls.Add(this.lbl_localFile);
            this.Controls.Add(this.lbl_url);
            this.Controls.Add(this.lst_processing);
            this.Name = "Form1";
            this.Text = "Form1";
            this.ResumeLayout(false);
            this.PerformLayout();
 
        }
 
        #endregion
 
        public System.Windows.Forms.ListBox lst_processing;
        private System.Windows.Forms.Label lbl_url;
        private System.Windows.Forms.Label lbl_localFile;
        private System.Windows.Forms.Label lbl_threadNum;
        private System.Windows.Forms.TextBox txt_url;
        private System.Windows.Forms.TextBox txt_localFile;
        private System.Windows.Forms.TextBox txt_threadNum;
        private System.Windows.Forms.Button btn_rec;
        private System.Windows.Forms.TextBox txt_overTime;
        private System.Windows.Forms.Label lbl_overTime;
    }
}

說明:

1)  添加四個 Lable 控件和 TextBox 控件;一個 ListBox 控件;一個 Button 控件;

2)  ListBox 控件需要將 private 屬性改為 public,以便在外部使用。

3,新建 HttpMultiThreadDownload.cs 類,代碼如下:

using System;
using System.Net;
using System.IO;
using System.Windows.Forms;
 
namespace HttpDownload
{
    /// <summary>
    /// 調用外部窗體
    /// </summary>
    /// <param name="text"></param>
    delegate void ProcessingCallback(string processing);
 
    public class HttpMultiThreadDownload
    {
        const int _bufferSize = 512;
        public Form1 frm { get; set; }
        public int ThreadId { get; set; }             // 線程 ID                 
        public string Url { get; set; }               // 文件 Url
 
        public HttpMultiThreadDownload(Form1 form, int threadId)
        {
            frm = form;
            ThreadId = threadId;
            Url = frm.Url;
        }
        /// <summary>
        /// 析構方法
        /// </summary>
        ~HttpMultiThreadDownload()
        {
            if (!frm.InvokeRequired)
            {
                frm.Dispose();
            }
        }
        /// <summary>
        /// 接收
        /// </summary>
        public void receive()
        {
            string filename = frm.fileNames[ThreadId];     // 線程臨時文件
            byte[] buffer = new byte[_bufferSize];         // 接收緩沖區
            int readSize = 0;                              // 接收字節數
            FileStream fs = new FileStream(filename, System.IO.FileMode.Create);
            Stream ns = null;
 
            this.SetListBox("線程[" + ThreadId.ToString() + "] 開始接收......");
            try
            {
                HttpWebRequest request = (HttpWebRequest)WebRequest.Create(Url);
                request.AddRange(frm.StartPos[ThreadId], frm.StartPos[ThreadId] + frm.fileSize[ThreadId]);
                ns = request.GetResponse().GetResponseStream();
                readSize = ns.Read(buffer, 0, _bufferSize);
                this.SetListBox("線程[" + ThreadId.ToString() + "] 正在接收 " + readSize);
                while (readSize > 0)
                {
                    fs.Write(buffer, 0, readSize);
                    readSize = ns.Read(buffer, 0, _bufferSize);
                    this.SetListBox("線程[" + ThreadId.ToString() + "] 正在接收 " + readSize);
                }
                fs.Close();
                ns.Close();
            }
            catch (Exception er)
            {
                MessageBox.Show(er.Message);
                fs.Close();
            }
            this.SetListBox("進程[" + ThreadId.ToString() + "] 結束!");
            frm.threadStatus[ThreadId] = true;
        }
        private void SetListBox(string processing)
        {
            if (frm.lst_processing.InvokeRequired)
            {
                ProcessingCallback d = new ProcessingCallback(SetListBox);
                frm.Invoke(d, new object[] { processing });
            }
            else
            {
                frm.lst_processing.Items.Add(processing);
            }
        }
    }
}

說明:

1)該類使用了析構函數。通常,.NET Framework 垃圾回收器會隱式地管理對象的內存分配和釋放。 但是,當應用程序封裝窗口、文件和網絡連接這類非托管資源時,應當使用析構函數釋放這些資源。 當對象符合析構時,垃圾回收器將運行對象的 Finalize 方法。

參考:http://msdn.microsoft.com/zh-cn/library/66x5fx1b.aspx

2)另外,SetListBox 方法確保以線程安全方式訪問 ListBox 控件。

參考:http://msdn.microsoft.com/zh-cn/library/ms171728(v=VS.90).aspx

3)多線程下載的關鍵在於能夠定位下載文件中流的指定位置,例如 HttpWebRequest.AddRange 方法,從而讓每個線程下載你指定的位置。該方式的下載類似於在瀏覽器中右鍵的“另存為”,不能用該方式下載類似 HTML 文件,因為這樣的流是不能尋址,不能定位的。

4,Form1.cs 代碼如下所示:

using System;
using System.Net;
using System.IO;
using System.Windows.Forms;
 
namespace HttpDownload
{
    public partial class Form1 : Form
    {
        public int threadNum { get; set; }          // 進程
        public bool[] threadStatus { get; set; }    // 每個線程結束標志
        public string[] fileNames { get; set; }     // 每個線程接收文件的文件名
        public int[] StartPos { get; set; }     // 每個線程接收文件的起始位置
        public int[] fileSize { get; set; }         // 每個線程接收文件的大小
        public string Url { get; set; }             // 接受文件的URL
        public bool HasMerge { get; set; }           // 文件合並標志
 
        public Form1()
        {
            InitializeComponent();
        }
        /// <summary>
        ///
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btn_rec_Click(object sender, EventArgs e)
        {
            Url = this.txt_url.Text.Trim().ToString();
            long fileSizeAll = 0;
 
            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(this.Url);
            fileSizeAll = request.GetResponse().ContentLength;
            request.Abort();
            threadNum = int.Parse(this.txt_threadNum.Text.Trim().ToString());
            Init(fileSizeAll);
            for (int i = 0; i < threadNum; i++)
            {
                this.lst_processing.Items.Add("線程[" + i + "]:" + (threadStatus[i] ? "結束" : "開始......"));
                this.lst_processing.Items.Add(("開始位置:" + StartPos[i].ToString()).PadLeft(20, ' ') +
                    ("總大小:" + fileSize[i].ToString()).PadLeft(20, ' '));
            }
            this.lst_processing.Items.Add("------------------------------文件總大小:" + fileSizeAll);
 
            // 定義並啟動線程數組
            System.Threading.Thread[] threads = new System.Threading.Thread[threadNum];
            HttpMultiThreadDownload[] httpDownloads = new HttpMultiThreadDownload[threadNum];
            for (int i = 0; i < threadNum; i++)
            {
                httpDownloads[i] = new HttpMultiThreadDownload(this, i);
                threads[i] = new System.Threading.Thread(new System.Threading.ThreadStart(httpDownloads[i].receive));
                threads[i].Start();
            }
            System.Threading.Thread merge = new System.Threading.Thread(new System.Threading.ThreadStart(MergeFile));
            merge.Start();
            this.txt_overTime.Text = DateTime.Now.ToString();
        }
        /// <summary>
        /// 初始化
        /// </summary>
        /// <remarks>
        /// 每個線程平均分配文件大小,剩余部分由最后一個線程完成
        /// </remarks>
        /// <param name="filesize"></param>
        private void Init(long filesize)
        {
            threadStatus = new bool[threadNum];
            fileNames = new string[threadNum];
            StartPos = new int[threadNum];
            fileSize = new int[threadNum];
            int filethread = (int)filesize / threadNum;
            int filethreade = filethread + (int)filesize % threadNum;
            for (int i = 0; i < threadNum; i++)
            {
                threadStatus[i] = false;
                fileNames[i] = i.ToString() + ".dat";
                if (i < threadNum - 1)
                {
                    StartPos[i] = filethread * i;
                    fileSize[i] = filethread - 1;
                }
                else
                {
                    StartPos[i] = filethread * i;
                    fileSize[i] = filethreade - 1;
                }
            }
        }
        /// <summary>
        /// 合並文件
        /// </summary>
        public void MergeFile()
        {
            while (true)
            {
                HasMerge = true;
                for (int i = 0; i < threadNum; i++)
                {
                    if (threadStatus[i] == false) // 若有未結束線程,則等待
                    {
                        HasMerge = false;
                        System.Threading.Thread.Sleep(100);
                        break;
                    }
                }
                if (HasMerge == true) // 否則,停止等待
                    break;
            }
 
            int bufferSize = 512;
            int readSize;
            string downFileNamePath = txt_localFile.Text.Trim().ToString();
            byte[] bytes = new byte[bufferSize];
            FileStream fs = new FileStream(downFileNamePath, FileMode.Create);
            FileStream fsTmp = null;
 
            for (int k = 0; k < threadNum; k++)
            {
                fsTmp = new FileStream(fileNames[k], FileMode.Open);
                while (true)
                {
                    readSize = fsTmp.Read(bytes, 0, bufferSize);
                    if (readSize > 0)
                        fs.Write(bytes, 0, readSize);
                    else
                        break;
                }
                fsTmp.Close();
            }
            fs.Close();
            MessageBox.Show("接收完畢!!!");
        }
    }
}

程序運行結果如下圖所示:

2012-10-07_103453

 

參考資料


 

修改記錄


  • 2012-10-07 [ADD][UPDATE]
  • 2012-10-08 [UPDATE]

下載 Demo


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM