因為有些人可能會疑惑,將了這么多多線程,到底在實際的應用上有什么作用的呢? 這里我在這里用多線程簡單實現了一個文件的下載的功能。
服務器端頁面:
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="FileServer.Default" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head runat="server"> <title></title> </head> <body> <form id="form1" runat="server"> <div> <asp:Image ID="Image1" runat="server" ImageUrl="~/Images/1.gif" /> 說明: CLR Via C# </div> </form> </body> </html>
服務器頁面只是一個簡單顯示需要下載文件的一些信息,這里通過Handler.ashx來處理文件的下載,把文件的轉化為二進制字節寫入到輸出流中,具體實現代碼為:
public class Handle : IHttpHandler { public void ProcessRequest(HttpContext context) { HttpResponse response = context.Response; HttpRequest request = context.Request; FileStream fileStream = null; byte[] buffer = new Byte[10240]; int length; // 剩余的字節大小 // 因為這里采取的是每次寫入10240字節到輸出流中 long readToData; try { string filename = "CLR via CSharp 3rd edition.pdf"; //通過解密得到文件名 string filepath = HttpContext.Current.Server.MapPath("~/") + "Resources/" + filename; //待下載的文件路徑 fileStream = new FileStream(filepath, FileMode.Open,FileAccess.Read, FileShare.Read); readToData = fileStream.Length; while (readToData > 0) { // 實際讀取的字節大小 length = fileStream.Read(buffer, 0, buffer.Length); // 把讀取到的字節寫入輸出流中 response.OutputStream.Write(buffer, 0, length); response.Flush(); readToData = readToData - length; } } catch (Exception ex) { response.Write("Error:" + ex.Message); } finally { if (fileStream != null) { fileStream.Close(); } response.End(); } } public bool IsReusable { get { return false; } } }
這里牽涉到HttpHandle對象問題,這個對象在Asp.net中是真正處理數據的對象,后面如果有時間也和大家分享下深入理解Asp.net系列,主要是介紹在Asp.net中一些核心對象為我們默默做的一些事情,在這里也不詳細介紹HttpHandle對象了, 這個示例中主要通過這個類來對文件的處理,把文件的二進制字節寫入到輸出流中, 客戶端在從輸出流中讀取字節,然后保存為文件(其實文件也就是“流”)。
客戶端:
客戶端建立了一個WinForm窗口,通過WebBrower控件(就是在WinForm程序中顯示網頁的控件)來連接服務器頁面,當按下下載按鈕后,通過線程池線程來執行下載方法。主要代碼為:
public void DownLoad(object state) { // 計時對象 Stopwatch sw = Stopwatch.StartNew(); HttpWebRequest request; HttpWebResponse response; Stream stream; // 下載下來的保存的地址 string savepath = "D:\\Download.pdf"; FileStream savestream = new FileStream(savepath, FileMode.OpenOrCreate); try { // 發出請求 request = (HttpWebRequest)HttpWebRequest.Create(url); // 獲得回應對象 response = (HttpWebResponse)request.GetResponse(); // 獲得回應流 stream = response.GetResponseStream(); byte[] bytes = new byte[10240]; int readsize; // 每次都讀取10240字節 // 采用的是同步讀取方法 // 計算耗費的時間 readsize = stream.Read(bytes, 0, bytes.Length); while (readsize > 0) { savestream.Write(bytes, 0, readsize); readsize = stream.Read(bytes, 0, bytes.Length); } sw.Stop(); MessageBox.Show("下載耗時為:" + sw.Elapsed.ToString(), "提示"); } catch (Exception ex) { MessageBox.Show(ex.Message, "Error"); } finally { savestream.Close(); } }
這樣就利用線程池線程簡單完成了客戶端下載服務器端文件的功能,並且使用線程池線程這樣不會主線程,從而導致在下載文件時,界面同樣可以操作,如果不采用多線程操作的話將會在下載過程導致界面“卡死”現象,這樣就會給用戶帶來不好的用戶體驗。
其實本來還想做復雜點的, 開始想實現的功能,是服務器端斷點續傳,然后客戶端多線程下載的功能的,這個示例中只用到了一個線程池線程來完成下載任務,本來想通過執行多個線程池線程來完成下載任務的, 每個線程只負責一部分的讀取工作, 然后把每個線程中讀取的字節合並起來就是完整的文件字節了,但是這里遇到一個問題,怎么在服務器端實現續傳的功能的, 客戶端通過AddRange方法來發出部分讀取請求,然后服務器端就要對請求頭Range進行解析的,實現原理我還是清楚,但是在做的過程中還是出現了問題。所以這里只能分享一個簡單的下載文件的功能給大家了, 至於多線程的下載和斷點續傳和大文件的上傳等問題,等我學習了再和大家分享, 如果有大牛可以幫助我解決服務端斷點續傳的問題的話,歡迎留言。
源文件下載鏈接:http://files.cnblogs.com/zhili/FileServer.zip (下載下來后只需要在服務器端Resources文件夾下添加一個文件就可以運行了)