HTTP 協議的 Range 標頭是實現斷點續傳的關鍵。客戶端(如下載工具、瀏覽器)在請求中發送 Range: bytes=開始位置-結束位置,服務器響應 206 Partial Content 狀態碼,並通過 Content-Range 標頭指示返回的字節範圍,配合 Content-Length 指明本次傳輸長度。服務端需支持隨機讀取文件流(FileStream.Seek),並分塊輸出以避免大文件佔用過多內存。
public void DownloadWithResume(string filepath)
{
// 1. 參數準備
const int BufferSize = 10240; // 10KB 緩衝區
byte[] buffer = new byte[BufferSize];
string filename = System.IO.Path.GetFileName(filepath);
long dataToRead = 0;
long startPos = 0;
try
{
// 2. 打開文件流 (共享讀取模式,允許其他進程同時讀取)
using (FileStream iStream = new FileStream(filepath, FileMode.Open, FileAccess.Read, FileShare.Read))
{
dataToRead = iStream.Length; // 文件總大小
// 3. 處理 Range 請求
string rangeHeader = Request.Headers["Range"];
if (!string.IsNullOrEmpty(rangeHeader))
{
// 解析 "bytes=開始-結束" 或 "bytes=開始-"
string range = rangeHeader.Replace("bytes=", "").Replace("-", "");
if (long.TryParse(range, out long parsedPos))
{
startPos = parsedPos;
}
}
// 4. 設置響應標頭
Response.Clear();
Response.ContentType = "application/octet-stream";
// 文件名編碼處理,兼容中文文件名
string encodedFilename = HttpUtility.UrlEncode(filename, Request.ContentEncoding);
Response.AddHeader("Content-Disposition", $"attachment; filename={encodedFilename}");
if (startPos > 0)
{
// 斷點續傳響應
Response.StatusCode = 206; // Partial Content
Response.AddHeader("Content-Range", $"bytes {startPos}-{dataToRead - 1}/{dataToRead}");
dataToRead -= startPos; // 剩餘待讀取字節數
Response.AddHeader("Content-Length", dataToRead.ToString());
// 定位文件流位置
iStream.Position = startPos;
}
else
{
// 完整下載響應
Response.AddHeader("Content-Length", dataToRead.ToString());
}
// 5. 分塊讀取並輸出 (核心循環)
while (dataToRead > 0)
{
// 檢查客戶端是否仍連接,防止斷開後服務端繼續無意義傳輸
if (!Response.IsClientConnected)
{
dataToRead = -1; // 跳出循環
break;
}
// 讀取數據到緩衝區
int length = iStream.Read(buffer, 0, BufferSize);
if (length == 0) break; // 讀取完畢
// 寫入輸出流並立即刷新到客戶端
Response.OutputStream.Write(buffer, 0, length);
Response.Flush();
dataToRead -= length;
// 重置緩衝區 (可選,為下一次讀取做準備)
// buffer = new byte[BufferSize]; // 實際上 Read 會覆蓋,無需重新 new
}
}
}
catch (Exception ex)
{
Response.Write($"Error: {ex.Message}");
}
finally
{
Response.End(); // 確保響應結束
}
}
new FileStream(..., FileShare.Read) 允許其他進程同時打開同一文件進行讀取。若無此標誌,文件被獨佔,會導致並發下載或文件被其他程序讀取時失敗。
實際場景中 Range 標頭可能為 bytes=100- (從 100 到結尾) 或 bytes=100-200。代碼中簡單移除 bytes= 和 - 後解析起始位置,生產環境建議使用正則表達式完整解析起止位置。
bytes 起始-結束/總大小,如 bytes 10240-20479/1048576總大小 - 起始位置)attachment 強制瀏覽器彈出下載對話框,文件名需 URL 編碼使用 10KB 緩衝區循環讀寫,配合 Response.Flush() 立即將數據發送給客戶端,而非緩存在服務端內存中。這對大文件下載至關重要,可防止服務端內存飆升(若一次性 ReadToEnd 或 WriteFile 會將整個文件載入內存)。
循環中檢查 Response.IsClientConnected,若客戶端取消下載或網絡中斷,立即終止循環釋放資源,避免無限循環佔用線程。
使用 using 語句確保 FileStream 異常時也能正確關閉。finally 中調用 Response.End() 終止響應處理管道。
If-Range, If-Match) 確保文件未變更Thread.Sleep 或 Token Bucket 算法限速本實現展示了 ASP.NET 中基於 HTTP Range 請求的標準斷點續傳下載模式。核心在於:正確解析 Range 標頭、設置 206 響應與 Content-Range、文件流隨機定位、分塊緩衝輸出配合 Flush、客戶端連接狀態檢測。這是文件下載服務、視頻流媒體、大數據導出等場景的基石技術。