自行實作 HTTP 協定成本高且易出錯,MFC CInternetSession 封裝了 WinInet API,但屬半成品,需再封裝才能實用。本文分享一個支援多執行緒、進度回呼、Proxy 驗證的下載元件。
TRY {
CInternetSession sess;
DWORD dwFlag = INTERNET_FLAG_TRANSFER_BINARY | INTERNET_FLAG_DONT_CACHE | INTERNET_FLAG_RELOAD;
CHttpFile* pF = (CHttpFile*)sess.OpenURL(strFilename, 1, dwFlag);
ASSERT(pF);
CString str;
pF->QueryInfo(HTTP_QUERY_CONTENT_LENGTH, str);
int nFileSize = _ttoi(str);
char* p = new char[nFileSize];
while (true) {
int n = pF->Read(p, min(nFileSize, 8192));
if (n <= 0) break;
p += n; nFileSize -= n;
}
delete[] p; delete pF;
} CATCH_ALL(e) {} END_CATCH_ALL
int n = pF->GetLength();
while (n) {
BYTE* p = new BYTE[n];
pF->Read(p, n);
delete[] p;
n = pF->GetLength();
}
if (n == 0) {
DWORD dw;
if (::InternetQueryDataAvailable((HINTERNET)*pF, &dw, 0, 0) && dw == 0) {
// 真正下載完成
}
}
執行緒內直接呼叫回呼函數易造成資源競爭,需加鎖同步。更優雅的做法:執行緒發送訊息至主視窗執行緒,Windows 訊息佇列天然序列化,等同將多執行緒轉單執行緒處理,極大降低複雜度(同事曾用於數百執行緒爬蟲,極度穩定)。
template
class FCDownloadFileWndBase : public T {
public:
FCDownloadFileWndBase() {}
FCDownloadFileWndBase(UINT nID, CWnd* pParent) : T(nID, pParent) {}
FCDownloadFileWndBase(UINT nID) : T(nID) {}
void DownloadFile(LPCTSTR strFileURL, int nPriority = THREAD_PRIORITY_IDLE);
protected:
// 必須實作:檢查最後修改時間,回傳 TRUE 續傳
virtual BOOL DownloadFile_OnCheckTime(CString strFileURL, CString strTime) = 0;
// 選擇性覆寫
virtual void DownloadFile_OnFinished(CString strFileURL, char* pBuffer, int nLength) {}
virtual void DownloadFile_OnProxyValidate(CString strFileURL, CString& strUsername, CString& strPassword) {}
virtual void DownloadFile_OnError(CString strFileURL) {}
virtual void DownloadFile_OnStartDownload(CString strFileURL) {}
virtual void DownloadFile_OnProgress(CString strFileURL, int nNow, int nTotal) {}
};
FCDownloadFileWndBase<CDialog> 或 CFormViewDownloadFile(url) 即可OnProxyValidate 讓使用者輸入帳密DECLARE_DYNAMIC,需移除並改用 C++ RTTI