开发者社区> 杨粼波> 正文

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

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

源代码下载
/*

URLDownloadToFile();

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

*/
如何下载一个http文件?我们当然可以用socket自己实现http协议去做,但费时费力还易出bug,对于一个客户端程序稳定易维护是第一位的,所幸MS给我们提供了功能强大的internet API函数族,MFC的CInternetSession对它们进行了一些简单的封装,但如此简单的封装对我等拿来主义者来说只是个半成品。必须经过再加工才能食用。

先来介绍一下CInternetSession的使用:

下面的代码是读取链接的基本方法:


None.gif// CInternetSession在遇到一些错误时会抛出异常,因此必须包起来
None.gif
TRY
ExpandedBlockStart.gif{
InBlock.gif      CInternetSession     sess ;
InBlock.gif
InBlock.gif      // 统一以二进制方式下载
InBlock.gif
      DWORD         dwFlag = INTERNET_FLAG_TRANSFER_BINARY|INTERNET_FLAG_DONT_CACHE|INTERNET_FLAG_RELOAD ;
InBlock.gif      CHttpFile     * pF = (CHttpFile*)sess.OpenURL(strFilename, 1, dwFlag); ASSERT(pF);
InBlock.gif      if (!pF)
ExpandedSubBlockStart.gif          {AfxThrowInternetException(1);}
InBlock.gif
InBlock.gif      // 得到文件大小
InBlock.gif
      CString        str ;
InBlock.gif      pF->QueryInfo (HTTP_QUERY_CONTENT_LENGTH, str) ;
InBlock.gif      int     nFileSize = _ttoi(str) ;
InBlock.gif
InBlock.gif      char     * p = new[nFileSize] ;
InBlock.gif      while (true)
ExpandedSubBlockStart.gif      {
InBlock.gif          // 每次下载8Kb
InBlock.gif
          int     n = pF->Read (p, (nFileSize < 8192) ? nFileSize : 8192) ;
InBlock.gif          if (n <= 0)
InBlock.gif              break ;
InBlock.gif          p += n ; nFileSize -= n ;
ExpandedSubBlockEnd.gif      }

InBlock.gif      delete[] p ;
InBlock.gif      delete pF ;
ExpandedBlockEnd.gif}

ExpandedBlockStart.gifCATCH_ALL(e) {}
None.gifEND_CATCH_ALL
  这段代码有一个问题,在获取文件大小这个地方,对于静态网页 HTTP_QUERY_CONTENT_LENGTH 查询会返回文件大小,但对于asp,php这样的动态网页,查询会返回0。必须通过不断的调用 CHttpFile::GetLength 来一点一点累加内容,就像这样:

None.gifint     n = pF->GetLength() ;
None.gifwhile (n)
ExpandedBlockStart.gif{
InBlock.gif      int     * p = new BYTE[n] ;
InBlock.gif      pF->Read (p, n) ;
InBlock.gif      delete[] p ;
InBlock.gif      n = pF->GetLength() ;
ExpandedBlockEnd.gif}

None.gif
None.gif不过网络断线同样会让 GetLength 返回0,必须把这种情况屏蔽掉。 
None.gifif (n == 0)
ExpandedBlockStart.gif{
InBlock.gif      DWORD     dw ;
InBlock.gif      if (::InternetQueryDataAvailable ((HINTERNET)(*pF), &dw, 0, 0) && (dw == 0))
ExpandedSubBlockStart.gif      {
InBlock.gif          // 到这里就代表文件下载成功了
ExpandedSubBlockEnd.gif
      }

ExpandedBlockEnd.gif}

None.gif
None.gif
   OK,我们已经把机制摸清了,剩下就是把这些体力活全扔进线程里,又一个麻烦产生了:线程里如何向外界通知事件(开始下载,下载完成之类)呢?直接调用回调函数当然可以,但这时回调函数是置于我们的线程中,造成在回调函数中对资源的访问必须非常小心,防止多线程冲突。下一步,加锁同步...。
挣扎在多线程泥潭中的人已经够多的了,其实我们有一个更安全方便的方法,借助 SendMessage 把线程里的事件发送到窗口线程统一处理,windows会帮我们把所有消息排队执行,相当于把多线程程序转成了单线程^_^ (我一个同事把此类用于包含数百个线程的爬虫程序中,非常稳定)

封装结果及使用:

None.giftemplate<class T>
None.gifclass FCDownloadFileWndBase : public T
ExpandedBlockStart.gif{
InBlock.gifpublic:
InBlock.gif      // 默认构造函数
ExpandedSubBlockStart.gif
      FCDownloadFileWndBase () {}
InBlock.gif      // CDialog 构造函数
ExpandedSubBlockStart.gif
      FCDownloadFileWndBase (UINT nID, CWnd* pParent) : T(nID, pParent) {}
InBlock.gif      // CFormView 构造函数
ExpandedSubBlockStart.gif
      FCDownloadFileWndBase (UINT nID) : T(nID) {}
InBlock.gif
InBlock.gif      // 创建一个线程下载文件URL,如果URL正在下载中,此函数什么也不做立即返回
InBlock.gif
      void DownloadFile (LPCTSTR strFileURL, int nPriority=THREAD_PRIORITY_IDLE) ;
InBlock.gif
InBlock.gifprotected:
InBlock.gif      // 检查链接最后修改时间,有些服务器会禁止查看时间,strTime为空
InBlock.gif      
// 用户必须重载实现本接口,返回TRUE则继续下载文件,返回FALSE则不再下载文件
InBlock.gif
      virtual BOOL DownloadFile_OnCheckTime (CString strFileURL, CString strTime) =0 ;
InBlock.gif
InBlock.gif      // 当链接成功下载完成后会调用此接口
ExpandedSubBlockStart.gif
      virtual void DownloadFile_OnFinished (CString strFileURL, char* pBuffer, int nLength) {}
InBlock.gif
InBlock.gif      // 当IE设置代理服务器并且服务器需要帐号认证时候回调
ExpandedSubBlockStart.gif
      virtual void DownloadFile_OnProxyValidate (CString strFileURL, CString& strUsername, CString& strPassword) {}
InBlock.gif
InBlock.gif      // 出现错误时回调
ExpandedSubBlockStart.gif
      virtual void DownloadFile_OnError (CString strFileURL) {}
InBlock.gif
InBlock.gif      // 开始下载一个链接
ExpandedSubBlockStart.gif
      virtual void DownloadFile_OnStartDownload (CString strFileURL) {}
InBlock.gif
InBlock.gif      // 当前进度,每下载一块数据就会回调
ExpandedSubBlockStart.gif
      virtual void DownloadFile_OnProgress (CString strFileURL, int nNow, int nTotal) {}
ExpandedBlockEnd.gif}
;
None.gif
使用起来非常简单,让你的窗口从它派生,然后选择你感兴趣的事件重载之即可。

几点说明:

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

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
jira学习案例25-用fetch封装http请求
jira学习案例25-用fetch封装http请求
5 0
Spring boot 封装的http调用工具 RestTemplate 代码简洁好用
经常会调用一些接口,需要自己写httpclient,后面会把java自带的调用例子补上。后来发现spring封装好了一工具比较实用,这里备忘一下,方便后面查阅和使用。后面还会更新一些新的内容。
35 0
vue2 + axios http请求封装
vue2 + axios http请求封装
69 0
微信小程序请求封装(http请求详解)
微信小程序请求封装(http请求详解)
132 0
RestTemplate:Spring 封装的 HTTP 同步请求类
RestTemplate:Spring 封装的 HTTP 同步请求类
501 0
RestTemplate:Spring 封装的 HTTP 同步请求类
RestTemplate:Spring 封装的 HTTP 同步请求类
2009 0
二十分钟封装,一个App前后台Http交互的实现
二十分钟封装,一个App前后台Http交互的实现
629 0
vue + axios---封装一个http请求
在使用vue开发时,官方推荐使用axios来请求接口 // axios官方地址 https://github.com/axios/axios 但是axios并不像 vue-resource 一样拥有install,即不能直接 Vue.use(axios) 来使用,所以需要我们自己根据axios来写一个具有install方法的Http库。
3075 0
WinForm多线程实现HTTP网络检测工具
一、背景描述与课程介绍 明人不说暗话,跟着阿笨一起玩WinForm。本次分享课程属于《C#高级编程实战技能开发宝典课程系列》中的一部分,阿笨后续会计划将实际项目中的一些比较实用的关于C#高级编程的技巧分享出来给大家进行学习,不断的收集、整理和完善此系列课程! 本次分享课程给大家带来一个基于WinForm桌面应用程序开发的HTTP网络检查工具,希望对的没有接触过C/S架构开发的童鞋有一定的了解和认识吧。
1536 0
+关注
杨粼波
网游的老兵
文章
问答
文章排行榜
最热
最新
相关电子书
更多
阿里巴巴HTTP 2.0实践及无线通信协议的演进之路
立即下载
CDN助力企业网站进入HTTPS时代
立即下载
低代码开发师(初级)实战教程
立即下载