使用 CInternetSession 封装多线程 http 文件下载

简介: 因为使用了模版,所以不支持MFC丑陋的dynamic机制:-( ,请把 DECLARE_DYNAMIC 和 IMPLEMENT_DYNAMIC 宏从你的类中移除。如果你需要运行时类型检查,可以用C++的RTTI机制dynamic_cast/typeid

源代码下载
/*

URLDownloadToFile();

主要是下载升级包..............................

*/
如何下载一个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) {}
}
;
使用起来非常简单,让你的窗口从它派生,然后选择你感兴趣的事件重载之即可。

几点说明:

  1. 本类会自动使用IE里的连接设置,如果代理服务器需要帐号验证,会回调 DownloadFile_OnProxyValidate 让用户输入帐号密码;
  2. 因为使用了模版,所以不支持MFC丑陋的dynamic机制:-( ,请把 DECLARE_DYNAMIC 和 IMPLEMENT_DYNAMIC 宏从你的类中移除。如果你需要运行时类型检查,可以用C++的RTTI机制dynamic_cast/typeid;
目录
相关文章
|
14天前
|
JSON 数据格式
.net HTTP请求类封装
`HttpRequestHelper` 是一个用于简化 HTTP 请求的辅助类,支持发送 GET 和 POST 请求。它使用 `HttpClient` 发起请求,并通过 `Newtonsoft.Json` 处理 JSON 数据。示例展示了如何使用该类发送请求并处理响应。注意事项包括:简单的错误处理、需安装 `Newtonsoft.Json` 依赖,以及建议重用 `HttpClient` 实例以优化性能。
55 2
|
8月前
|
JSON Java 数据安全/隐私保护
java中的http请求的封装(GET、POST、form表单、JSON形式、SIGN加密形式)
java中的http请求的封装(GET、POST、form表单、JSON形式、SIGN加密形式)
600 1
|
6月前
|
JSON Dart API
Flutter dio http 封装指南说明
本文介绍了如何实现一个通用、可重构的 Dio 基础类,包括单例访问、日志记录、常见操作封装以及请求、输出、报错拦截等功能。
148 2
Flutter dio http 封装指南说明
|
6月前
|
Java Spring
spring restTemplate 进行http请求的工具类封装
spring restTemplate 进行http请求的工具类封装
245 3
|
6月前
|
Go 开发者
golang的http客户端封装
golang的http客户端封装
119 0
|
7月前
|
缓存 负载均衡 NoSQL
Redis系列学习文章分享---第十四篇(Redis多级缓存--封装Http请求+向tomcat发送http请求+根据商品id对tomcat集群负载均衡)
Redis系列学习文章分享---第十四篇(Redis多级缓存--封装Http请求+向tomcat发送http请求+根据商品id对tomcat集群负载均衡)
94 1
|
6月前
|
网络协议 安全 Python
我们将使用Python的内置库`http.server`来创建一个简单的Web服务器。虽然这个示例相对简单,但我们可以围绕它展开许多讨论,包括HTTP协议、网络编程、异常处理、多线程等。
我们将使用Python的内置库`http.server`来创建一个简单的Web服务器。虽然这个示例相对简单,但我们可以围绕它展开许多讨论,包括HTTP协议、网络编程、异常处理、多线程等。
|
8月前
|
资源调度 JavaScript API
|
8月前
|
监控 网络安全 C++
Qt 5.14.2 网络编程揭秘:构建高效HTTP客户端与文件下载器
Qt 5.14.2 网络编程揭秘:构建高效HTTP客户端与文件下载器
512 0
|
8月前
【web server】HTTP协议如何解析or封装
【web server】HTTP协议如何解析or封装