转载​​​​​​ Qt中使用Http下载文件并实时显示进度 - 知乎

前几天写了一篇关于Http通讯进行GET、POST的操作,那么,今天的这篇文章也是基于Http通讯的,但是功能不同。

当前文章讲解的主要功能是:

使用Http/Https通讯下载文件并将下载进度实时显示到页面上。

对于下载文件来说,不是很难,我们可以从网上搜索。难点在于如何将下载进度实时的呈现给用户呢?

首先,想要在下载的同时并实时的将数据传送给页面,我们可以用发消息的方式通知给页面。在以下讲解中,我使用的回调方法。

发消息不是很简单的解决了吗?为什么还要搞那么复杂?

有些刚刚学编程的人甚至对回调不熟悉,我想说明下我为什么要使用回调。看过我以往写过文章的人会知道(csdn上),我之前的开发环境是MFC框架,也是最近这几年转成了QT框架开发。两个框架的消息通知是不一致的,但是!它们都是基于标准C++语言的。只是书写的形式不同。

在写一些核心的功能性代码时,最好采用标准语言来写,当我们以后切换框架时,不会修改核心代码,只是更改消息机制。而且,在我们开发过程中,最好功能性代码与业务处理代码分开(仅做建议,根据个人书写习惯决定)

话不多说,我们来讲解下实现方法。

1:Http通讯下载

1.1:分解指定的Url

需要用到的API:WinHttpCrackUrl

The WinHttpCrackUrl function separates a URL into its component parts such as host name and path.

将Url分割成多个组成部分,如主机名和路径

假设,我们需要访问的http的Url路径用 std::string strRemoteFile表示。

DWORD  dwErrorCode = NO_ERROR;
URL_INFO url_info = { 0 };
URL_COMPONENTSW lpUrlComponents = { 0 };
lpUrlComponents.dwStructSize = sizeof(lpUrlComponents);
lpUrlComponents.lpszExtraInfo = url_info.szExtraInfo;
lpUrlComponents.lpszHostName = url_info.szHostName;
lpUrlComponents.lpszPassword = url_info.szPassword;
lpUrlComponents.lpszScheme = url_info.szScheme;
lpUrlComponents.lpszUrlPath = url_info.szUrlPath;
lpUrlComponents.lpszUserName = url_info.szUserName;lpUrlComponents.dwExtraInfoLength = 512;
lpUrlComponents.dwHostNameLength = 512;
lpUrlComponents.dwPasswordLength = 512;
lpUrlComponents.dwSchemeLength = 512;
lpUrlComponents.dwUrlPathLength = 512;
lpUrlComponents.dwUserNameLength = 512;if (!WinHttpCrackUrl(stringToWString(strRemoteFile).c_str(), 0, ICU_ESCAPE, &lpUrlComponents))
{return GetLastError();
}

在这里,我需要强调的是512这个长度。一般应用这个函数时是不会出错的,但是,有一次我就获取到了错误,经过发现是我的路径太长太长了,超过了512,简直让我吐血,后来再后台做了限制,不让访问过长的路径。不过,一般情况下使用512是没有问题的,当时做的是极限测试,也请大家注意这一点。

stringToWString这个函数的将string类型转成Wstring类型,以前的文章中有写,有需要可以查看,这里不是重点。

URL_INFO的定义,如下:

typedef struct
{WCHAR szScheme[512];WCHAR szHostName[512];WCHAR szUserName[512];WCHAR szPassword[512];WCHAR szUrlPath[512];WCHAR szExtraInfo[512];
}URL_INFO, *PURL_INFO;

1.2:创建一个会话

需要用到的API:WinHttpOpen

The WinHttpOpen function initializes, for an application, the use of WinHTTP functions and returns a WinHTTP-session handle.

初始化WinHTTP函数的使用,并返回一个WinHTTP会话句柄。

HINTERNET hSession = WinHttpOpen(NULL, WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, NULL);

1.3:创建一个连接

需要用到的API:WinHttpConnect

The WinHttpConnect function specifies the initial target server of an HTTP request and returns an HINTERNET connection handle to an HTTP session for that initial target.

指定HTTP请求的初始目标服务器,并返回该初始目标的HTTP会话的HINTERNET连接句柄。

HINTERNET hConnect = WinHttpConnect(hSession, lpUrlComponents.lpszHostName, lpUrlComponents.nPort, 0);

1.4:创建请求句柄

需要用到的API:WinHttpOpenRequest、WinHttpSetOption

整个下载文件区分Http/Https的核心来了!

我们在使用两种不同方式下载时,所对应的参数也是不一致的,咱们直接上代码更清楚,哈哈

DWORD dwFlags;
if (enumType == HttpType_HTTP)
{dwFlags=WINHTTP_FLAG_REFRESH;
}
else if (enumType == HttpType_HTTPS)
{dwFlags = WINHTTP_FLAG_REFRESH| WINHTTP_FLAG_SECURE;
}
HINTERNET hRequest = WinHttpOpenRequest(hConnect, L"GET", lpUrlComponents.lpszUrlPath, L"HTTP/1.1", WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, dwFlags);
if (enumType == HttpType_HTTPS)
{DWORD  dwFlags = SECURITY_FLAG_IGNORE_UNKNOWN_CA |SECURITY_FLAG_IGNORE_CERT_WRONG_USAGE |SECURITY_FLAG_IGNORE_CERT_CN_INVALID |SECURITY_FLAG_IGNORE_CERT_DATE_INVALID;//设置支持httpsif (!WinHttpSetOption(hRequest, WINHTTP_OPTION_SECURITY_FLAGS, &dwFlags, sizeof(dwFlags))){return GetLastError();}
}

其中,enumType是Http/Https的枚举类型。

1.5:发送请求

需要用到的API:WinHttpSendRequest

The WinHttpSendRequest function sends the specified request to the HTTP server.

向HTTP服务器发送指定的请求

if (!WinHttpSendRequest(hRequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0, WINHTTP_NO_REQUEST_DATA, 0, 0, 0))
{return GetLastError();
}

1.6:等待接收请求响应

需要用到的API:WinHttpReceiveResponse

The WinHttpReceiveResponse function waits to receive the response to an HTTP request initiated by WinHttpSendRequest.

等待接收由WinHttpSendRequest发起的HTTP请求的响应。当WinHttpReceiveResponse成功完成时,状态代码和响应头已经被接收,应用程序可以使用WinHttpQueryHeaders进行检查。 在使用WinHttpQueryDataAvailable和winhttppreaddata访问响应实体(如果有的话)之前,应用程序必须调用WinHttpReceiveResponse。

if (!WinHttpReceiveResponse(hRequest, 0))
{return GetLastError();
}

1.7:文件总体大小获取

需要用到的API:WinHttpQueryHeaders

The WinHttpQueryHeaders function retrieves header information associated with an HTTP request.

经过前面6个步骤的操作后,如果没有任何错误,我们就可以获取服务器上的文件了。首先第一步需要获取文件的总体大小。其中获取文件大小的另一个功能是需要将该大小传递给页面。

在实时显示下载进度时需要知道文件的总大小。这里我采用的是进度条的方式,简单方便查看。

DWORD dwContentSize=0,dwIndex = 0, dwSizeDW = sizeof(dwSizeDW);
WinHttpQueryHeaders(hRequest, WINHTTP_QUERY_CONTENT_LENGTH | WINHTTP_QUERY_FLAG_NUMBER, NULL, &dwContentSize, &dwSizeDW, &dwIndex);
if (m_pCallBackTotalData != nullptr)
{m_pCallBackTotalData(dwContentSize);
}

其中,m_pCallBackTotalData是回调函数,只作用于回调文件总长度。

1.8:创建本地文件

BYTE *pBuffer = new BYTE[4096];
HANDLE  hFile = CreateFile(StringToWString(strLocalFile).c_str(), GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL);
if (hFile == INVALID_HANDLE_VALUE)
{//创建文件失败return GetLastError();
}

以上代码就不用我再过多解释了,只是一个文件的创建,很简单。

1.9:读取有效数据

需要用到的API:WinHttpQueryDataAvailable、WinHttpReadData

The WinHttpQueryDataAvailable function returns the amount of data, in bytes, available to be read with WinHttpReadData.

在这里,我是以每次最大4096的长度读取。并将每次读取的长度实时的传递给页面

DWORD  dwSize, dwWrite, dwCalcSize = 0;
while (WinHttpQueryDataAvailable(hRequest, &dwSize) && dwSize)
{if (dwSize > 4096){dwSize = 4096;}if (m_pCallBackDownload != nullptr){dwCalcSize += dwSize;m_pCallBackDownload(dwCalcSize);}ZeroMemory(pBuffer, 4096);WinHttpReadData(hRequest, pBuffer, dwSize, &dwSize);WriteFile(hFile, pBuffer, dwSize, &dwWrite, NULL);
}
CloseHandle(hFile);
delete[]pBuffer;

1.10:关闭访问句柄

WinHttpCloseHandle(hRequest);
WinHttpCloseHandle(hConnect);
WinHttpCloseHandle(hSession);

这里没什么可说的,直接关闭即可。在程序使用的过程中,如果遇到了错误时,也要记得先关闭句柄再返回错误值。好习惯一定要养成。以上代码在return GetLastError时并没有写明,大家需要注意,我只是放到了最后统一说明,并不是不重要!

2:回调函数使用

第一部分只是说了在哪里使用回调函数,接下来我们实现以下回调函数的用法。

2.1:定义回调函数

当前功能中只用到了两个回调方法,那么我就以这两个回调为例。

 typedef void(*DownloadFilesProgressData)(int dwCurrentSize); //文件实时下载进度typedef void(*DownloadFilesProgressTotalData)(int dwTotalSize); //文件下载文件总长度

2.2:定义下载类中回调函数变量

DownloadFilesProgressData m_pCallBackDownload;
DownloadFilesProgressTotalData m_pCallBackTotalData;

2.3:回调函数在类中传递消息应用

第一部分已经介绍了,这里我也不做特殊说明了,看1.7、1.9即可

2.4:回调函数的注册

这一步骤是重点操作。我们设置了回调函数后,外面如何调用呢?用哪个函数来接收呢?

void SetDownloadFilesProgressMsg(DownloadFilesProgressTotalData callBackFuncTotalData, DownloadFilesProgressData callBackFunc); void HttpRequest::SetDownloadFilesProgressMsg(DownloadFilesProgressTotalData callBackFuncTotalData, DownloadFilesProgressData callBackFunc)
{//TODO:设置下载文件的进度回调消息m_pCallBackTotalData = callBackFuncTotalData;m_pCallBackDownload = callBackFunc;
}

2.5:外部应用使用

经过以上步骤,我们的回调函数的框架已经写好了,在我们实际调用的地方如何使用呢

/创建、注册下载类回调信息
m_pHttpResult = new HttpRequest();
m_pHttpResult->SetDownloadFilesProgressMsg(CallBackDownloadTotalData, CallBackDownloadCurrentData);

CallBackDownloadTotalData、CallBackDownloadCurrentData是在应用类中回调的响应函数,记住,必须使用static

static void CallBackDownloadTotalData(int dwTotalSize);
static void CallBackDownloadCurrentData(int dwCurrentSize);

写到这里的时候,至于你是用MFC框架的sendMessage/PostMessage或者是Qt的emit方法都随你。

3:页面展示

前面我说到了,在显示下载进度时使用了进度条,简单明了。进度条的使用不用我再过多解释了吧。

主要流程是:当我们需要下载某个文件时将进度条页面显示出来,随着从服务

使用Http下载文件并实时显示进度 --转载相关推荐

  1. python request下载文件时、显示进度以及网速_实时网速显示_实例_python

    import psutil import time from tkinter import * def make_app(): app =Tk() app.geometry('200x100') ap ...

  2. QT 下载文件时,显示进度以及网速

    //进度获取 bytesReceived 当前下载大小 bytesTotal 总下载大小 void MainWindow::downloadProgress(qint64 bytesReceived, ...

  3. java 上传 进度,关于 javaweb的文件上传实时显示进度

    方法:使用单例保存实时信息.具体的实现方法就是,当用户点击了处理按钮时,在后台开启一个线程进行处理,并且每进行到一步,就向单例中写入当前状态信息.然后编写一个servlet,用于返回单例中的信息,前台 ...

  4. 基于Jquery插件Uploadify实现实时显示进度条上传图片

    网址:http://www.jb51.net/article/83811.htm 这篇文章主要介绍了基于Jquery插件Uploadify实现实时显示进度条上传图片的相关资料,感兴趣的小伙伴们可以参考 ...

  5. android multipartentity 怎么上传参数,android-通过MultipartEntityBuilder通过HTTP表单上传文件,并显示进度b...

    android-通过MultipartEntityBuilder通过HTTP表单上传文件,并显示进度b 短版本-.jar已弃用,其升级版本java.lang.NoClassDefFoundError在 ...

  6. java ftp获取文件夹大小,java 用FTPClient 下载文件时不显示总大小?解决方案

    java 用FTPClient 下载文件时不显示总大小? FTPClient ftp = new FTPClient(); ftp.setControlEncoding("utf-8&quo ...

  7. android+后台下载notification,Android实现Service下载文件,Notification显示下载进度的示例...

    先放个gif..最终效果如果: 主要演示了Android从服务器下载文件,调用Notification显示下载进度,并且在下载完毕以后点击通知会跳转到安装APK的界面,演示是在真实的网络环境中使用真实 ...

  8. 使用Uploadify实现上传图片生成缩略图例子,实时显示进度条

    不了解Uploadify的,先看看前一篇详细说明 http://www.cnblogs.com/XuebinDing/archive/2012/04/26/2470995.html Uploadify ...

  9. 基于pyqt5 构建弹窗进度条,在大型计算中实时显示进度

    在大型计算时(例如神经网络训练),经常会遇到计算时间过长,无法知道当前的计算进度,无法判断程序是否进入死循环等问题.采用进度条可以在一定程度上了解当前进度,判断后续所需的计算时间,缓解等待过程中的焦虑 ...

最新文章

  1. oracle列字符可以增加长度,ORACLE字符列长度语义
  2. python3编译exe_编译 – 如何将我的Python 3应用程序编译到.exe?
  3. unreachable code 错误解决方法
  4. 2020noi普及组优秀的拆分_吉首市乾元小学荣获北斗领航梦想全国青少年科技实践活动优秀组织奖...
  5. 卧槽,别人家的黑客增长!
  6. HDU 2897 (博弈 找规律) 邂逅明下
  7. ADN中国团队參加微软的Kinect全国大赛获得三等奖
  8. 定义斜体文本的html标签,HTML 文本格式化
  9. android手机设置固定dns,手机怎么设置dns 手机设置dns方法【详解】
  10. google 阅读器
  11. linux卸载杀毒软件clama,centos 6 安装clamav杀毒软件查毒
  12. 巴比特 | 元宇宙每日必读:微博动漫将招募全球各类虚拟偶像并为其提供扶持...
  13. VS Code利用GIT对源码进行管理
  14. 同期群分析是什么?教你用 SQL 来搞定
  15. 深入学习POST + JS加解密
  16. 腾讯技术开放日 | 腾讯会议如何进行视频质量评估与优化?
  17. 诺基亚老年机信息中心设置路径
  18. 点击获取验证码并登录的实现和验证原理
  19. 创建支持多种屏幕尺寸的Android应用
  20. 影响消费者行为的个人因素

热门文章

  1. 【转】令人忧虑,不阅读的中国人
  2. 小程序如何引入ttf字体文件
  3. 让你的大疆在测绘领域物尽其用
  4. 您所在的服务器更新维护尚未完成,【已开服】2月21日全部服务器更新维护公告...
  5. (理解)单链表算法循环条件while(p)和while(p->next)有什么区别
  6. 织梦装修网站设计师与案例相关联的数据调用教程
  7. 实习公司没有转正hc,怎么办?
  8. ESP8266如何使用u8g2(I2C)驱动SH1106的OLED
  9. nginx日志格式分析及修改
  10. 苏宁iphone俱乐部会员服务,葫芦里装的什么药?