HTTP 協議基礎
- GET:請求資源。支援
Range: bytes=start-end 斷點續傳。
- HEAD:僅取得標頭 (
Content-Length, Accept-Ranges)。
- 伺服器回應
206 Partial Content 表示支援範圍請求。
WinInet API 核心句柄層級
HINTERNET hSession = InternetOpen(...):會話句柄 (最上層)。
HINTERNET hConnect = InternetConnect(hSession, ...):連線句柄。
HINTERNET hRequest = HttpOpenRequest(hConnect, "GET", ...):請求句柄。
HttpAddRequestHeaders(hRequest, "Range: bytes=1000-1999", ...):加入範圍標頭。
HttpSendRequest(hRequest, ...):發送請求。
InternetReadFile(hRequest, buf, size, &read):讀取資料。
InternetCloseHandle 依序關閉三層句柄。
下載執行緒類別設計 (THttpGetThread)
class THttpGetThread : public TThread {
private:
AnsiString FUrl, FOutFile;
DWORD FStartPos, FEndPos; // 此執行緒負責的位元組範圍 [StartPos, EndPos]
HINTERNET FhSession, FhConnect, FhRequest;
HANDLE FFileHandle; // 目的檔案句柄 (共享寫入)
bool FSuccess;
DWORD FBytesDownloaded;
protected:
virtual void __fastcall Execute();
void __fastcall CleanupHandles();
void __fastcall ReportProgress(DWORD bytes); // Synchronize 回主執行緒更新 UI
public:
__fastcall THttpGetThread(AnsiString Url, AnsiString OutFile,
DWORD StartPos, DWORD EndPos, HANDLE FileHandle);
__fastcall ~THttpGetThread();
bool Success() { return FSuccess; }
DWORD BytesDownloaded() { return FBytesDownloaded; }
};
Execute 核心邏輯
void __fastcall THttpGetThread::Execute()
{
FhSession = InternetOpen("MyDownloader/1.0", INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0);
if (!FhSession) return;
// 解析 URL 取得 host, path, port (略,可用 InternetCrackUrl)
FhConnect = InternetConnect(FhSession, Host, Port, User, Pass,
INTERNET_SERVICE_HTTP, 0, 0);
if (!FhConnect) { CleanupHandles(); return; }
FhRequest = HttpOpenRequest(FhConnect, "GET", Path, NULL, NULL, NULL,
INTERNET_FLAG_RELOAD | INTERNET_FLAG_NO_CACHE_WRITE, 0);
if (!FhRequest) { CleanupHandles(); return; }
// 設定 Range 標頭
AnsiString rangeHdr = "Range: bytes=" + IntToStr(FStartPos) + "-" + IntToStr(FEndPos);
HttpAddRequestHeaders(FhRequest, rangeHdr.c_str(), rangeHdr.Length(),
HTTP_ADDREQ_FLAG_ADD | HTTP_ADDREQ_FLAG_REPLACE);
if (!HttpSendRequest(FhRequest, NULL, 0, NULL, 0)) { CleanupHandles(); return; }
// 讀取迴圈
const int BUFSZ = 8192;
BYTE* buf = new BYTE[BUFSZ];
DWORD dwRead = 0;
OVERLAPPED ol = {0};
ol.Offset = FStartPos;
ol.OffsetHigh = 0;
while (InternetReadFile(FhRequest, buf, BUFSZ, &dwRead) && dwRead > 0) {
// 寫入檔案對應位置 (需檔案以 FILE_FLAG_OVERLAPPED 開啟)
DWORD dwWritten;
WriteFile(FFileHandle, buf, dwRead, &dwWritten, &ol);
ol.Offset += dwWritten;
FBytesDownloaded += dwWritten;
if (FBytesDownloaded % 16384 == 0) Synchronize(&ReportProgress);
if (Terminated) break;
}
delete[] buf;
FSuccess = (FBytesDownloaded == (FEndPos - FStartPos + 1));
CleanupHandles();
}
主控制器:分段策略與合併
HEAD 請求取得 Content-Length、確認 Accept-Ranges: bytes。
- 決定執行緒數 N (如 4),計算每段大小
seg = total / N,最後一段含餘數。
- 建立目的檔案:
CreateFile(..., FILE_FLAG_OVERLAPPED | FILE_FLAG_SEQUENTIAL_SCAN, ...),SetFilePointerEx 預分配大小 (或寫入時自動擴展)。
- 啟動 N 個
THttpGetThread,各自寫入對應區段 (使用 OVERLAPPED 指定 Offset)。
- 等待所有執行緒完成 (
WaitForMultipleObjects 或輪詢 Thread->Finished)。
- 驗證總位元組數、MD5/CRC32 校驗 (可選)。
- 失敗段重試機制 (指數退避、最多 3 次)。
斷點續傳持久化
- 下載前讀取
.dld 索引檔 (JSON/二進制),記錄各段 StartPos, EndPos, DownloadedBytes。
- 啟動時跳過已完成段,僅下載未完成段。
- 每段完成/定期更新索引檔。
- 完成後刪除索引檔。
錯誤處理重點
InternetReadFile 返回 FALSE → GetLastError() 區分 ERROR_INTERNET_CONNECTION_RESET, ERROR_INTERNET_TIMEOUT, ERROR_HANDLE_EOF。
- 伺服器不支援 Range → 回應 200 OK 而非 206 → 偵測並回退單執行緒完整下載。
- 磁碟寫入失敗 →
GetLastError() 檢查磁碟滿、權限。
- UI 執行緒安全:所有 VCL 控件操作用
Synchronize 或 Queue。