用C#實現HTTP協議下的多線程文件傳輸
很多人都有過使用網絡螞蟻或網絡快車軟件下載互聯網文件的經歷,這些軟件的使用可以大大加速互聯網上文件的傳輸速度,減少文件傳輸的時間。這些軟件為什么 有如此大的魔力呢?其主要原因是這些軟件都采用了多線程下載和斷點續傳技術。如果我們自己來編寫一個類似這樣的程序,也能夠快速的在互聯網上下載文件,那 一定是非常愉快的事情。下面我就講一講如何利用C#語言編寫一個支持多線程下載文件的程序,你會看到利用C#語言編寫網絡應程序是多么的容易,從中也能體 會到C#語言中強大的網絡功能。
首先介紹一下HTTP協議,HTTP亦即Hpyer Text Transfer Protocal的縮寫,它是現代互聯網上最重要的一種網絡協議,超文本傳輸協議位于TCP/IP協議的應用層,是一個面向無連接、簡單、快速的C/S結 構的協議。HTTP的工作過程大體上分連接、請求、響應和斷開連接四個步驟。C#語言對HTTP協議提供了良好的支持,在.NET類庫中提供了 WebRequest和WebResponse類,這兩個類都包含在System.Net命名空間中,利用這兩個類可以實現很多高級的網絡功能,本文中多 線程文件下載就是利用這兩個類實現的。 WebRequest和WebResponse都是抽象基類,因此在程序中不能直接作為對象使用,必須被繼承,實際使用中,可根據URI參數中的URI前 綴選用它們合適的子類,對于HTTP這類URI,HttpWebRequest和HttpWebResponse類可以用于處理客戶程序同WEB服務器之 間的HTTP通訊。
HttpWebRequest類實現了很多通過HTTP訪問WEB服務器上文件的高級功能。HttpWebRequest類對WebRequest中 定義的屬性和方法提供支持,HttpWebRequest將發送到Internet資源的公共HTTP標頭的值公開為屬性,由方法或系統設置,常用的由屬 性或方法設置的HTTP標頭為:接受, 由Accept屬性設置, 連接, 由Connection屬性和KeepAlive屬性設置, Content-Length, 由ContentLength屬性設置, Content-Type, 由ContentType屬性設置, 范圍, 由AddRange方法設置. 實際使用中是將標頭信息正確設置后,傳遞到WEB服務器,WEB服務器根據要求作出回應。
HttpWebResponse類繼承自WebResponse類,專門處理從WEB服務器返回的HTTP響應,這個類實現了很多方法,具有很多屬 性,可以全面處理接收到的互聯網信息。在HttpWebResponse類中,對于大多數通用的HTTP標頭字段,都有獨立的屬性與其對應,程序員可以通 過這些屬性方便的訪問位于HTTP接收報文標頭字段中的信息,本例中用到的HttpWebResponse類屬性為:ContentLength 既接收內容的長度。
有了以上的了解后,下面看看這兩個類的用法,要創建HttpWebRequest對象,不要直接使用HttpWebRequest的構造函數,而要使用WebRequest.Create方法初始化一個HttpWebRequest實例,如:
HttpWebRequest hwr=(HttpWebRequest)WebRequest.Create(http://www.163.com/);
創建了這個對象后,就可以通過HttpWebRequest屬性,設置很多HTTP標頭字段的內容,如hwr.AddRange(100,1000);設置接收對象的范圍為100-1000字節。
HttpWebReques對象使用GetResponse()方法時,會返回一個HttpWebResponse對象,為提出HTTP返回報文信 息,需要使用HttpWebResponse的GetResponseStream()方法,該方法返回一個Stream對象,可以讀取HTTP返回的報 文,如:首先定義一個Strean 對象 public System.IO.Stream ns; 然后 ns=hwr.GetResponse ().GetResponseStream ();即可創建Stream對象。有了以上的準備知識后下面開始設計我們的多線程互聯網文件的下載程序,首先打開Visual Studio.Net集成開發環境,選擇“文件”、“新建”、“項目”,然后選擇“Visual C#項目”,在向導右邊列表框中選中“Windows應用程序”,輸入項目名稱,如本例為:httpftp,然后選擇“確定”按鈕,向導自動生成了一個 Windows應用程序項目。首先打開窗口設計器設計應用程序窗口,增加如下控件:
一個列表框 listBox1 三個文本標簽 label1-label3 三個文本框 textBox1-textBox3 一個開始接收按鈕 button1 設計好的窗口如下圖:
控件定義代碼是:
public System.Windows.Forms.ListBox listBox1;
private System.Windows.Forms.Label label1;
private System.Windows.Forms.TextBox textBox1
private System.Windows.Forms.Button button1;
private System.Windows.Forms.Label label2;
private System.Windows.Forms.TextBox textBox2;
private System.Windows.Forms.Label label3;
private System.Windows.Forms.TextBox textBox3;
private System.Windows.Forms.Label label4;
private System.Windows.Forms.TextBox textBox4;
打開Form1的代碼編輯器,增加如下的命名空間:
using System.Net;//網絡功能
using System.IO;//流支持
using System.Threading ;//線程支持
增加如下的程序變量:
public bool[] threadw; //每個線程結束標志
public string[] filenamew;//每個線程接收文件的文件名
public int[] filestartw;//每個線程接收文件的起始位置
public int[] filesizew;//每個線程接收文件的大小
public string strurl;//接受文件的URL
public bool hb;//文件合并標志
public int thread;//進程數
定義一個HttpFile類,用于管理接收線程,其代碼如下:
public class HttpFile { public Form1 formm; public int threadh;//線程代號 public string filename;//文件名 public string strUrl;//接收文件的URL public FileStream fs; public HttpWebRequest request; public System.IO.Stream ns; public byte[] nbytes;//接收緩沖區 public int nreadsize;//接收字節數 public HttpFile(Form1 form,int thread)//構造方法 { formm=form; threadh=thread; } ~HttpFile()//析構方法 { formm.Dispose (); } public void receive()//接收線程 { filename=formm.filenamew[threadh]; strUrl=formm.strurl; ns=null; nbytes= new byte[512]; nreadsize=0; formm.listBox1 .Items .Add ("線程"+threadh.ToString ()+"開始接收"); fs=new FileStream (filename,System.IO.FileMode.Create); try { request=(HttpWebRequest)HttpWebRequest.Create (strUrl); //接收的起始位置及接收的長度 request.AddRange(formm.filestartw [threadh], formm.filestartw [threadh]+formm.filesizew [threadh]); ns=request.GetResponse ().GetResponseStream ();//獲得接收流 nreadsize=ns.Read (nbytes,0,512); while (nreadsize>0) { fs.Write (nbytes,0,nreadsize); nreadsize=ns.Read (nbytes,0,512); formm.listBox1 .Items .Add ("線程"+threadh.ToString ()+"正在接收"); } fs.Close(); ns.Close (); } catch (Exception er) { MessageBox.Show (er.Message ); fs.Close(); } formm.listBox1 .Items.Add ("進程"+threadh.ToString ()+"接收完畢!"); formm.threadw[threadh]=true; } }
該類和Form1類處于統一命名空間,但不包含在Form1類中。下面定義“開始接收”按鈕控件的事件響應函數:
private void button1_Click(object sender, System.EventArgs e) { DateTime dt=DateTime.Now;//開始接收時間 textBox1.Text =dt.ToString (); strurl=textBox2.Text .Trim ().ToString (); HttpWebRequest request; long filesize=0; try { request=(HttpWebRequest)HttpWebRequest.Create (strurl); filesize=request.GetResponse ().ContentLength;//取得目標文件的長度 request.Abort (); } catch (Exception er) { MessageBox.Show (er.Message ); } // 接收線程數 thread=Convert.ToInt32 (textBox4.Text .Trim().ToString (),10); //根據線程數初始化數組 threadw=new bool [thread]; filenamew=new string [thread]; filestartw=new int [thread]; filesizew=new int[thread]; //計算每個線程應該接收文件的大小 int filethread=(int)filesize/thread;//平均分配 int filethreade=filethread+(int)filesize%thread;//剩余部分由最后一個線程完成 //為數組賦值 for (int i=0;i<thread;i++) { threadw[i]=false;//每個線程狀態的初始值為假 filenamew[i]=i.ToString ()+".dat";//每個線程接收文件的臨時文件名 if (i<thread-1) { filestartw[i]=filethread*i;//每個線程接收文件的起始點 filesizew[i]=filethread-1;//每個線程接收文件的長度 } else { filestartw[i]=filethread*i; filesizew[i]=filethreade-1; } } //定義線程數組,啟動接收線程 Thread[] threadk=new Thread [thread]; HttpFile[] httpfile=new HttpFile [thread]; for (int j=0;j<thread;j++) { httpfile[j]=new HttpFile(this,j); threadk[j]=new Thread(new ThreadStart (httpfile[j].receive )); threadk[j].Start (); } //啟動合并各線程接收的文件線程 Thread hbth=new Thread (new ThreadStart (hbfile)); hbth.Start (); } 合并文件的線程hbfile定義在Form1類中,定義如下: public void hbfile() { while (true)//等待 { hb=true; for (int i=0;i<thread;i++) { if (threadw[i]==false)//有未結束線程,等待 { hb=false; Thread.Sleep (100); break; } } if (hb==true)//所有線程均已結束,停止等待, { break; } } FileStream fs;//開始合并 FileStream fstemp; int readfile; byte[] bytes=new byte[512]; fs=new FileStream (textBox3.Text .Trim ().ToString (),System.IO.FileMode.Create); for (int k=0;k0) { fs.Write (bytes,0,readfile); } else { break; } } fstemp.Close (); } fs.Close (); DateTime dt=DateTime.Now; textBox1.Text =dt.ToString ();//結束時間 MessageBox.Show ("接收完畢!!!"); }
至此,一個多線程下載文件的程序就大功告成了,注意在輸入本地文件名時,應按如下格式輸入:“c:\\test\\httpftp\\bin \\d.htm”,因”\”后的字符在C#中是轉義字符,線程數并非越大越好,一般5個線程就可以了,該程序在Visual Studio.Net 2002開發環境及Windows xp 操作系統上通過。