架構概覽
- 主執行緒:負責 UI 回應、啟動/取消下載、顯示進度。
- 工作執行緒:執行實際下載迴圈、定期發送進度訊息至主執行緒。
- 通訊機制:
PostMessage、Synchronize 或佇列 + 定時器輪詢。
核心下載邏輯 (工作執行緒)
// 使用 MFC CInternetSession / CStdioFile (亦可用 WinInet API)
CInternetSession session("MyDownloader");
CStdioFile* pRemoteFile = session.OpenURL(url,
1, INTERNET_FLAG_TRANSFER_BINARY | INTERNET_FLAG_RELOAD);
if (!pRemoteFile) return; // 開啟失敗
// 取得檔案大小 (作為進度條最大值)
pRemoteFile->SeekToEnd();
DWORD dwFileSize = pRemoteFile->SeekToBegin();
m_ProgressCtrl.SetRange32(0, dwFileSize);
// 本地檔案
CFile localFile;
localFile.Open(localPath, CFile::modeCreate | CFile::modeWrite | CFile::typeBinary);
const int BUF_SIZE = 8192;
BYTE buffer[BUF_SIZE];
DWORD dwTotalRead = 0;
COleDateTime startTime = COleDateTime::GetCurrentTime();
while (true) {
UINT nRead = pRemoteFile->Read(buffer, BUF_SIZE);
if (nRead == 0) break;
localFile.Write(buffer, nRead);
dwTotalRead += nRead;
// 計算速度與進度
COleDateTimeSpan elapsed = COleDateTime::GetCurrentTime() - startTime;
double secs = elapsed.GetTotalSeconds();
double kbSec = (dwTotalRead / 1024.0) / (secs > 0 ? secs : 1);
int percent = (dwFileSize > 0) ? (dwTotalRead * 100 / dwFileSize) : 0;
// 發送進度至 UI (自訂訊息 WM_USER+1, wParam=percent, lParam=(LPARAM)kbSec)
::PostMessage(hWndMain, WM_DOWNLOAD_PROGRESS, percent, (LPARAM)(kbSec * 100));
// 檢查取消旗標
if (m_bCancel) break;
}
pRemoteFile->Close();
localFile.Close();
delete pRemoteFile;
進度條 UI 更新 (主執行緒)
// 訊息對應
ON_MESSAGE(WM_DOWNLOAD_PROGRESS, OnDownloadProgress)
LRESULT CMainDlg::OnDownloadProgress(WPARAM wParam, LPARAM lParam) {
int percent = (int)wParam;
double kbSec = (double)lParam / 100.0;
m_Progress.SetPos(percent);
m_lblPercent.SetWindowText(Format("%d%%", percent));
m_lblSpeed.SetWindowText(Format("%.1f KB/s", kbSec));
return 0;
}
斷點續傳核心 (HTTP Range 標頭)
// 在 OpenURL 前設定 Range 標頭
CString strHeaders;
strHeaders.Format("Range: bytes=%lu-\r\n", dwAlreadyDownloaded);
pRemoteFile = session.OpenURL(url, 1,
INTERNET_FLAG_TRANSFER_BINARY | INTERNET_FLAG_RELOAD,
strHeaders); // 注意:OpenURL 的第 4 參數為 headers
// 伺服器回應 206 Partial Content,內容從指定位置開始
// 本地檔案以 modeNoTruncate 開啟並 SeekToEnd 追加寫入
多執行緒下載 (分段並行)
- 先用
HEAD 取得 Content-Length 並確認支援 Accept-Ranges: bytes。
- 將檔案分為 N 段 (如 4 段),每段建立一個下載執行緒,各自請求對應
Range。
- 各執行緒寫入各自暫存檔,全部完成後合併 (或預先分配大檔案,各執行緒
Seek 寫入對應位置,需檔案系統支援同步)。
- 合併策略:簡單可靠 → 各段下載完成後依序
CopyFile 追加到最終檔案。
錯誤處理與健壯性
- 網路中斷:捕捉
CInternetException,記錄錯誤碼,支援重試 (指數退避)。
- 伺服器不支援 Range:回退單執行緒完整下載。
- 磁碟空間不足:
GetDiskFreeSpaceEx 預檢。
- UI 執行緒安全:所有控件操作必須在主執行緒 (用
PostMessage/Synchronize)。