用HttpListener實現文件斷點續傳

斷點續傳的原理很簡單,就是在Http的請求和應答的報文頭上和一般的下載有所不同而已。

普通方式請求服務器上的一個文時,所發出的請求和接受到的服務器如下:

request header
Cache-Control: no-cache
Connection: close
Pragma: no-cache
Accept: */*
Host: localhost

response header

200
Content-Type: application/octet-stream
Content-Disposition: attachment;FileName=time.pdf

當服務器支持斷點續傳時,請求和應答如下:

request header
Cache-Control: no-cache
Connection: close
Pragma: no-cache
Accept: */*
Host: localhost
Range: bytes=15360-

response header

206
Content-Type: application/octet-stream
Content-Disposition: attachment;FileName=time.pdf

兩個報文的不同部分已用紅色部分標記出來。可以看出:

  1. 客戶端報文頭中通過Range報文頭來標識客戶期望的下載位置。
  2. 服務器的應答號爲200時表示是從文件頭開始下載,而206表示是從文件的特定位置開始傳輸,客戶端從該應答號可以看出服務器是否支持斷點續傳。

也就是說,支持斷點續傳的時候可以從文件任一部分開始下載,而普通的方式只能從文件頭開始下載

要使得服務器支持斷點續傳,需要解決以下幾個問題:

1。需要判斷客戶端是否是續傳請求,如果是續傳請求時,需要獲取客戶端所需的文件範圍。

從上面的分析可以看到,當客戶端爲斷點傳輸時,報文頭裏會增加Range字段,則可以通過如下方式判斷是否是斷點傳輸請求。

string range = request.Headers["Range"];
bool isResume = string.IsNullOrEmpty(range);

2。對客戶端做正確的應答相應,以通知客戶端服務器支持端點續傳

當爲斷點傳輸請求時,對客戶端的相應號可以通過如下方式設置:

response.StatusCode = 206;

3。傳送客戶端所需正確的內容

傳送客戶端所需正確的內容一般需要經過以下幾個步驟

  • 通過分析range來獲取客戶端的文件請求範圍。
  • 斷點傳輸請求時,所需的長度比文件的長度短,故需要正確的設置response.ContentLength64屬性。
  • 正確傳輸所需的內容

代碼示例:

static void ProcessHttpClient(object obj)
{
    HttpListenerContext context = obj as HttpListenerContext;
    HttpListenerRequest request = context.Request;
    HttpListenerResponse response = context.Response;

    FileStream fs = File.OpenRead(@"f:/123.pdf"); //
待下載的文件

    long startPos = 0;
    string range = request.Headers["Range"];
    bool isResume = string.IsNullOrEmpty(range);
    if (isResume) //
斷點續傳請求
    {
        //
格式bytes=9216-
        startPos = long.Parse(range.Split('=')[1].Split('-')[0]);
        response.StatusCode = 206;
        response.ContentLength64 = fs.Length - startPos;
        fs.Position = startPos; //
設置傳送的起始位置
    }
    else
    {
        response.ContentLength64 = fs.Length;
    }

    Console.WriteLine("request header");
    Console.WriteLine(request.Headers.ToString());

    response.ContentType = "application/octet-stream";

    string fileName = "time.pdf";
    response.AddHeader("Content-Disposition", "attachment;FileName=" + fileName);
    Stream output = response.OutputStream;

    try
    {
        Console.WriteLine("response header");
        Console.WriteLine(response.Headers.ToString());
        CopyStream(fs, output); //
文件傳輸
        output.Close();
    }
    catch (HttpListenerException e) //
在未寫完所有文件時,如果客戶端關閉連接,會拋此異常
    {
        Console.WriteLine(e.Message);
        //output.Close(); //
如果執行此函數會拋異常在寫入所有字節之前不能關閉流。
    }
}

static void CopyStream(Stream orgStream, Stream desStream)
{
    byte[] buffer = new byte[1024];

    int read = 0;
    while ((read = orgStream.Read(buffer, 0, 1024)) > 0)
    {
        desStream.Write(buffer, 0, read);

        System.Threading.Thread.Sleep(1000); //
模擬慢速設備
    }
}

發佈了28 篇原創文章 · 獲贊 1 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章