一個下載文件的線程類

  有時候,我們需要通過INTERNET下載文件,在文件下載的過程中,我們還需要處理其他事情,爲此,我們需要把文件下載的工作放到一個線程中來實現。爲了體現面向對象程序設計的封裝性,我們最好把線程封裝爲一個類,以後我們每需要一個這樣的線程,我們只要實例化該類的一個對象就可以了。如果自己實現這樣的類,應該有一定的難度,所幸的事,MFC爲我們提供了這樣的一個基類,CWinThread類,我們只要以它爲基類派生一個符合我們需要的子類就可以了。
  現在,我有幾個文件要下載,要下載的文件保存在一個鏈表中,下載的過程中,要向界面顯示錯誤或下載進度等一些信息。我的程序是一個嚮導程序,嚮導的CPropertySheet類的子類CWizardSheet管理着各個屬性頁,因此,我將保存着將要下載的文件信息的鏈表保存爲CWizardSheet類的成員,要下載的時候,我將一個CWizardSheet的指針傳給線程,線程讀取它的鏈表成員,順序下載其中的文件,下載的過程中,將發一些消息來向主窗口通知主窗口文件下載的狀態信息。

線程類定義如下:
#if !defined(AFX_CDownloadThread_H__1238AC8E_F397_4E8C_AC43_50170B789119__INCLUDED_)
#define AFX_CDownloadThread_H__1238AC8E_F397_4E8C_AC43_50170B789119__INCLUDED_

#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000
// CDownloadThread.h : header file
//
#include "common.h"

class CWizardSheet;
/////////////////////////////////////////////////////////////////////////////
// CDownloadThread thread

class CDownloadThread : public CWinThread
{
 DECLARE_DYNCREATE(CDownloadThread)
private:
 CDownloadThread();           // 定義爲私有的是爲了防止外部定義線程數組和調用該無參構造函數
public:
 //傳給構造函數一個父窗口的指針
    CDownloadThread(CWizardSheet* pWizSheet, AFX_THREADPROC pfnThreadProc);
// Attributes
public:

// Operations
public:
 static UINT ThreadFunc(LPVOID param);   //線程函數,將調用下面的函數來下載文件
 void GetFile(NewModuleInfo& newModuleInfo);  //真正實現文件下載的函數

// Overrides
 // ClassWizard generated virtual function overrides
 //{{AFX_VIRTUAL(CDownloadThread)
 public:
 virtual BOOL InitInstance();
 virtual int ExitInstance();
 //}}AFX_VIRTUAL

// Implementation
public:
 virtual ~CDownloadThread();

 // Generated message map functions
 //{{AFX_MSG(CDownloadThread)
  // NOTE - the ClassWizard will add and remove member functions here.
 //}}AFX_MSG

 DECLARE_MESSAGE_MAP()
private:
 CWizardSheet* m_pWizSheet;  //保存父窗口指針用
 CString m_saveDir;             //文件保存路徑
};

/////////////////////////////////////////////////////////////////////////////

//{{AFX_INSERT_LOCATION}}
// Microsoft Visual C++ will insert additional declarations immediately before the previous line.

#endif // !defined(AFX_CDownloadThread_H__1238AC8E_F397_4E8C_AC43_50170B789119__INCLUDED_)

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
下載線程的實現如下:
// CDownloadThread.cpp : implementation file
//

#include "stdafx.h"
#include <afxinet.h>
#include "Wiz2.h"
#include "DownloadThread.h"
#include "WizardSheet.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

//extern WM_MSG_PROGRS = WM_USER+100;
/////////////////////////////////////////////////////////////////////////////
// CDownloadThread

IMPLEMENT_DYNCREATE(CDownloadThread, CWinThread)

CDownloadThread::CDownloadThread()
{
}

CDownloadThread::CDownloadThread(
   CWizardSheet* pWizSheet,
   AFX_THREADPROC pfnThreadProc):CWinThread(pfnThreadProc, NULL)
{
 m_pWizSheet = pWizSheet;    //保存父窗口指針
 m_saveDir = pWizSheet->GetSaveDir();//獲得文件要保存的路徑
 
 m_bAutoDelete   = TRUE;//線程結束將自動清除
 m_pThreadParams = this; //線程參數設爲自己
}
CDownloadThread::~CDownloadThread()
{
 
}

BOOL CDownloadThread::InitInstance()
{
 // TODO:  perform and per-thread initialization here
 return TRUE;
}

int CDownloadThread::ExitInstance()
{
 // TODO:  perform any per-thread cleanup here
 return CWinThread::ExitInstance();
}

BEGIN_MESSAGE_MAP(CDownloadThread, CWinThread)
 //{{AFX_MSG_MAP(CDownloadThread)
  // NOTE - the ClassWizard will add and remove mapping macros here.
 //}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CDownloadThread message handlers

UINT CDownloadThread::ThreadFunc(LPVOID param)
{
 CDownloadThread* pthread = (CDownloadThread*)param;
 
 NewModuleInfoList list = pthread->m_pWizSheet->GetNewModuleInfoList();//獲得要下載的文件列表
 HWND hWnd = AfxGetApp()->GetMainWnd()->m_hWnd;
 NewModuleInfoList::iterator item;
 
 WaitForSingleObject(pthread->m_pWizSheet->m_hNewModulesListMutex, INFINITE);
//順序下載各個文件
 for (item=list.begin(); item!=list.end(); item++)
 {
  if (item->isFullFile==FALSE && item->isSelected==TRUE)
   pthread->GetFile(*item);
 }
 SendMessage(hWnd, WM_MSG_DOWNMODULE, NOTIFY_MSG, DOWNLOAD_FINISH);//發下載完成消息,
    ReleaseMutex(pthread->m_pWizSheet->m_hNewModulesListMutex);
 
 return 0;
}

//This is the real thread function, it will open the url the
//caller supply, download the file specify by the url to the
//specify dir, and send the statistics messge to the UI to
//tell the user the progress
void CDownloadThread::GetFile(NewModuleInfo& newModuleInfo)
{
 HWND hWnd = AfxGetApp()->GetMainWnd()->m_hWnd;
 
 CInternetSession session;
 CStdioFile*    pFile     = NULL;
    CStdioFile     localFile;
 
 session.SetOption(INTERNET_OPTION_CONNECT_RETRIES, 10);  
 try
 {
  pFile = session.OpenURL(newModuleInfo.URL,      //打開URL
        1,
        INTERNET_FLAG_TRANSFER_BINARY
        | INTERNET_FLAG_RELOAD
        | INTERNET_FLAG_DONT_CACHE
        | INTERNET_FLAG_PASSIVE);  
  if (pFile == NULL)
  {
   //send connect failed message, notify the user an error encounter
   ReleaseMutex(m_pWizSheet->m_hNewModulesListMutex);
   SendMessage(hWnd, WM_MSG_DOWNMODULE, NOTIFY_MSG, CONNECT_FAILED);
   return;
  }

  CString strLen;
     DWORD len = 0;
  if (newModuleInfo.serverType == 0)//如果是HTTP,則得到文件長度
  {
   if (((CHttpFile*)pFile)->QueryInfo(HTTP_QUERY_CONTENT_LENGTH,strLen) != 0)
   {
    len = atol(strLen);
   }
  }
  else if (newModuleInfo.serverType == 1)
  {
   len = pFile->GetLength();          //如果是FTP,則得到文件長度,但實際調試發現FTP文件無法這樣得到長度
  }
  
  if (len < newModuleInfo.info.length)
  {
   len = newModuleInfo.info.length;
  }
  
  //Open a local file to store the internet file打開本地文件用於保存
  CString fileName = m_saveDir + "//" +newModuleInfo.info.fileName;
  BOOL bIsOK = localFile.Open(fileName,
     CFile::modeCreate | CFile::modeWrite
   | CFile::shareDenyWrite| CFile::typeBinary);
  if (bIsOK == FALSE)
  {
   //Send open file failed message, notify the user an error encounter
   ReleaseMutex(m_pWizSheet->m_hNewModulesListMutex);
   SendMessage(hWnd, WM_MSG_DOWNMODULE, NOTIFY_MSG, OPENFILE_FAILED);
   session.Close();
   return;
  }  
  localFile.Seek(0, CFile::begin);
  
  //Read the internet file to buffer and write to local file
  char buf[4096];
        memset(buf, 0, 4096);
  
  DWORD nTotal = 0;
  double dper = 0;
  int   per = 0;
  UINT ret = 0;
  CTime startTime = CTime::GetCurrentTime();
  CTimeSpan usedTime;

  do{
   ret = pFile->Read(buf, 4096);
   localFile.Write(buf, ret);    //保存文件
   nTotal += ret;
   
   usedTime = CTime::GetCurrentTime()-startTime;//計算用了多少時間
   Statics* staticsPack = new Statics;
   staticsPack->length = len;
   staticsPack->dwTotal = nTotal;
   staticsPack->usedTime = usedTime.GetTotalSeconds(); 
   //send the statistics infomation to the UI to show the progress
   SendMessage(hWnd, WM_MSG_DOWNMODULE, STATICS_MSG, (LPARAM)staticsPack);
   
   if (len > 0)
   { 
    dper = ((double)nTotal)/((double)len);
    per = 100*dper;
    if (per<5)
     per = 5;
    //Send the download percentage to UI to show the progress
    SendMessage(hWnd, WM_MSG_DOWNMODULE, PROGRS_MSG, per);//發送下載進度給主窗口
   }
   
  }while (ret != 0);
  
 
  //Do some cleaning
  if (pFile != NULL)
  {
   pFile->Close();
   delete pFile;
   pFile = NULL;
  }
  if (localFile.m_hFile != CFile::hFileNull)
  {
   localFile.Close();
  }   
 }
 catch(CInternetException InetEx)
 {
  ReleaseMutex(m_pWizSheet->m_hNewModulesListMutex);
        SendMessage(hWnd, WM_MSG_DOWNMODULE, NOTIFY_MSG, NETWORK_EXCEPTION);
  pFile = NULL;
  session.Close();
  return;
 }
 catch(CFileException fileEx)
 {
  ReleaseMutex(m_pWizSheet->m_hNewModulesListMutex);
  SendMessage(hWnd, WM_MSG_DOWNMODULE, NOTIFY_MSG, FILE_EXCEPTION);
  if (pFile != NULL)
  {
   pFile->Close();
   pFile = NULL;
  }
  session.Close();
  return;
 }
 catch(...)
 {
  ReleaseMutex(m_pWizSheet->m_hNewModulesListMutex);
  SendMessage(hWnd, WM_MSG_DOWNMODULE, NOTIFY_MSG, NETWORK_EXCEPTION);
  pFile = NULL;
  session.Close();
  return;
 }

 newModuleInfo.isAvailable = TRUE;
 //Send download finished msg to UI to tell the user
 //that a module has been successfully downloaded.
 CString* msg = new CString(newModuleInfo.info.fileName);
 ReleaseMutex(m_pWizSheet->m_hNewModulesListMutex);
 SendMessage(hWnd, WM_MSG_DOWNMODULE, MODULENAME_MSG, (LPARAM)msg);
 
}

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
我可以在主窗口類中這樣使用該線程類:
void CWizardSheet::DownloadModules()

 if (!m_NewModuleInfoList.empty())//要下載的文件鏈表不爲空
 {
  CDownloadThread* pThread =
   new CDownloadThread(this, CDownloadThread::ThreadFunc);//生成一個線程類對象
  pThread->CreateThread();                                                        //啓動線程
 }
}

整個類實現也不難,你可以按你的需要,修改線程構造時接受的參數。
  有的些細節是要注意的,從上面的代碼看,要把一個類的成員函數做爲參數,該成員必須是靜態成員。從代碼你也看出,這有一個技巧,我們把線程類本身的this指針做爲參數傳給線程,這樣的做法的優點在於我們在線程函數ThreadFunc()中調用線程類的任何一個成員函數和成員變量,實現了良好的封裝性。該類的實現是參考《WIN32多線程程序設計(侯捷 譯)》286頁(此書絕對值得收藏)的一個例子來實現的,本人的描述可能有不清楚的地方,不懂的要以參閱此書,在www.infoxa.com有該書的電子版下載。

發佈了29 篇原創文章 · 獲贊 0 · 訪問量 10萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章