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

转载​​​​​​ 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;
	//设置支持https
	if (!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:页面展示

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

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

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值