Chinaunix首页 | 论坛 | 博客
  • 博客访问: 2086662
  • 博文数量: 909
  • 博客积分: 4000
  • 博客等级: 上校
  • 技术积分: 12260
  • 用 户 组: 普通用户
  • 注册时间: 2008-05-06 20:50
文章分类

全部博文(909)

文章存档

2008年(909)

我的朋友

分类:

2008-05-06 22:40:23

一起学习
使用 CInternetSession 封装多线程 http 文件下载

作者:付黎

源代码下载

  如何下载一个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 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) {}

};

使用起来非常简单,让你的窗口从它派生,然后选择你感兴趣的事件重载之即可。

几点说明:

  1. 本类会自动使用IE里的连接设置,如果代理服务器需要帐号验证,会回调 DownloadFile_OnProxyValidate 让用户输入帐号密码;
  2. 因为使用了模版,所以不支持MFC丑陋的dynamic机制:-( ,请把 DECLARE_DYNAMIC 和 IMPLEMENT_DYNAMIC 宏从你的类中移除。如果你需要 运行时类型检查,可以用C 的RTTI机制dynamic_cast/typeid;

 

下载本文示例代码


使用 CInternetSession 封装多线程 http 文件下载使用 CInternetSession 封装多线程 http 文件下载使用 CInternetSession 封装多线程 http 文件下载使用 CInternetSession 封装多线程 http 文件下载使用 CInternetSession 封装多线程 http 文件下载使用 CInternetSession 封装多线程 http 文件下载使用 CInternetSession 封装多线程 http 文件下载使用 CInternetSession 封装多线程 http 文件下载使用 CInternetSession 封装多线程 http 文件下载使用 CInternetSession 封装多线程 http 文件下载使用 CInternetSession 封装多线程 http 文件下载使用 CInternetSession 封装多线程 http 文件下载
阅读(695) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~