程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> VC >> 關於VC++ >> 使用CInternetSession封裝多線程http文件下載

使用CInternetSession封裝多線程http文件下載

編輯:關於VC++

如何下載一個http文件?我們當然可以用socket自己實現http協議去做,但費時費力還易出bug,對於一個客戶端程序穩定易維護是第一位的,所幸MS給我們提供了功能強大的internet API函數族,MFC的CInternetSession對它們進行了一些簡單的封裝,但如此簡單的封裝對我等拿來主義者來說只是個半成品。必須經過再加工才能食用。

先來介紹一下CInternetSession的使用:

下面的代碼是讀取鏈接的基本方法:

// CInternetSession在遇到一些錯誤時會拋出異常,因此必須包起來
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);
  if (!pF)
    {AfxThrowInternetException(1);}
  // 得到文件大小
  CString   str ;
  pF->QueryInfo (HTTP_QUERY_CONTENT_LENGTH, str) ;
  int  nFileSize = _ttoi(str) ;
  char  * p = new[nFileSize] ;
  while (true)
  {
    // 每次下載8Kb
    int  n = pF->Read (p, (nFileSize < 8192) ? nFileSize : 8192) ;
    if (n <= 0)
      break ;
    p += n ; nFileSize -= n ;
  }
  delete[] p ;
  delete pF ;
}
CATCH_ALL(e) {}
END_CATCH_ALL

這段代碼有一個問題,在獲取文件大小這個地方,對於靜態網頁 HTTP_QUERY_CONTENT_LENGTH 查詢會返回文件大小,但對於asp,php這樣的動態網頁,查詢會返回0。必須通過不斷的調用 CHttpFile::GetLength 來一點一點累加內容,就像這樣:int  n = pF->GetLength() ;
while (n)
{
  int  * p = new BYTE[n] ;
  pF->Read (p, n) ;
  delete[] p ;
  n = pF->GetLength() ;
}

不過網絡斷線同樣會讓 GetLength 返回0,必須把這種情況屏蔽掉。if (n == 0)
{
  DWORD  dw ;
  if (::InternetQueryDataAvailable ((HINTERNET)(*pF), &dw, 0, 0) && (dw == 0))
  {
    // 到這裡就代表文件下載成功了
  }
}

OK,我們已經把機制摸清了,剩下就是把這些體力活全扔進線程裡,又一個麻煩產生了:線程裡如何向外界通知事件(開始下載,下載完成之類)呢?直接調用回調函數當然可以,但這時回調函數是置於我們的線程中,造成在回調函數中對資源的訪問必須非常小心,防止多線程沖突。下一步,加鎖同步...。

掙扎在多線程泥潭中的人已經夠多的了,其實我們有一個更安全方便的方法,借助 SendMessage 把線程裡的事件發送到窗口線程統一處理,windows會幫我們把所有消息排隊執行,相當於把多線程程序轉成了單線程^_^ (我一個同事把此類用於包含數百個線程的爬蟲程序中,非常穩定)

封裝結果及使用:

template<class T>
class FCDownloadFileWndBase : public T
{
public:
  // 默認構造函數
  FCDownloadFileWndBase () {}
  // CDialog 構造函數
  FCDownloadFileWndBase (UINT nID, CWnd* pParent) : T(nID, pParent) {}
  // CFormView 構造函數
  FCDownloadFileWndBase (UINT nID) : T(nID) {}
  // 創建一個線程下載文件URL,如果URL正在下載中,此函數什麼也不做立即返回
  void DownloadFile (LPCTSTR strFileURL, int nPriority=THREAD_PRIORITY_IDLE) ;
protected:
  // 檢查鏈接最後修改時間,有些服務器會禁止查看時間,strTime為空
  // 用戶必須重載實現本接口,返回TRUE則繼續下載文件,返回FALSE則不再下載文件
  virtual BOOL DownloadFile_OnCheckTime (CString strFileURL, CString strTime) =0 ;
  // 當鏈接成功下載完成後會調用此接口
  virtual void DownloadFile_OnFinished (CString strFileURL, char* pBuffer, int nLength) {}
  // 當IE設置代理服務器並且服務器需要帳號認證時候回調
  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) {}
};

使用起來非常簡單,讓你的窗口從它派生,然後選擇你感興趣的事件重載之即可。

幾點說明:

本類會自動使用IE裡的連接設置,如果代理服務器需要帳號驗證,會回調 DownloadFile_OnProxyValidate 讓用戶輸入帳號密碼;

因為使用了模版,所以不支持MFC丑陋的dynamic機制:-( ,請把 DECLARE_DYNAMIC 和 IMPLEMENT_DYNAMIC 宏從你的類中移除。如果你需要運行時類型檢查,可以用C++的RTTI機制dynamic_cast/typeid;

本文配套源碼

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved