首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> C++ >> 關於C++

MFC 多執行緒檔案下載與進度條追蹤實作 (CInternetSession)

     編輯:關於C++

架構概覽

使用 MFC WinInet 封裝類別 (CInternetSession, CStdioFile, CHttpFile) 在工作執行緒 執行下載,避免封鎖 UI 執行緒。主執行緒透過 PostMessage 或控制項通知更新 CProgressCtrl、顯示速度、剩餘時間。支援暫停/繼續/取消、斷點續傳 (HTTP Range 標頭)。

核心類別與函數

類別/函數用途
CInternetSessionWinInet 會話管理,建立連線、設定選項 (逾時、Proxy、User-Agent)。
CInternetSession::OpenURL()開啟 URL 回傳 CStdioFile* (或 CHttpFile*),支援 HTTP/FTP/Gopher。
CStdioFile::Read()讀取資料區塊 (預設緩衝 4096 bytes)。
CFile::Write()寫入本地檔案 (CFile::modeCreate | modeWrite | typeBinary)。
CStdioFile::SeekToEnd()取得遠端檔案總大小 (bytes),用於設定進度條範圍。
CProgressCtrl::SetRange32(0, total)設定進度條 0~總大小。
CProgressCtrl::SetPos(current)更新進度。
COleDateTime / COleDateTimeSpan計算經過時間、下載速度 (KB/s)、預估剩餘時間。

下載執行緒類別範例


// DownloadThread.h
class CDownloadThread : public CWinThread
{
    DECLARE_DYNCREATE(CDownloadThread)
protected:
    CDownloadThread();           // 動態建立需 protected 建構函式
    virtual ~CDownloadThread();
    virtual BOOL InitInstance();
    virtual int Run();           // 執行緒主函式
    virtual void ExitInstance();

public:
    // 參數 (主執行緒建立後設定)
    CString m_strURL;
    CString m_strLocalFile;
    HWND    m_hWndProgress;      // 進度條視窗控制代碼
    HWND    m_hWndNotify;        // 接收進度通知的視窗
    UINT    m_nMsgUpdate;        // 自訂訊息 (WM_USER+1)
    BOOL    m_bCancel;           // 取消旗標
    BOOL    m_bPause;            // 暫停旗標
    // 斷點續傳支援
    ULONGLONG m_ullDownloaded;   // 已下載位元組數
};

// DownloadThread.cpp
IMPLEMENT_DYNCREATE(CDownloadThread, CWinThread)

CDownloadThread::CDownloadThread() : m_bCancel(FALSE), m_bPause(FALSE), m_ullDownloaded(0) {}

BOOL CDownloadThread::InitInstance() { return TRUE; }

int CDownloadThread::Run()
{
    CInternetSession session(_T("MyDownloader/1.0"), 1, PRE_CONFIG_INTERNET_ACCESS, NULL, NULL, 0);
    session.SetOption(INTERNET_OPTION_CONNECT_TIMEOUT, 10000);
    session.SetOption(INTERNET_OPTION_RECEIVE_TIMEOUT, 30000);

    CStdioFile* pRemoteFile = NULL;
    CFile localFile;
    CFileException fe;
    ULONGLONG totalSize = 0, current = 0;
    BYTE buffer[8192];
    UINT nRead;
    COleDateTime startTime = COleDateTime::GetCurrentTime();

    TRY {
        // 斷點續傳:若本地檔存在,設定 Range 標頭
        if (m_ullDownloaded > 0 && localFile.Open(m_strLocalFile, CFile::modeRead | CFile::typeBinary, &fe)) {
            ULONGLONG existing = localFile.GetLength();
            if (existing == m_ullDownloaded) {
                CString rangeHeader;
                rangeHeader.Format(_T("Range: bytes=%llu-\r\n"), m_ullDownloaded);
                session.SetOption(INTERNET_OPTION_HTTP_VERSION, HTTP_VERSION_1_1); // 需 HTTP/1.1
                pRemoteFile = session.OpenURL(m_strURL, 1, INTERNET_FLAG_TRANSFER_BINARY | INTERNET_FLAG_RELOAD | INTERNET_FLAG_DONT_CACHE, rangeHeader);
                localFile.Close();
                localFile.Open(m_strLocalFile, CFile::modeWrite | CFile::modeNoTruncate | CFile::typeBinary, &fe);
                localFile.SeekToEnd();
            } else {
                m_ullDownloaded = 0; // 檔案不一致,重頭下載
            }
        } else {
            pRemoteFile = session.OpenURL(m_strURL, 1, INTERNET_FLAG_TRANSFER_BINARY | INTERNET_FLAG_RELOAD | INTERNET_FLAG_DONT_CACHE);
            localFile.Open(m_strLocalFile, CFile::modeCreate | CFile::modeWrite | CFile::typeBinary, &fe);
        }

        if (!pRemoteFile) AfxThrowInternetException(ERROR_INTERNET_CANNOT_OPEN_FILE);

        // 取得總大小 (若支援 Range,需自行計算或從 Content-Range 解析)
        // 簡化:假設第一次下載可用 SeekToEnd
        if (m_ullDownloaded == 0) {
            totalSize = pRemoteFile->SeekToEnd();
            pRemoteFile->Seek(0, CFile::begin);
        } else {
            // 續傳模式下總大小 = 已下載 + 剩餘 (需從 Content-Range 取得)
            // 此處略過,實際需解析 HTTP 回應標頭
        }

        // 通知主執行緒設定進度條範圍
        ::PostMessage(m_hWndNotify, m_nMsgUpdate, 0, (LPARAM)totalSize); // wParam=0 表設定範圍

        current = m_ullDownloaded;
        while ((nRead = pRemoteFile->Read(buffer, sizeof(buffer))) > 0) {
            // 暫停/取消檢查
            while (m_bPause) { Sleep(100); if (m_bCancel) break; }
            if (m_bCancel) break;

            localFile.Write(buffer, nRead);
            current += nRead;

            // 更新進度 (每 64KB 或 500ms 通知一次,避免訊息洪流)
            static ULONGLONG lastUpdate = 0;
            static COleDateTime lastTime = COleDateTime::GetCurrentTime();
            COleDateTime now = COleDateTime::GetCurrentTime();
            if (current - lastUpdate >= 65536 || (now - lastTime).GetTotalSeconds() >= 0.5) {
                ::PostMessage(m_hWndNotify, m_nMsgUpdate, 1, (LPARAM)current); // wParam=1 表更新進度
                lastUpdate = current;
                lastTime = now;
            }
        }

        if (!m_bCancel) {
            ::PostMessage(m_hWndNotify, m_nMsgUpdate, 2, 0); // wParam=2 表完成
        } else {
            ::PostMessage(m_hWndNotify, m_nMsgUpdate, 3, 0); // wParam=3 表取消
        }
    }
    CATCH(CInternetException, e) {
        TCHAR szErr[256];
        e->GetErrorMessage(szErr, 255);
        ::PostMessage(m_hWndNotify, m_nMsgUpdate, 4, (LPARAM)_tcsdup(szErr)); // wParam=4 表錯誤
    }
    END_CATCH

    if (pRemoteFile) { pRemoteFile->Close(); delete pRemoteFile; }
    if (localFile.m_hFile != CFile::hFileNull) localFile.Close();
    session.Close();
    return 0;
}

void CDownloadThread::ExitInstance() { CWinThread::ExitInstance(); }

主視窗訊息處理


// MainFrm.h / Dialog.h
#define WM_DOWNLOAD_UPDATE (WM_USER + 1)

afx_msg LRESULT OnDownloadUpdate(WPARAM wParam, LPARAM lParam);
// 訊息對應
ON_MESSAGE(WM_DOWNLOAD_UPDATE, OnDownloadUpdate)

// MainFrm.cpp
LRESULT CMainFrame::OnDownloadUpdate(WPARAM wParam, LPARAM lParam)
{
    switch (wParam) {
        case 0: // 設定範圍
            m_wndProgress.SetRange32(0, (int)lParam);
            m_wndProgress.SetPos(0);
            break;
        case 1: // 更新進度
            m_wndProgress.SetPos((int)lParam);
            // 計算速度、剩餘時間 (需記錄開始時間、總大小)
            UpdateSpeedDisplay(lParam);
            break;
        case 2: // 完成
            MessageBox(_T("下載完成!"));
            break;
        case 3: // 取消
            MessageBox(_T("下載已取消。"));
            break;
        case 4: // 錯誤
            MessageBox((LPCTSTR)lParam, _T("下載錯誤"), MB_ICONERROR);
            free((void*)lParam); // _tcsdup 配置的記憶體
            break;
    }
    return 0;
}

速度與剩餘時間計算


void CMainFrame::UpdateSpeedDisplay(ULONGLONG current)
{
    static COleDateTime start = COleDateTime::GetCurrentTime();
    COleDateTimeSpan elapsed = COleDateTime::GetCurrentTime() - start;
    double seconds = elapsed.GetTotalSeconds();
    if (seconds < 0.1) return;

    double kbReceived = current / 1024.0;
    double kbps = kbReceived / seconds;
    CString strSpeed;
    strSpeed.Format(_T("%.1f KB/s"), kbps);

    ULONGLONG total = m_wndProgress.GetRange32(FALSE); // 取最大值
    if (total > current && kbps > 0) {
        double eta = (total - current) / 1024.0 / kbps;
        int mins = (int)eta / 60;
        int secs = (int)eta % 60;
        CString strETA;
        strETA.Format(_T("剩餘 %02d:%02d"), mins, secs);
        // 更新狀態列或標籤
    }
    // 更新速度標籤
}

啟動下載


void CMainFrame::OnDownloadStart()
{
    CDownloadThread* pThread = (CDownloadThread*)AfxBeginThread(RUNTIME_CLASS(CDownloadThread), THREAD_PRIORITY_NORMAL, 0, CREATE_SUSPENDED);
    pThread->m_strURL = _T("https://example.com/largefile.zip");
    pThread->m_strLocalFile = _T("C:\\Downloads\\largefile.zip");
    pThread->m_hWndProgress = m_wndProgress.m_hWnd;
    pThread->m_hWndNotify = m_hWnd;
    pThread->m_nMsgUpdate = WM_DOWNLOAD_UPDATE;
    pThread->m_bCancel = FALSE;
    pThread->m_bPause = FALSE;
    pThread->m_ullDownloaded = 0; // 或讀取現有檔案大小支援續傳
    pThread->ResumeThread();
}

暫停/取消/續傳控制


void CMainFrame::OnDownloadPause()
{
    if (pThread) pThread->m_bPause = !pThread->m_bPause;
    // 更新按鈕文字 "暫停" / "繼續"
}

void CMainFrame::OnDownloadCancel()
{
    if (pThread) {
        pThread->m_bCancel = TRUE;
        // 等待執行緒結束 (可選)
        // WaitForSingleObject(pThread->m_hThread, 5000);
    }
}

現代替代方案

  • WinHTTP / IXMLHTTPRequest2:系統級 HTTP 客戶端,支援 HTTP/2、非同步、更佳效能。
  • libcurl:跨平台、功能最完整、支援所有協議/認證/代理。
  • C++ REST SDK (Casablanca):現代 C++ 非同步 HTTP 客戶端 (PPL tasks/await)。
  • Qt Network / Boost.Beast / cpp-httplib:跨平台 C++ 庫選擇。

MFC WinInet 類別適合維護既有 MFC 專案,新專案建議採用上述現代庫。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved