一个下载文件的线程类

  有时候,我们需要通过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有该书的电子版下载。

你可能感兴趣的:(exception,File,null,download,internet,statistics)