MFC文件下载

来源:互联网 发布:java将字符转换成数字 编辑:程序博客网 时间:2024/05/23 19:03

        我们知道,windows有关网络连接的API在wininet.h里,而在MFC里,这些API被封装成了类。

CInternetSession类:直接继承自CObject类,该类用来建立与某个Internet服务器的会话

CInternetConnection类:帮助用户管理与Internet服务器的连接,同时还提供一些函数完成和响应服务器的通信

CInternetConnection类又派生出三个类:

CHttpConnection类:管理与HTTP服务器的连接

CFtpConnection类:用于管理与FTP服务器的连接

CGopherConnection类:管理与Gopher服务器的连接

我们要实现根据URL下载并保存文件,就需要CInternetSession、CHttpConnection、CHttpFile。

CHttpFile用来做什么呢?它可以关联起URL指向的一个WEB对象,由于CHttpFile继承与CFile,我们可以像读取一个CFile文件一样,把它从HTTP服务器上下载下来。

MFC就是通过对CHttpFile对象的读写来完成与HTTP服务器的对话的,包括GET/POST提交数据,接收数据。

1.CInternetSession     

    使用类CInternetSession 创建并初始化一个或多个同时的Internet会话。如果需要,还可描述与代理服务器的连接。如果Internet 连接必须在应用过程中保持着,可创建一个类CWinApp的CInternetSession成员。一旦已建起Internet 会话,就可调用OpenURL。CInternetSession会通过调用全局函数AfxParseURL来为分析映射URL。无论协议类型如何,CInternetSession 解释URL并管理它。它可处理由URL资源“file://”标志的本地文件的请求。如果传给它的名字是本地文件,OpenURL将返回一个指向CStdioFile对象的指针。

    如果使用OpenURLInternet服务器上打开一个URL,你可从此处读取信息。如果要执行定位在服务器上的指定的服务(例如,HTTPFTPGopher)行为,必须与此服务器建立适当的连接。直接打开与指定的服务器的指定的类型的连接,请使用下列成员函数:

·GetGopherConnection

打开与Gopher服务的连接。

·GetHttpConnection

打开与HTTP服务的连接。

·GetFtpConnection

打开与FTP服务的连接。

可使用以下代码连接到代理服务器:

    INTERNET_PROXY_INFO proxyinfo;
    proxyinfo.dwAccessType = INTERNET_OPEN_TYPE_PROXY;
    proxyinfo.lpszProxy =
"192.168.69.160:808";
    proxyinfo.lpszProxyBypass =
NULL;
    session.SetOption(
INTERNET_OPTION_PROXY,(LPVOID)&proxyinfo,
        sizeof(
INTERNET_PROXY_INFO));

   QueryOptionSetOption允许设置会话的查询选项,如超时值、再试次数等等。Internet会话过程中,象查找或数据下载这样的事务处理会占用一定的时间。使用者可能想继续工作,或获得事务处理进程的状态信息。为解决这个问题,CInternetSession可以让查找和数据传输异步发生,允许使用者在传输结束时进行其它任务。如果要为使用者提供状态信息,或异步处理任意操作,必须设置三个条件:

 1 

在构造函数中,dwFlags必须包括INTERNET_FLAG_ASYNC。

 2

在构造函数中,dwContext必须设置为1。

 3

必须通过调用EnableStatusCallback来建立回调函数。

    使用覆盖成员函数OnStatusCallback来获得异步获取的状态信息。使用此覆盖成员函数,必须从CInternetSession派生你自己的类。

设置会话的查询选项可使用如下代码

    CInternetSessionsession;

    session.SetOption(INTERNET_OPTION_CONNECT_TIMEOUT, 15000);      // 15秒的连接超时
    session.SetOption(INTERNET_OPTION_SEND_TIMEOUT, 1000);           // 1秒的发送超时
    session.SetOption(INTERNET_OPTION_RECEIVE_TIMEOUT, 37000);        // 37秒的接收超时
    session.SetOption(INTERNET_OPTION_DATA_SEND_TIMEOUT, 1000);     // 1秒的发送超时
    session.SetOption(INTERNET_OPTION_DATA_RECEIVE_TIMEOUT, 37000);       // 37秒的接收超时
    session.SetOption(INTERNET_OPTION_CONNECT_RETRIES, 10);          // 10次重试

2.CHttpConnection

    MFC类CHttpConnection管理与HTTP服务器的连接。HTTP是用MFCWinInet类实现的三个Internet服务器协议之中的一个。类CHttpConnection包含一个构造函数和一个成员函数OpenRequest,使用HTTP协议来管理与服务器的连接。要与一个HTTP服务器通讯,必须先构造一个CInternetSession的实例,然后构造一个CHttpConnection对象。不能直接构造一个CHttpConnection对象,而是调用CInternetSession::GetHttpConnection,创建CHttpConnection对象并返回其指针。

3.CHttpFile

       CHttpFile提供向HTTP服务器中请求和读取的功能。如果Internet会话要从一个HTTP服务器中读取数据,则必须构造一个CHttpFile实例。

可用如下代码建立与互联网上文件的连接,实现文件下载的前提,这也是文件下载的核心代码了。

CInternetSession session;

CHttpConnection* pHttpConnection = NULL;

CHttpFile* pHttpFile = NULL;

pHttpConnection = session.GetHttpConnection(strServer, wPort);

CString strServer, strObject;
INTERNET_PORT wPort;

DWORD dwType;

AfxParseURL(“待下载文件的URL”, dwType, strServer, strObject, wPort);

        pHttpConnection = session.GetHttpConnection(strServer, wPort);

        pHttpFile = pHttpConnection->OpenRequest(CHttpConnection::HTTP_VERB_GET, strObject);


附(文件下载函数)

bool XXX::Download(const CString& strFileURLInServer, //待下载文件的URL 
              const CString & strFileLocalFullPath)//存放到本地的路径 

    ASSERT(strFileURLInServer != "");
    ASSERT(strFileLocalFullPath != "");
    CInternetSession session;
    CHttpConnection* pHttpConnection = NULL;

    CHttpFile* pHttpFile = NULL;


    INTERNET_PROXY_INFO proxyinfo;
    proxyinfo.dwAccessType = INTERNET_OPEN_TYPE_PROXY;
    proxyinfo.lpszProxy ="192.168.69.160:808";
    proxyinfo.lpszProxyBypass = NULL;
    session.SetOption(INTERNET_OPTION_PROXY,(LPVOID)&proxyinfo,
        sizeof(INTERNET_PROXY_INFO));

    session.SetOption(INTERNET_OPTION_CONNECT_TIMEOUT, 15000);      // 15秒的连接超时
    session.SetOption(INTERNET_OPTION_SEND_TIMEOUT, 1000);           // 1秒的发送超时
    session.SetOption(INTERNET_OPTION_RECEIVE_TIMEOUT, 37000);        // 37秒的接收超时
    session.SetOption(INTERNET_OPTION_DATA_SEND_TIMEOUT, 1000);     // 1秒的发送超时
    session.SetOption(INTERNET_OPTION_DATA_RECEIVE_TIMEOUT, 37000);       // 37秒的接收超时
    session.SetOption(INTERNET_OPTION_CONNECT_RETRIES, 10);          // 10次重试

    CString strServer, strObject;
    INTERNET_PORT wPort;
    DWORD dwType;

    char* pszBuffer = NULL;
    try
    {
        AfxParseURL(strFileURLInServer, dwType, strServer, strObject, wPort);
        pHttpConnection = session.GetHttpConnection(strServer, wPort);
        pHttpFile = pHttpConnection->OpenRequest(CHttpConnection::HTTP_VERB_GET, strObject);
        if (pHttpFile->SendRequest() == FALSE)
            return false;
        DWORD dwStateCode;
        pHttpFile->QueryInfoStatusCode(dwStateCode);

        if (dwStateCode == HTTP_STATUS_OK)
        {
            HANDLE hFile = CreateFile(strFileLocalFullPath, GENERIC_WRITE,

            FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL,

                NULL);  //创建本地文件

            if (hFile == INVALID_HANDLE_VALUE)

            {

                pHttpFile->Close();
                pHttpConnection->Close();
                session.Close();
                return false;
            }

            char szInfoBuffer[1000];  //返回消息
            DWORD dwFileSize = 0;   //文件长度
            DWORD dwInfoBufferSize = sizeof(szInfoBuffer);
            BOOL bResult = FALSE;
            bResult = pHttpFile->QueryInfo(HTTP_QUERY_CONTENT_LENGTH,
                (void*)szInfoBuffer, &dwInfoBufferSize, NULL);
            dwFileSize = atoi(szInfoBuffer);
            const int BUFFER_LENGTH = 1024 * 10;
            pszBuffer = new char[BUFFER_LENGTH];  //读取文件的缓冲
            DWORD dwWrite, dwTotalWrite;
            dwWrite = dwTotalWrite = 0;
            UINT nRead = pHttpFile->Read(pszBuffer, BUFFER_LENGTH); //读取服务器上数据

            while (nRead > 0)
            {
                WriteFile(hFile, pszBuffer, nRead, &dwWrite, NULL);  //写到本地文件
                dwTotalWrite += dwWrite;
                nRead = pHttpFile->Read(pszBuffer, BUFFER_LENGTH);
            }

            delete[]pszBuffer;
            pszBuffer = NULL;
            CloseHandle(hFile);
        }
        else
        {
            delete[]pszBuffer;
            pszBuffer = NULL;
            if (pHttpFile != NULL)
            {
                pHttpFile->Close();
                delete pHttpFile;
                pHttpFile = NULL;
            }
            if (pHttpConnection != NULL)
            {
                pHttpConnection->Close();
                delete pHttpConnection;
                pHttpConnection = NULL;
            }
            session.Close();
            return false;
        }
    }
    catch (...)
    {
        delete[]pszBuffer;
        pszBuffer = NULL;
        if (pHttpFile != NULL)
        {
            pHttpFile->Close();
            delete pHttpFile;
            pHttpFile = NULL;
        }
        if (pHttpConnection != NULL)
        {
            pHttpConnection->Close();
            delete pHttpConnection;
            pHttpConnection = NULL;
        }
        session.Close();
        return false;
    }

    if (pHttpFile != NULL)

        pHttpFile->Close();

    if (pHttpConnection != NULL)

        pHttpConnection->Close();

    session.Close();

    return true;

}

利用以上的下载函数,是可以实现下载功能的,需要说明一下的是,要测试下载功能需要有一个下载的连接地址URL,也许你还不清除这个要怎么弄来,这里也说明下:URL不是云盘上的目录地址,而是下载链接,打开下载管理器,右键即可复制了



我们来做个简单的下载工具,模拟这个Sogou的下载管理器。


有了下载函数,主要还要做以下功能:

1、进度条

2、图片按钮

3、开启定时器来显示下载进度

4、控件背景

5、下载类型的文件图标

下面就来解决这些问题:

1、进度条

文件下载是比较耗能的工作,因此是不适合在下载函数里显示进度条进度的,事实上,进度条显示的,都是时间比较长的工作,不然干嘛需要用进度条来显示进度从而告诉我们程序没卡死呢,因此,进度条的显示都是开线程或者用线程回调来做的。

获得进度条id

先定义 CProgressCtrl  m_ctrProgress;  //进度条控制函数

再在下面的地方添加:

void CdowmloadDlg::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
    DDX_Control(pDX, IDC_PROGRESS1, m_ctrProgress);

}

在头文件添加声明

DWORD _stdcall ShowThread(LPVOIDlpParam);

在cpp文件写下面的线程处理函数
DWORD _stdcall ShowThread(LPVOIDlpParam)
{  
    m_ctrProgress.SetRange32(0,filesize);//设置进度条的上下界
    m_ctrProgress.SetPos(dwTotalWrite);//当前进度
    return 0;
}

在文件下载的函数循环里创建线程

CreateThread(NULL,0,ShowThread,NULL,0,NULL);

2、图片按钮

图片按钮的方法有几种的:

1、按钮贴图,然后再改变按钮样式来实现

按钮属性里的Bitmap要选为TRUE‘

(1)首先建立位图句柄,

  1. // 方法一:加载非资源图片  
  2. HBITMAP   hBitmap;   
  3. hBitmap = (HBITMAP)::LoadImage(   
  4. NULL,   
  5. "E://a.bmp",                           // 图片全路径  
  6. IMAGE_BITMAP,                          // 图片格式  
  7. 0,0,   
  8. LR_LOADFROMFILE|LR_CREATEDIBSECTION);  // 注意LR_LOADFROMFILE  
  9. // 方法二:加载资源图片  
  10. HBITMAP   hBitmap;   
  11. hBitmap = LoadBitmap(AfxGetInstanceHandle(),   
  12.     MAKEINTRESOURCE(IDB_BITMAP_TEST)); // IDB_BITMAP_TEST为资源图片ID 

(2)获得按钮句柄并调用SetBitmap即可

  1. ((CButton *)GetDlgItem(IDC_BUTTON_TEST))->SetBitmap(hBitmap);

2、自己绘制

按钮属性里的Owner Draw要选为TRUE,按钮自定义绘图,需要用户在OndrawItem消息处理函数中绘制按钮的外观。代码大概用下面的

                 CRect rect;
                 Btn_start->GetWindowRect(rect);
                 ScreenToClient(rect);// 
                 CBitmap bitmap;
                 bitmap.LoadBitmap(IDB_BITMAP1);//其中IDB_BITMAP是位图名称
                 CBrush brush(&bitmap);
                 CClientDC dc(this);
                 dc.FillRect(rect,&brush);

3、用MFC现成的类CBitmapButton,我就是用这个来实现的,方便点,就是有点局限

(1)在头文件中声明

CBitmapButtonbmpBtnStart,bmpBtnStop,bmpBtnCancle;

(2)在cpp文件中的BOOL CxxxDlg::OnInitDialog()中

       bmpBtnStart.LoadBitmaps(IDB_start,IDB_startOn); //绑定图片,IDB_BTN_LOGIN为图片资源名,资源需要事先导入
       bmpBtnStart.
SubclassDlgItem(IDC_BTN_START,this); //绑定控件ID
       bmpBtnStart.
SizeToContent();            //设置控件大小与图片相同

       bmpBtnStop.
LoadBitmaps(IDB_stop,IDB_stopOn);
       bmpBtnStop.
SubclassDlgItem(IDC_BTN_STOP,this);
       bmpBtnStop.
SizeToContent();


       bmpBtnCancle.
LoadBitmaps(IDB_close,IDB_closeOn);
       bmpBtnCancle.
SubclassDlgItem(IDC_BTN_cancel,this);
       bmpBtnCancle.
SizeToContent();

当然需要先在资源文件里加载图片资源。



3、开启定时器来显示下载进度

(1)头文件中什么消息响应函数void OnTimer(UINT_PTR nIDEvent);

(2)cpp中首先要添加消息 ON_WM_TIMER()

(3)cpp中定时器响应函数

void CxxxDlg::OnTimer(UINT_PTR nIDEvent)
{     
    switch (nIDEvent)    
    {     
    case 1:  // 计时器1                
        .... // 处理的代码                
            KillTimer(1);  // 删除此计时器,否则计时器中断完后会自动重新开始计时,到下一次中断时还会发生新的中断               
        break;     
    case 2:  // 计时器2                
        .... // 处理的代码               
            break;    
    default:                
        MessageBox("default: KillTimer");               
        KillTimer(nIDEvent);               
        break;    
    }
}

(4)启动一个1秒钟的定时器SetTimer(1,1000, NULL);

关闭该定时器 KillTimer(1);

4、控件背景

(1)头文件里声明 HBRUSH OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor);

(2)cpp里添加重载消息 ON_WM_CTLCOLOR()

(3)重载函数

HBRUSH CdowmloadDlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
{
    HBRUSH hbr = CDialog::
OnCtlColor(pDC, pWnd, nCtlColor);
    switch(nCtlColor)
    {
      case
CTLCOLOR_STATIC: //静态文本控件
      {
//       pDC->SetBkMode(TRANSPARENT); 
//       //pDC->SetTextColor(RGB(255,255,0)); //设置字体颜色
//       //pWnd->SetFont(cfont); //设置字体
//       HBRUSH B = CreateSolidBrush(RGB(125,125,255)); //创建画刷
        //pDC->SetTextColor(RGB(250,250,250));

        pDC->SetBkColor(RGB(250,250,250));
      return (HBRUSH)staticBrush; 
      }
//       case CTLCOLOR_BTN: //按钮控件
//       {
//       pDC->SetBkMode(TRANSPARENT);
//      /* pDC->SetTextColor(RGB(255,255,0));
//       pWnd->SetFont(cfont); */
//       HBRUSH B = CreateSolidBrush(RGB(125,125,255)); 
//       return (HBRUSH) B; 
//       }

    }
    return hbr;
}

5、下载类型的文件图标

(1)添加一个picture control的控件,其实它用起来和static控件差不多

(2)判断待下载的图片类型,然后用对应的图标显示就可以了。下面为加载图片代码

CStatic *pStatic = (CStatic *)GetDlgItem(IDC_picture);
        //加载资源图片
        HBITMAP   hBitmap;
        hBitmap = LoadBitmap(AfxGetInstanceHandle(),   
                     MAKEINTRESOURCE(IDB_zip)); // IDB_BITMAP_TEST为资源图片ID 


程序效果图:






文件下载的源代码http://download.csdn.net/detail/sns1991sns/9150529



0 0