斷點續傳的原理很簡單,就是在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
兩個報文的不同部分已用紅色部分標記出來。可以看出:
-
客戶端報文頭中通過Range報文頭來標識客戶期望的下載位置。
-
服務器的應答號爲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); //模擬慢速設備
}
}