我们需要使用的WinInet API函数,调用顺序基本上是从上到下,在使用这些函数时,必须严格区分它们使用的句柄。这些句柄的类型是一样的,都是HINTERNET,但是作用不同,这一点非常让人迷惑。按照这些句柄的产生顺序和调用关系,可以分为三个级别,下一级的句柄由上一级的句柄得到。
InternetOpen是最先调用的函数,它返回的HINTERNET句柄级别最高,我习惯定义为hSession,即会话句柄。
InternetConnect使用hSession句柄,返回的是http连接句柄,我把它定义为hConnect。
HttpOpenRequest使用hConnect句柄,返回的句柄是http请求句柄,定义为hRequest。
HttpSendRequest、HttpQueryInfo、InternetSetFilePointer和InternetReadFile都使用HttpOpenRequest返回的句柄,即hRequest。
当这几个句柄不再使用是,应该用函数InternetCloseHandle把它关闭,以释放其占用的资源。
由于,我是封装好的类,
下面,我们来看看.h文件,这个文件里面声明,变量,函数,对象。
#pragma once
#include <windows.h>
#include <wininet.h>
#define HTTPGET_BUFFER_MAX 1024
#define DOWNHELPER_AGENTNAME "MyAppByMulinB"
#define LEN_OF_BUFFER_FOR_QUERYINFO 128
#define DOWNLOAD_BUF_SIZE (10*1024) //10KB
#define MAX_DOWNLOAD_REQUEST_TIME 10
#define MAX_DOWNLOAD_BYTESIZE (1000*1024*1024) //1000MB
#define MAXSIZE 1024
class THttpGetThread
{
//定义变量
HINTERNET hInet; //打开internet连接handle
HINTERNET hConnect; //HTTP连接
HINTERNET hRequestHead; //HTTP Request
HINTERNET hRequestGet; //HTTP Request
HANDLE hFileWrite = NULL; //写文件的句柄
DWORD dwDownBytes; //每次下载的大小
DWORD dwDownFileTotalBytes; //下载的文件总大小
DWORD dwWriteBytes; //写入文件的大小
char bufQueryInfo[LEN_OF_BUFFER_FOR_QUERYINFO] ; //用来查询信息的buffer
DWORD dwBufQueryInfoSize ;
DWORD dwStatusCode;
DWORD dwContentLen;//获取文件大小
DWORD dwSizeDW ;//文件大小变量大小
WCHAR lpszFileName[INTERNET_MAX_HOST_NAME_LENGTH];//本地储存路径
//分割URL
CHAR pszHostName[INTERNET_MAX_HOST_NAME_LENGTH] ;
CHAR pszUserName[INTERNET_MAX_USER_NAME_LENGTH] ;
CHAR pszPassword[INTERNET_MAX_PASSWORD_LENGTH] ;
CHAR pszURLPath[INTERNET_MAX_URL_LENGTH] ;
CHAR szURL[INTERNET_MAX_URL_LENGTH] ;
URL_COMPONENTSA urlComponents ;//创建结构体
BOOL bRet;//返回值
std::string strUrl;//url地址
public:
THttpGetThread(char *, char *);
~THttpGetThread();
BOOL Initialization();//初始化
BOOL analysisURL();//解析URL
void ReleaseResources();//释放资源
BOOL CreateFile();//创建打开文件
BOOL FileExists(LPCWSTR );//查看文件是否存在
void Download();//下载
void execute();//执行
BOOL _TryHttpSendRequest(LPVOID , int );//尝试发送请求
};那么,接着,我们看看.cpp文件
#include "stdafx.h"
#include "THttpGetThread.h"
#include <stdio.h>
#include<string.h>
#include <windows.h>
#include <wininet.h>
#define DOWNHELPER_AGENTNAME "MyAppByMulinB"
#pragma comment(lib, "wininet.lib")
//构造
THttpGetThread::THttpGetThread(char *url, char *path){
strUrl.assign(url);//赋值给结构体的URl地址
MultiByteToWideChar(CP_ACP, 0, path, strlen(path) + 1, lpszFileName,
sizeof(lpszFileName) / sizeof(lpszFileName[0]));//赋值下载路径
}THttpGetThread::~THttpGetThread()
{
}
//解析URL//填充结构体
BOOL THttpGetThread::analysisURL(){ urlComponents.dwStructSize = sizeof(URL_COMPONENTSA);//这个结构体的大小,以字节为单位。
urlComponents.lpszScheme = NULL;//指向包含该计划名称的字符串的指针。
urlComponents.dwSchemeLength = NULL; //指向包含该计划名称的字符串的指针的长度
urlComponents.nScheme = INTERNET_SCHEME_FTP;//internet_scheme值表明互联网协议方案。
urlComponents.lpszHostName = pszHostName;//指向包含主机名的字符串的指针。
urlComponents.dwHostNameLength = INTERNET_MAX_HOST_NAME_LENGTH;//主机名长度
urlComponents.nPort = NULL;//转换后的端口号
urlComponents.lpszUserName = pszUserName;//用户名
urlComponents.dwUserNameLength = INTERNET_MAX_USER_NAME_LENGTH;//用户名长度
urlComponents.lpszPassword = pszPassword;//指向包含密码的字符串的指针。
urlComponents.dwPasswordLength = INTERNET_MAX_PASSWORD_LENGTH;//密码的大小,在TCHARs。
urlComponents.lpszUrlPath = pszURLPath;//指向包含网址路径的字符串的指针。
urlComponents.dwUrlPathLength = INTERNET_MAX_URL_LENGTH;//URL路径的大小,在TCHARs。
urlComponents.lpszExtraInfo = NULL;//指向一个包含额外信息的字符串的指针(例如?什么东西#)。
urlComponents.dwExtraInfoLength = NULL;//指向一个包含额外信息的字符串的指针的长度(例如?什么东西#)。 bRet = InternetCrackUrlA(strUrl.c_str(), 0, NULL, &urlComponents);//破解一个网址到它的组成部分。
bRet = (bRet && urlComponents.nScheme == INTERNET_SERVICE_HTTP);//查看协议
if (!bRet)
{
return false;
}
return true;
}
//初始化
BOOL THttpGetThread::Initialization(){
//初始化
hInet = InternetOpenA(DOWNHELPER_AGENTNAME, INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, NULL);
if (!hInet)
{
return FALSE;
}
//打开HTTP连接
hConnect = InternetConnectA(hInet, pszHostName, urlComponents.nPort, pszUserName, pszPassword, INTERNET_SERVICE_HTTP, 0, NULL);
if (!hConnect)
{
return FALSE;
} //创建HTTP request句柄
if (urlComponents.dwUrlPathLength != 0)
strcpy(szURL, urlComponents.lpszUrlPath);
else
strcpy(szURL, "/");
//请求HEAD,通过HEAD获得文件大小及类型进行校验
hRequestHead = HttpOpenRequestA(hConnect, "HEAD", szURL, "HTTP/1.1", "", NULL, INTERNET_FLAG_RELOAD, 0);
bRet=_TryHttpSendRequest(hRequestHead, 1);//发送5次链接请求
if (bRet)
{
return FALSE;
}
//查询content-length大小
dwContentLen = 0;
dwSizeDW = sizeof(DWORD);
bRet = HttpQueryInfo(hRequestHead, HTTP_QUERY_FLAG_NUMBER | HTTP_QUERY_CONTENT_LENGTH, &dwContentLen, &dwSizeDW, NULL);//查询文件大小
if (!bRet)
{
return FALSE;
}
//校验完成后再请求GET,下载文件
hRequestGet = HttpOpenRequestA(hConnect, "GET", szURL, "HTTP/1.1", "", NULL, INTERNET_FLAG_RELOAD, 0);//打开获取文件句柄
bRet = _TryHttpSendRequest(hRequestGet, 1);//发送5次链接请求
if (bRet)
{
return FALSE;
} return TRUE;
}
//下载
void THttpGetThread::Download(){
char *pBuf = new char[DOWNLOAD_BUF_SIZE*sizeof(char)]; //缓冲区
dwDownFileTotalBytes = 0;
while (1)
{
dwDownBytes = 0;
memset(pBuf, 0, DOWNLOAD_BUF_SIZE*sizeof(char));
bRet = InternetReadFile(hRequestGet, pBuf, DOWNLOAD_BUF_SIZE, &dwDownBytes);//从一个打开的句柄中读取数据true或者false
//参数,句柄,缓冲区,每次读取最大长度,实际读取的长度。
if (bRet)
{
if (dwDownBytes > 0)
{
dwDownFileTotalBytes += dwDownBytes;
bRet = WriteFile(hFileWrite, pBuf, dwDownBytes, &dwWriteBytes, NULL); //写入文件
if (bRet)
{
}
}
else if (0 == dwDownBytes)
{
bRet = TRUE;
break; //下载成功完成
}
}
}
}
//释放资源
void THttpGetThread::ReleaseResources()
{
CloseHandle(hFileWrite);
InternetCloseHandle(hRequestGet);
InternetCloseHandle(hRequestHead);
InternetCloseHandle(hConnect);
InternetCloseHandle(hInet);}
//创建文件
BOOL THttpGetThread::CreateFile()
{
char path[MAX_PATH] = { 0 };//分配目标缓存
bRet = FileExists(lpszFileName);
DWORD dBufSize = WideCharToMultiByte(CP_OEMCP, 0, lpszFileName, -1, NULL, 0, NULL, FALSE);//获取转换所需的目标缓存大小
int nRet = WideCharToMultiByte(CP_OEMCP, 0, lpszFileName, -1, path, dBufSize, NULL, FALSE);//转换
if (bRet)//如果存在则删除
{
remove(path);//删除文件
hFileWrite = CreateFileA(path, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);//创建文件
}
else{
hFileWrite = CreateFileA(path, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);//创建文件
}
if (hFileWrite == NULL)
{
return false;
}
return true;
}
//检测文件是否存在
BOOL THttpGetThread::FileExists(LPCWSTR lpszFileName)//检测文件是否存在
{
//试图取得文件属性
DWORD dwAttributes = ::GetFileAttributesW(lpszFileName);
if (INVALID_FILE_ATTRIBUTES == dwAttributes)
{
return FALSE;
}
return TRUE;
}
//执行
void THttpGetThread::execute(){
if (analysisURL())
{
if (Initialization())
{
if (CreateFile())
{
Download();
ReleaseResources();
}
}
}
}BOOL THttpGetThread::_TryHttpSendRequest(LPVOID hRequest, int nMaxTryTimes)
{
BOOL bRet = FALSE;
DWORD dwStatusCode = 0;
DWORD dwSizeDW = sizeof(DWORD);
while (hRequest && (nMaxTryTimes-- > 0)) //多次尝试发送请求
{
//发送请求
bRet = HttpSendRequestA(hRequest, NULL, 0, NULL, 0);
if (!bRet)
{
continue;
}
else
{
//判断HTTP返回的状态码
dwStatusCode = 0;
dwSizeDW = sizeof(DWORD);
bRet = HttpQueryInfo(hRequest, HTTP_QUERY_STATUS_CODE, &dwStatusCode, &dwSizeDW, NULL);//用于查询一个http请求
if (bRet)
{
//检查状态码
if (HTTP_STATUS_OK == dwStatusCode) //200-OK 401-未授权 404-找不到该资源 500-服务器错误 503– 服务不可用 100(继续) 101(切换协议)
{ break;
}
else
{
bRet = FALSE;
continue;
}
}
}
} return bRet;
}最后,我们来看看主函数调用
// 断点续传,多线程.cpp : 定义控制台应用程序的入口点。
//#include "stdafx.h"
#include "THttpGetThread.h"int _tmain(int argc, _TCHAR* argv[])
{
THttpGetThread a("调用构造的时候把,下载地址和存储路径传进去
a.execute();
return 0;
}