WinInet辅助类封装, GET, POST, 多线程下载文件

45 篇文章 0 订阅
1 篇文章 0 订阅

CWinInetHelper.h

#pragma once

#include <Windows.h>
#include <WinInet.h>
#include <string>
#include <vector>
#include <functional>
#include <tchar.h>
#include <map>
#include <set>

#ifdef _UNICODE
using _tstring = std::wstring;
#else
using _tstring = std::string;
#endif

typedef struct _WININET_URL_INFO WININET_URL_INFO, *PWININET_URL_INFO;
typedef struct _WININET_PACKAGE_INFO WININET_PACKAGE_INFO, *PWININET_PACKAGE_INFO;

#define WININET_HTTPS_DOWNLOAD_AGENT _T(R"(Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36 Edg/122.0.0.0)")

#define WININET_MAX_CONNS_PER_SERVER                    (64)            // 服务器最大连接数
#define WININET_DOWNLOAD_BLOCK_SIZE                     (1024 * 32)     // 下载缓冲大小
#define WININET_DOWNLOAD_MINIMUM_SIZE                   (1024 * 4)      // 单线程最小下载大小
#define WININET_PROGRESS_UPDATE_INTERVAL_TIME           (1000)          // 下载进度更新时间
#define WININET_SPEED_UPDATE_INTERVAL_TIME              (1000)          // 下载速度更新时间
#define WININET_FILE_BACKUP_MAX_CONUT                   (0)             // 最多备份数量

class CWinInetResult
{
public:
    CWinInetResult() :code(0) {}
    std::string result;         //响应结果
    DWORD code;                 //响应状态码
};

// https下载类
class CWinInetHelper
{
public:
    CWinInetHelper();
    ~CWinInetHelper();

    //
    // @brief: 添加请求头信息
    // @param: strCaption       头名
    // @param: strData          头数据
    // @ret: void
    void AddRequestHeader(
        const _tstring strCaption, 
        const _tstring strData
    );

    //
    // @brief: 移除请求头信息
    // @param: strCaption       头名
    // @param: strData          头数据
    // @ret: void
    void RemoveRequestHeader(
        const _tstring strCaption, 
        const _tstring strData
    );

    //
    // @brief: 获取下载内容大小
    // @param: strUrl           URL链接
    // @param: pUllLength       内容长度缓冲指针
    // @ret: bool               操作成功与否
    bool GetContentLength(
        const _tstring& strUrl, 
        PULONGLONG pUllLength
    );

    //
    // @brief: 下载保存到文件
    // @param: strUrl           URL链接
    // @param: strFile          保存文件路径
    // @param: cbProgress       下载进度回调函数
    // @param: dwThreadCount    下载线程数
    // @ret: bool               操作成功与否
    bool DownloadToFile(
        const _tstring& strUrl, 
        const _tstring& strFile, 
        std::function<void(double lfProgress, ULONGLONG ullCur, ULONGLONG ullTotal, double lfSpeed)> cbProgress = nullptr,
        DWORD dwThreadCount = 8
    );

    //
    // @brief: 下载保存到缓存
    // @param: strUrl           URL链接
    // @param: lpBuf            保存缓冲
    // @param: dwBufSize        缓冲大小
    // @param: cbProgress       下载进度回调函数
    // @param: dwThreadCount    下载线程数
    // @ret: bool               操作成功与否
    bool DownloadToBuffer(
        const _tstring& strUrl, 
        LPVOID lpBuf, 
        DWORD dwBufSize, 
        std::function<void(double lfProgress, ULONGLONG ullCur, ULONGLONG ullTotal, double lfSpeed)> cbProgress = nullptr,
        DWORD dwThreadCount = 8
    );

    //
    // @brief: 发送Get请求
    // @param: strUrl           URL链接
    // @param: strHeader        请求头
    // @param: cbProgress       进度回调
    // @ret: CHttpsResponse     响应结果
    CWinInetResult Get(const _tstring& strUrl,
        const _tstring& strHeader = _T("Accept: */*\r\nContent-Type:application/json;charset=UTF-8"),
        std::function<void(double lfProgress, ULONGLONG ullCur, ULONGLONG ullTotal)> cbProgress = nullptr
    );

    //
    // @brief: 发送Post请求
    // @param: strUrl           URL链接
    // @param: strParam         请求参数
    // @param: strHeader        请求头
    // @param: cbProgress       进度回调
    // @ret: CHttpsResponse     响应结果
    CWinInetResult Post(const _tstring& strUrl,
        std::string& strParam,
        const _tstring& strHeader = _T("Accept: */*\r\nContent-Type:application/json;charset=UTF-8"),
        std::function<void(double lfProgress, ULONGLONG ullCur, ULONGLONG ullTotal)> cbProgress = nullptr
    );

private:

    //
    // @brief: 执行请求
    // @param: strUrl           URL链接
    // @param: strMethod        请求方法
    // @param: strParam         请求参数
    // @param: strHeader        请求头
    // @param: cbProgress       进度回调
    // @ret: CWinHttpResponse   响应结果
    CWinInetResult _DoSendRequest(
        const _tstring& strUrl,
        const _tstring& strMethod,
        const std::string& strParam,
        _tstring strHeader,
        std::function<void(double lfProgress, ULONGLONG ullCur, ULONGLONG ullTotal)> cbProgress = nullptr
    );

    // 获取请求头字符串
    _tstring _GetRequestHeaderString();

    // 清空文件内容
    void _TruncateFile(const _tstring& strPath);

    // 下载前备份文件
    bool _BackupFile(const _tstring& strPath);

    // 进度处理
    void _ProcessProcess(std::vector<WININET_PACKAGE_INFO>& taskPackages);

    // 分段下载到文件
    bool _PartialDownload(
        const _tstring& strUrl, 
        const _tstring& strUrlFileName, 
        ULONGLONG ullContentLength, 
        DWORD dwThreadCount
    );
    
    // 分段下载到缓冲
    bool _PartialDownload(
        const _tstring& strUrl, 
        LPVOID lpBuf,
        DWORD dwBufSize, 
        ULONGLONG ullContentLength, 
        DWORD dwThreadCount
    );

    // 分解链接
    bool _CrackUrl(
        const _tstring& strUrl, 
        PWININET_URL_INFO lpUci
    );

    // 查询是否支持接收范围
    bool _IsSupportAcceptRanges(HINTERNET hRequest);

    // 查询资源大小
    bool _QueryContentLength(
        HINTERNET hRequest, 
        PULONGLONG lpUllContentLength
    );

    // 获取状态码
    long _GetStatusCode(HINTERNET hRequest);

    // 发送请求
    bool _SendRequest(
        HINTERNET hRequest, 
        LPCTSTR lpszHeaders,
        DWORD dwHeadersLength, 
        LPVOID lpData, 
        DWORD dwSize
    );

    // 读取网络流
    bool _InternetReadData(
        HINTERNET hRequest, 
        PWININET_PACKAGE_INFO lpDownloadPackage,
        bool fFile = false,
        std::function<void(double lfProgress, ULONGLONG ullCur, ULONGLONG ullTotal)> cbProgress = nullptr
    );

    // 设置请求数据范围
    bool _SetRequestDataRange(
        HINTERNET hRequest, 
        LONGLONG nBegin, 
        LONGLONG nEng, 
        bool isHasEnd = TRUE
    );

    // 下载分包
    bool _DownloadPackage(PWININET_PACKAGE_INFO pPackageInfo);
    
    void _SleepMillisecond(int millisecond) const;

    // 打印警告
    void _PrintWarn(LPCTSTR lpszError) const;

    // 打印错误
    void _PrintError(LPCTSTR lpszError) const;

    // 异步回调函数(暂时未使用)
    static void _InternetStatusCallback(
        HINTERNET hInternet, 
        DWORD_PTR dwContext, 
        DWORD dwInternetStatus, 
        LPVOID lpvStatusInformation, 
        DWORD dwStatusInformationLength
    );

private:

    std::function<void(double lfProgress, ULONGLONG ullCur, ULONGLONG ullTotal, double lfSpeed)> m_cbProgress;
    std::map<_tstring, std::set<_tstring>> m_RequestHeader;
    bool m_bAbort;
    bool m_bSupportAcceptRanges;
    bool m_bHasSize;
};

CWinInetHelper.cpp

#include "CWinInetHelper.h"
#include <strsafe.h>
#include <time.h>
#include <thread>
#include <algorithm>

#pragma comment(lib, "WinInet.lib")

const DWORD HTTP_REQUEST_FLAGS  =   INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTP |     // 禁用此特殊类型的重定向检测。 使用此标志时,WinINet 函数以透明方式允许从 HTTPS 重定向到 HTTP URL
                                    INTERNET_FLAG_KEEP_CONNECTION |             // 对连接使用保持活动语义(如果可用)
                                    INTERNET_FLAG_NO_AUTH |                     // 不会自动尝试身份验证
                                    INTERNET_FLAG_NO_COOKIES |                  // 不会自动向请求添加 Cookie 标头,也不会自动将返回的 Cookie 添加到 Cookie 数据库
                                    INTERNET_FLAG_NO_UI |                       // 禁用 Cookie 对话框
                                    INTERNET_FLAG_RELOAD;                       // 强制从源服务器下载请求的文件、对象或目录列表,而不是从缓存下载

const DWORD HTTPS_REQUEST_FLAGS =   HTTP_REQUEST_FLAGS |

                                    // HTTPS 设置
                                    INTERNET_FLAG_SECURE |                      // 使用安全事务语义。 这转换为使用安全套接字层/专用通信技术 (SSL/PCT) ,并且仅在 HTTP 请求中有意义
                                    INTERNET_FLAG_IGNORE_CERT_CN_INVALID |      // 忽略X509 证书中的错误通用名称
                                    SECURITY_FLAG_IGNORE_UNKNOWN_CA |           // 忽略未知证书颁发机构问题
                                    INTERNET_FLAG_IGNORE_CERT_DATE_INVALID;     // 忽略过期的 X509 证书


// 下载分包信息
typedef struct _WININET_PACKAGE_INFO
{
    enum DownloadStatus {
        DS_Downloading = 0,
        DS_DownloadFinish = 1,
        DS_DownloadFailed = 2,
        DS_DownloadCancel = 3,
    };

    _tstring strUrl;
    _tstring strFileName;
    LPVOID lpBuffer;
    ULONGLONG ullDataCurPos;
    ULONGLONG ullDataStartPos;
    ULONGLONG ullContentLength;
    ULONGLONG ullDownloaded;
    DWORD dwStatus;
    DWORD dwPartID;

    _WININET_PACKAGE_INFO()
    {
        this->lpBuffer = NULL;
        this->ullDataCurPos = 0;
        this->ullDataStartPos = 0;
        this->ullContentLength = 0;
        this->ullDownloaded = 0;
        this->dwStatus = DownloadStatus::DS_Downloading;
        this->dwPartID = 0;
    }

    _WININET_PACKAGE_INFO(const _tstring& strUrl, const _tstring& strFileName, ULONGLONG ullBeginPos, ULONGLONG ullContentLength, DWORD dwPartID)
    {
        this->strUrl = strUrl;
        this->strFileName = strFileName;
        this->lpBuffer = NULL;
        this->ullDataCurPos = ullBeginPos;
        this->ullDataStartPos = ullBeginPos;
        this->ullContentLength = ullContentLength;
        this->ullDownloaded = 0;
        this->dwPartID = dwPartID;
        this->dwStatus = DownloadStatus::DS_Downloading;
    }

    _WININET_PACKAGE_INFO(const _tstring& strUrl, LPVOID lpBuffer, ULONGLONG ullBeginPos, ULONGLONG ullContentLength, DWORD dwPartID)
    {
        this->strUrl = strUrl;
        this->lpBuffer = lpBuffer;
        this->ullDataCurPos = ullBeginPos;
        this->ullDataStartPos = ullBeginPos;
        this->ullContentLength = ullContentLength;
        this->ullDownloaded = 0;
        this->dwPartID = dwPartID;
        this->dwStatus = DownloadStatus::DS_Downloading;
    }

}WININET_PACKAGE_INFO, *PWININET_PACKAGE_INFO;

typedef struct _WININET_URL_INFO
{
    TCHAR szScheme[INTERNET_MAX_SCHEME_LENGTH];
    TCHAR szHostName[INTERNET_MAX_HOST_NAME_LENGTH];
    TCHAR szUserName[INTERNET_MAX_USER_NAME_LENGTH];
    TCHAR szPassword[INTERNET_MAX_PASSWORD_LENGTH];
    TCHAR szUrlPath[INTERNET_MAX_URL_LENGTH];
    TCHAR szExtraInfo[MAX_PATH];
    URL_COMPONENTS uc = { 0 };

    _WININET_URL_INFO()
    {
        memset(this, 0, sizeof(*this));
        this->uc.dwStructSize = sizeof(this->uc);
        this->uc.lpszUrlPath = this->szUrlPath;
        this->uc.dwUrlPathLength = _countof(this->szUrlPath);
        this->uc.lpszScheme = this->szScheme;
        this->uc.dwSchemeLength = _countof(this->szScheme);
        this->uc.lpszHostName = this->szHostName;
        this->uc.dwHostNameLength = _countof(this->szHostName);
        this->uc.lpszUserName = this->szUserName;
        this->uc.dwUserNameLength = _countof(this->szUserName);
        this->uc.lpszPassword = this->szPassword;
        this->uc.dwPasswordLength = _countof(this->szPassword);
        this->uc.lpszExtraInfo = this->szExtraInfo;
        this->uc.dwExtraInfoLength = _countof(this->szExtraInfo);
    }
}WININET_URL_INFO, *PWININET_URL_INFO;

CWinInetHelper::CWinInetHelper()
    :
    m_bAbort(false),
    m_bSupportAcceptRanges(false),
    m_bHasSize(false)
{
}

CWinInetHelper::~CWinInetHelper()
{

}

void CWinInetHelper::AddRequestHeader(const _tstring strCaption, const _tstring strData)
{
    auto itFind = m_RequestHeader.find(strCaption);
    if (m_RequestHeader.end() == itFind)
    {
        std::set<_tstring> setData;
        setData.insert(strData);
        m_RequestHeader.insert(std::make_pair(strCaption, setData));

    }
    else
    {
        itFind->second.insert(strData);
    }
}

void CWinInetHelper::RemoveRequestHeader(const _tstring strCaption, const _tstring strData)
{
    auto itFind = m_RequestHeader.find(strCaption);
    if (m_RequestHeader.end() == itFind)
    {
        return;
    }

    if (strData.empty())
    {
        m_RequestHeader.erase(itFind);
    }
    else
    {
        itFind->second.erase(strData);

        if (itFind->second.empty())
        {
            m_RequestHeader.erase(itFind);
        }
    }
}

_tstring CWinInetHelper::_GetRequestHeaderString()
{
    _tstring strResult;

    size_t nItemIndex = 0;
    size_t nItemCount = m_RequestHeader.size();
    for (const auto& item : m_RequestHeader)
    {
        strResult += item.first;
        strResult += _T(": ");

        size_t nDataIndex = 0;
        size_t nDataCount = item.second.size();
        for (const auto& data : item.second)
        {
            strResult += data;
            nDataIndex++;
            if (nDataIndex < nDataCount)
            {
                strResult += _T(";");
            }
        }

        nItemIndex++;
        if (nItemIndex < nItemCount)
        {
            strResult += _T("\r\n");
        }
    }

    return strResult;
}

bool CWinInetHelper::GetContentLength(
    const _tstring& strUrl, 
    PULONGLONG lpUllContentLength
)
{
    WININET_URL_INFO urlParts;
    HINTERNET hSession = NULL;
    HINTERNET hConnect = NULL;
    HINTERNET hRequest = NULL;
    bool bSuccess = false;

    if (strUrl.empty() || NULL == lpUllContentLength)
    {
        return false;
    }

    // 分解URL
    if (!_CrackUrl(strUrl, &urlParts))
    {
        return false;
    }

    // 协议检查
    if (!(INTERNET_SCHEME_HTTPS == urlParts.uc.nScheme || INTERNET_SCHEME_HTTP == urlParts.uc.nScheme))
    {
        _PrintError(_T("Scheme"));
        return false;
    }

    _tstring strHeader = _GetRequestHeaderString();

    do
    {
        // 初始化应用程序对 WinINet 函数的使用
        hSession = ::InternetOpen(WININET_HTTPS_DOWNLOAD_AGENT, INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0);
        if (NULL == hSession)
        {
            _PrintError(_T("InternetOpen"));
            break;
        }

        // 打开给定站点的 HTTP 会话
        hConnect = ::InternetConnect(hSession, urlParts.szHostName, urlParts.uc.nPort, urlParts.szUserName, urlParts.szPassword, INTERNET_SERVICE_HTTP, 0, NULL);
        if (NULL == hConnect)
        {
            _PrintError(_T("InternetConnect"));
            break;
        }

        // 修改每个服务器的连接数
        DWORD dwMaxConns = WININET_MAX_CONNS_PER_SERVER;
        if (!::InternetSetOption(hConnect, INTERNET_OPTION_MAX_CONNS_PER_SERVER, (LPVOID)&dwMaxConns, sizeof(dwMaxConns)))
        {
            _PrintWarn(_T("InternetSetOption"));
        }

        if (!::InternetSetOption(NULL, INTERNET_OPTION_MAX_CONNS_PER_1_0_SERVER, (LPVOID)&dwMaxConns, sizeof(dwMaxConns)))
        {
            _PrintWarn(_T("InternetSetOption"));
        }

        // 创建 HTTPS / HTTP 请求句柄
        DWORD dwFlags = (INTERNET_SCHEME_HTTPS == urlParts.uc.nScheme) ? HTTPS_REQUEST_FLAGS : HTTP_REQUEST_FLAGS;
        hRequest = ::HttpOpenRequest(hConnect, _T("GET"), urlParts.szUrlPath, NULL, NULL, NULL, dwFlags, 0);
        if (NULL == hRequest)
        {
            _PrintError(_T("HttpOpenRequest"));
            break;
        }

        // 发送请求
        if (!_SendRequest(hRequest, strHeader.data(), (DWORD)strHeader.size(), nullptr, 0))
        {
            _PrintError(_T("SendRequest"));
            break;
        }

        DWORD statusCodes = _GetStatusCode(hRequest);
        if (statusCodes < 200 || statusCodes >= 300)
        {
            char szResponseBuffer[MAX_PATH] = { 0 };
            DWORD dwBufferLength = sizeof(szResponseBuffer);
            ::HttpQueryInfoA(hRequest, HTTP_QUERY_STATUS_TEXT, szResponseBuffer, &dwBufferLength, NULL);

            char szErrorBuf[MAX_PATH] = { 0 };
            StringCchPrintfA(szErrorBuf, _countof(szErrorBuf),
                R"({"status":%d,"data":null,"message":"%d %s"})",
                statusCodes, statusCodes, szResponseBuffer
            );

            printf("%s\r\n", szErrorBuf);

            break;
        }

        // 记录是否支持接收范围请求
        m_bSupportAcceptRanges = _IsSupportAcceptRanges(hRequest);

        // 查询文件大小
        if (!_QueryContentLength(hRequest, lpUllContentLength))
        {
            _PrintError(_T("QueryContentLength"));
            break;
        }

        bSuccess = true;

    } while (false);

    // 释放资源
    if (hRequest)
    {
        ::InternetCloseHandle(hRequest);
    }

    if (hConnect)
    {
        ::InternetCloseHandle(hConnect);
    }

    if (hSession)
    {
        ::InternetCloseHandle(hSession);
    }

    return bSuccess;
}

bool CWinInetHelper::DownloadToFile(
    const _tstring& strUrl, 
    const _tstring& strFile, 
    std::function<void(double lfProgress, ULONGLONG ullCur, ULONGLONG ullTotal, double lfSpeed)> cbProgress,
    DWORD dwThreadCount
)
{
    ULONGLONG ullContentLength = 0;
    _tstring strTmpFile = strFile + _T(".tmp");

    if (strUrl.empty() || strFile.empty())
    {
        return false;
    }

    m_cbProgress = cbProgress;

    //限制线程数量
    dwThreadCount = dwThreadCount < 1 ? 1 : dwThreadCount;
    dwThreadCount = dwThreadCount > 64 ? 64 : dwThreadCount;

    // 获取资源大小
    if (!GetContentLength(strUrl, &ullContentLength))
    {
        _PrintError(_T("GetContentLength"));
        return false;
    }

    // 不支持范围请求则仅单线程下载
    if (!m_bSupportAcceptRanges || !m_bHasSize)
    {
        dwThreadCount = 1;
    }

    // 备份文件
    if (!_BackupFile(strFile))
    {
        _PrintError(_T("BackupFile"));
        return false;
    }

    // 删除文件, 防止下载后改名冲突
    if (!::DeleteFile(strFile.c_str()))
    {
        if (ERROR_FILE_NOT_FOUND != ::GetLastError())
        {
            return false;
        }
    }

    // 清空一下临时文件(如果存在的话)
    (void)_TruncateFile(strTmpFile);

    // 多线程分段下载
    if (!_PartialDownload(strUrl, strTmpFile, ullContentLength, dwThreadCount))
    {
        _PrintError(_T("PartialDownload"));
        return false;
    }

    // 下载完成则修改文件名
    ::MoveFile(strTmpFile.c_str(), strFile.c_str());

    return true;
}

bool CWinInetHelper::DownloadToBuffer(
    const _tstring& strUrl, 
    LPVOID lpBuf, 
    DWORD dwBufSize, 
    std::function<void(double lfProgress, ULONGLONG ullCur, ULONGLONG ullTotal, double lfSpeed)> cbProgress,
    DWORD dwThreadCount
)
{
    ULONGLONG ullContentLength = 0;

    if (strUrl.empty() || NULL == lpBuf || 0 == dwBufSize)
    {
        return false;
    }

    m_cbProgress = cbProgress;

    //限制线程数量
    dwThreadCount = dwThreadCount < 2 ? 2 : dwThreadCount;
    dwThreadCount = dwThreadCount > 64 ? 64 : dwThreadCount;

    // 获取资源大小
    if (!GetContentLength(strUrl, &ullContentLength))
    {
        _PrintError(_T("GetContentLength"));
        return false;
    }

    // 不支持范围请求则仅单线程下载
    if (!m_bSupportAcceptRanges || !m_bHasSize)
    {
        dwThreadCount = 1;
    }

    // 多线程分段下载
    if (!_PartialDownload(strUrl, lpBuf, dwBufSize, ullContentLength, dwThreadCount))
    {
        _PrintError(_T("PartialDownload"));
        return false;
    }

    return true;
}

CWinInetResult CWinInetHelper::Get(
    const _tstring& strUrl, 
    const _tstring& strHeader/* = _T("Content-Type:application/json;charset=UTF-8")*/,
    std::function<void(double lfProgress, ULONGLONG ullCur, ULONGLONG ullTotal)> cbProgress/* = nullptr*/
)
{
    return _DoSendRequest(strUrl, _T("GET"), "", strHeader, cbProgress);
}

CWinInetResult CWinInetHelper::Post(
    const _tstring& strUrl, 
    std::string& strParam, 
    const _tstring& strHeader,
    std::function<void(double lfProgress, ULONGLONG ullCur, ULONGLONG ullTotal)> cbProgress/* = nullptr*/
)
{
    return _DoSendRequest(strUrl, _T("POST"), strParam, strHeader, cbProgress);
}

CWinInetResult CWinInetHelper::_DoSendRequest(
    const _tstring& strUrl,
    const _tstring& strMethod,
    const std::string& strParam,
    _tstring strHeader,
    std::function<void(double lfProgress, ULONGLONG ullCur, ULONGLONG ullTotal)> cbProgress/* = nullptr*/
)
{
    CWinInetResult httpsResponse;

    WININET_URL_INFO urlParts;
    ULONGLONG ullContentLength = 0;
    HINTERNET hSession = NULL;
    HINTERNET hConnect = NULL;
    HINTERNET hRequest = NULL;
    bool bSuccess = false;

    // 分解URL
    if (!_CrackUrl(strUrl, &urlParts))
    {
        return httpsResponse;
    }

    if (strHeader.empty())
    {
        strHeader = _GetRequestHeaderString();
    }

    do
    {
        // 初始化应用程序对 WinINet 函数的使用
        hSession = ::InternetOpen(WININET_HTTPS_DOWNLOAD_AGENT, INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0);
        if (NULL == hSession)
        {
            _PrintError(_T("InternetOpen"));
            break;
        }

        // 打开给定站点的 HTTP 会话
        hConnect = ::InternetConnect(hSession, urlParts.szHostName, urlParts.uc.nPort, urlParts.szUserName, urlParts.szPassword, INTERNET_SERVICE_HTTP, 0, NULL);
        if (NULL == hConnect)
        {
            _PrintError(_T("InternetConnect"));
            break;
        }

        // 创建 HTTPS / HTTP 请求句柄
        DWORD dwFlags = (INTERNET_SCHEME_HTTPS == urlParts.uc.nScheme) ? HTTPS_REQUEST_FLAGS : HTTP_REQUEST_FLAGS;
        hRequest = ::HttpOpenRequest(hConnect, strMethod.c_str(), urlParts.szUrlPath, NULL, NULL, NULL, dwFlags, 0);
        if (NULL == hRequest)
        {
            _PrintError(_T("HttpOpenRequest"));
            break;
        }

        // 发送请求
        if (!_SendRequest(hRequest, strHeader.data(), (DWORD)strHeader.size(), (LPVOID)strParam.data(), (DWORD)strParam.size()))
        {
            _PrintError(_T("SendRequest"));
            break;
        }

        // 记录状态码
        httpsResponse.code = _GetStatusCode(hRequest);

        // 查询文件大小
        if (!_QueryContentLength(hRequest, &ullContentLength))
        {
            _PrintError(_T("QueryContentLength"));
            break;
        }

        WININET_PACKAGE_INFO packageInfo;
        httpsResponse.result.resize(ullContentLength);
        packageInfo.lpBuffer = (LPVOID)httpsResponse.result.data();
        packageInfo.ullContentLength = ullContentLength;

        if (!_InternetReadData(hRequest, &packageInfo, false, cbProgress))
        {
            _PrintError(_T("SaveToBuffer"));
            break;
        }

        bSuccess = true;
    } while (false);

    // 释放资源
    if (hRequest)
    {
        ::InternetCloseHandle(hRequest);
    }

    if (hConnect)
    {
        ::InternetCloseHandle(hConnect);
    }

    if (hSession)
    {
        ::InternetCloseHandle(hSession);
    }

    return httpsResponse;
}

void CWinInetHelper::_TruncateFile(const _tstring& strPath)
{
    HANDLE hFile = INVALID_HANDLE_VALUE;
    hFile = ::CreateFile(strPath.c_str(),
        GENERIC_READ | GENERIC_WRITE, FILE_SHARE_WRITE,
        NULL,
        TRUNCATE_EXISTING,
        FILE_ATTRIBUTE_ARCHIVE,
        NULL);

    if (INVALID_HANDLE_VALUE != hFile)
    {
        ::CloseHandle(hFile);
    }
}

bool CWinInetHelper::_BackupFile(const _tstring& strPath)
{
    TCHAR szBuf[MAX_PATH] = { 0 };
    _tstring strBakFile;
    bool isBackupOk = false;

    if (0 == WININET_FILE_BACKUP_MAX_CONUT)
    {
        return true;
    }

    for (int i = 1; i <= WININET_FILE_BACKUP_MAX_CONUT; i++)
    {
        ::StringCchPrintf(szBuf, _countof(szBuf), _T("%02d"), i);
        strBakFile = strPath + _T(".") + szBuf + _T(".bak");
        if (::MoveFile(strPath.c_str(), strBakFile.c_str()))
        {
            isBackupOk = true;
            break;
        }

        if (ERROR_FILE_NOT_FOUND == ::GetLastError())
        {
            isBackupOk = true;
            break;
        }
    }

    //备份失败, 则删除最早一个备份文件
    if (!isBackupOk)
    {
        _tstring strOld;
        _tstring strNew;

        ::StringCchPrintf(szBuf, _countof(szBuf), _T("%02d"), 1);
        strOld = strPath + _T(".") + szBuf + _T(".bak");

        // 无法删除
        if (!::DeleteFile(strOld.c_str()))
        {
            return false;
        }

        for (int i = 1; i < WININET_FILE_BACKUP_MAX_CONUT; i++)
        {
            ::StringCchPrintf(szBuf, _countof(szBuf), _T("%02d"), i);
            strNew = strPath + _T(".") + szBuf + _T(".bak");;
            ::StringCchPrintf(szBuf, _countof(szBuf), _T("%02d"), i + 1);
            strOld = strPath + _T(".") + szBuf + _T(".bak");;
            if (!::MoveFile(strOld.c_str(), strNew.c_str()))
            {
                break;
            }
        }

        ::StringCchPrintf(szBuf, _countof(szBuf), _T("%02d"), WININET_FILE_BACKUP_MAX_CONUT);
        strBakFile = strPath + _T(".") + szBuf + _T(".bak");
        if (::MoveFile(strPath.c_str(), strBakFile.c_str()))
        {
            isBackupOk = true;
        }
    }

    return isBackupOk;
}

void CWinInetHelper::_ProcessProcess(std::vector<WININET_PACKAGE_INFO>& taskPackages)
{
    ULONGLONG ullLastDownload = 0;
    clock_t refreshInterval = WININET_PROGRESS_UPDATE_INTERVAL_TIME;
    clock_t lastTime = ::clock();
    clock_t lastProgressTime = ::clock();
    double lfSpeed = 0.0f;
    clock_t speedTime = WININET_SPEED_UPDATE_INTERVAL_TIME;
    bool fQuit = false;

    // 等待所有线程结束下载
    while (!fQuit)
    {
        ULONGLONG ullDownloaded = 0;
        ULONGLONG ullTotalLength = 0;
        DWORD dwDownloadingCount = 0;

        // 统计已下载量 与 总下载量
        for (const auto& item : taskPackages)
        {
            ullDownloaded += item.ullDownloaded;
            ullTotalLength += item.ullContentLength;

            if (WININET_PACKAGE_INFO::DownloadStatus::DS_Downloading == item.dwStatus)
            {
                dwDownloadingCount++;
            }
        }

        clock_t curTime = ::clock();
        if (curTime - lastTime >= speedTime)
        {
            lfSpeed = (double)(ullDownloaded - ullLastDownload) / ((double)speedTime / 1000.0f);
            lastTime = curTime;
            ullLastDownload = ullDownloaded;
        }

        // 进度报告
        if (curTime - lastProgressTime > refreshInterval || 0 == dwDownloadingCount)
        {
            // 进度回调
            if (m_cbProgress)
            {
                double lfProgress = (double)ullDownloaded / (double)ullTotalLength;
                m_cbProgress(lfProgress, ullDownloaded, ullTotalLength, lfSpeed);
            }

            lastProgressTime = curTime;

            if (0 == dwDownloadingCount)
            {
                fQuit = true;
            }
        }

        _SleepMillisecond(50);
    }
}

bool CWinInetHelper::_PartialDownload(
    const _tstring& strUrl, 
    const _tstring& strUrlFileName, 
    ULONGLONG ullContentLength, 
    DWORD dwThreadCount
)
{
    //资源过小检查
    if (ullContentLength < WININET_DOWNLOAD_MINIMUM_SIZE)
    {
        dwThreadCount = 1;
    }

    // 每个包大小
    ULONGLONG ullSinglePackageSize = ullContentLength / dwThreadCount;
    std::vector<std::thread> downloadThreads;

    // 保留容量, 防止发生空间分配导致迭代器失效
    std::vector<WININET_PACKAGE_INFO> m_downloadPackages;
    m_downloadPackages.reserve(dwThreadCount);

    // 添加下载任务
    for (DWORD i = 0; i < dwThreadCount; i++)
    {
        ULONGLONG ullBeginPos = ullSinglePackageSize * i;
        ULONGLONG ullPackageSize = (i != dwThreadCount - 1) ? ullSinglePackageSize : ullContentLength - ullBeginPos;
        m_downloadPackages.emplace_back(WININET_PACKAGE_INFO(strUrl, strUrlFileName, ullBeginPos, ullPackageSize, i));
        downloadThreads.emplace_back(std::thread(
            [this, &m_downloadPackages, i]() {
                if (!_DownloadPackage(&m_downloadPackages[i]))
                {
                    m_downloadPackages[i].dwStatus = WININET_PACKAGE_INFO::DownloadStatus::DS_DownloadFailed;
                }
            }));
    }

    // 创建进度报告线程
    std::thread processTask([this, &m_downloadPackages]() {
        _ProcessProcess(m_downloadPackages);
        }
    );

    processTask.join();

    // 等待所有下载线程结束
    for (auto &item : downloadThreads)
    {
        item.join();
    }

    // 获取每个线程的下载状态, 全部下载完成才算完成
    bool isAllFinish = std::all_of(m_downloadPackages.begin(), m_downloadPackages.end(), 
        [](const WININET_PACKAGE_INFO& item) {
            return WININET_PACKAGE_INFO::DownloadStatus::DS_DownloadFinish == item.dwStatus;
        });

    return isAllFinish;
}

bool CWinInetHelper::_PartialDownload(
    const _tstring& strUrl, 
    LPVOID lpBuf, 
    DWORD dwBufSize, 
    ULONGLONG ullContentLength, 
    DWORD dwThreadCount
)
{
    //资源过小检查
    if (ullContentLength < WININET_DOWNLOAD_MINIMUM_SIZE || dwBufSize < WININET_DOWNLOAD_MINIMUM_SIZE)
    {
        dwThreadCount = 1;
    }

    // 每个包大小
    ullContentLength = ullContentLength < dwBufSize ? ullContentLength : dwBufSize;
    ULONGLONG ullSinglePackageSize = ullContentLength / dwThreadCount;
    std::vector<std::thread> downloadThreads;

    // 保留容量, 防止发生空间分配导致迭代器失效
    std::vector<WININET_PACKAGE_INFO> m_downloadPackages;
    m_downloadPackages.reserve(dwThreadCount);

    // 添加下载任务
    for (DWORD i = 0; i < dwThreadCount; i++)
    {
        ULONGLONG ullBeginPos = ullSinglePackageSize * i;
        ULONGLONG ullPackageSize = (i != dwThreadCount - 1) ? ullSinglePackageSize : ullContentLength - ullBeginPos;
        m_downloadPackages.emplace_back(WININET_PACKAGE_INFO(strUrl, (LPBYTE)lpBuf + ullBeginPos, ullBeginPos, ullPackageSize, i));
        downloadThreads.emplace_back(std::thread(
            [this, &m_downloadPackages, i]() {
                if (!_DownloadPackage(&m_downloadPackages[i]))
                {
                    m_downloadPackages[i].dwStatus = WININET_PACKAGE_INFO::DownloadStatus::DS_DownloadFailed;
                }
            }));
    }

    // 创建进度报告线程
    std::thread processTask([this, &m_downloadPackages]() {
        _ProcessProcess(m_downloadPackages);
        }
    );

    processTask.join();

    // 等待所有下载线程结束
    for (auto &item : downloadThreads)
    {
        item.join();
    }

    // 获取每个线程的下载状态, 全部下载完成才算完成
    bool isAllFinish = std::all_of(m_downloadPackages.begin(), m_downloadPackages.end(),
        [](const WININET_PACKAGE_INFO& item) {
            return WININET_PACKAGE_INFO::DownloadStatus::DS_DownloadFinish == item.dwStatus;
        });

    return isAllFinish;
}

void CWinInetHelper::_SleepMillisecond(int millisecond) const
{
    int span = 10;

    if (millisecond < span)
    {
        std::this_thread::sleep_for(std::chrono::milliseconds(span));
        return;
    }

    // 分段休眠, 防止中断休眠不及时
    clock_t tBegin = clock();
    while (!m_bAbort)
    {
        if (clock() - tBegin > millisecond)
        {
            break;
        }

        std::this_thread::sleep_for(std::chrono::milliseconds(span));
    }
}

bool CWinInetHelper::_DownloadPackage(PWININET_PACKAGE_INFO pPackageInfo)
{
    WININET_URL_INFO urlParts;
    ULONGLONG ullContentLength = 0;
    HINTERNET hSession = NULL;
    HINTERNET hConnect = NULL;
    HINTERNET hRequest = NULL;
    bool bSuccess = false;

    // 分解URL
    if (!_CrackUrl(pPackageInfo->strUrl, &urlParts))
    {
        return false;
    }

    _tstring strHeader = _GetRequestHeaderString();

    do
    {
        // 初始化应用程序对 WinINet 函数的使用
        hSession = ::InternetOpen(WININET_HTTPS_DOWNLOAD_AGENT, INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0);
        if (NULL == hSession)
        {
            _PrintError(_T("InternetOpen"));
            break;
        }
        
        // 打开给定站点的 HTTP 会话
        hConnect = ::InternetConnect(hSession, urlParts.szHostName, urlParts.uc.nPort, urlParts.szUserName, urlParts.szPassword, INTERNET_SERVICE_HTTP, 0, NULL);
        if (NULL == hConnect)
        {
            _PrintError(_T("InternetConnect"));
            break;
        }
        
        // 创建 HTTPS / HTTP 请求句柄
        DWORD dwFlags = (INTERNET_SCHEME_HTTPS == urlParts.uc.nScheme) ? HTTPS_REQUEST_FLAGS : HTTP_REQUEST_FLAGS;
        hRequest = ::HttpOpenRequest(hConnect, _T("GET"), urlParts.szUrlPath, NULL, NULL, NULL, dwFlags, 0);
        if (NULL == hRequest)
        {
            _PrintError(_T("HttpOpenRequest"));
            break;
        }

        // 设置请求文件数据范围
        ULONGLONG ullStart = pPackageInfo->ullDataCurPos;
        ULONGLONG ullEnd = pPackageInfo->ullDataCurPos;

        if (pPackageInfo->ullContentLength > 0)
        {
            ullEnd = pPackageInfo->ullDataStartPos + pPackageInfo->ullContentLength - 1;
        }

        if (!_SetRequestDataRange(hRequest, ullStart, ullEnd, true))
        {
            _PrintError(_T("SetContentRange"));
            break;
        }

        // 发送请求
        if (!_SendRequest(hRequest, strHeader.data(), (DWORD)strHeader.size(), nullptr, 0))
        {
            _PrintError(_T("SendRequest"));
            break;
        }

        // 查询文件大小
        if (!_QueryContentLength(hRequest, &ullContentLength))
        {
            _PrintError(_T("QueryContentLength"));
            break;
        }

        if (NULL != pPackageInfo->lpBuffer)
        {
            if (!_InternetReadData(hRequest, pPackageInfo, false))
            {
                _PrintError(_T("SaveToBuffer"));
                break;
            }
        }
        else
        {
            if (!_InternetReadData(hRequest, pPackageInfo, true))
            {
                _PrintError(_T("SaveToFile"));
                break;
            }
        }

        bSuccess = true;
    } while (false);

    // 释放资源
    if (hRequest)
    {
        ::InternetCloseHandle(hRequest);
    }

    if (hConnect)
    {
        ::InternetCloseHandle(hConnect);
    }

    if (hSession)
    {
        ::InternetCloseHandle(hSession);
    }

    return bSuccess;
}

bool CWinInetHelper::_SetRequestDataRange(
    HINTERNET hRequest, 
    LONGLONG nBegin, 
    LONGLONG nEng, 
    bool isHasEnd
)
{
    TCHAR szBuf[MAX_PATH] = { 0 };

    if (isHasEnd)
    {
        (void)::StringCchPrintf(szBuf, _countof(szBuf), _T("Range:bytes=%lld-%lld"), nBegin, nEng);
    }
    else
    {
        (void)::StringCchPrintf(szBuf, _countof(szBuf), _T("Range:bytes=%lld-"), nBegin);
    }

    return ::HttpAddRequestHeaders(hRequest, szBuf, (DWORD)_tcslen(szBuf), 0);
}

bool CWinInetHelper::_InternetReadData(
    HINTERNET hRequest,
    PWININET_PACKAGE_INFO lpDownloadPackage,
    bool fFile/* = false*/,
    std::function<void(double lfProgress, ULONGLONG ullCur, ULONGLONG ullTotal)> cbProgress/* = nullptr*/
)
{
    LPBYTE lpBuffer = NULL;
    LPBYTE lpBufPos = (LPBYTE)lpDownloadPackage->lpBuffer;
    LARGE_INTEGER liDistanceToMove = { 0 };
    HANDLE hFile = INVALID_HANDLE_VALUE;
    bool bDownloadFinish = false;
    bool bRet = false;

    ULONGLONG ullCurRead = 0;
    ULONGLONG ullTotal = lpDownloadPackage->ullContentLength;
    clock_t lastTime = ::clock();
    clock_t updateInterval = 100;

    do
    {
        if (fFile)
        {
            // 共享读写 创建/打开 文件, 多线程读写
            hFile = ::CreateFile(lpDownloadPackage->strFileName.c_str(),
                GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE,
                NULL,
                OPEN_ALWAYS,
                FILE_ATTRIBUTE_ARCHIVE,
                NULL);

            if (INVALID_HANDLE_VALUE == hFile)
            {
                _PrintError(_T("CreateFile"));
                break;
            }
        }

        // 分配下载缓冲
        lpBuffer = (LPBYTE)::HeapAlloc(::GetProcessHeap(), 0, WININET_DOWNLOAD_BLOCK_SIZE);
        if (NULL == lpBuffer)
        {
            _PrintError(_T("HeapAlloc"));
            break;
        }

        liDistanceToMove.QuadPart = lpDownloadPackage->ullDataStartPos;
        ::SetFilePointerEx(hFile, liDistanceToMove, NULL, FILE_BEGIN);

        do
        {
            // 读取下载的数据到缓冲区
            DWORD dwRead = 0;
            bRet = ::InternetReadFile(hRequest, lpBuffer, WININET_DOWNLOAD_BLOCK_SIZE, &dwRead);
            if (false == bRet)
            {
                lpDownloadPackage->dwStatus = WININET_PACKAGE_INFO::DownloadStatus::DS_DownloadFailed;
                _PrintError(_T("InternetReadFile"));
                break;
            }

            clock_t curTime = ::clock();

            ullCurRead += dwRead;
            if (curTime - lastTime > updateInterval || 0 == dwRead)
            {
                lastTime = curTime;

                // 进度回调
                if (cbProgress)
                {
                    cbProgress((double)ullCurRead / (double)ullTotal, ullCurRead, ullTotal);
                }
            }

            // 检查下载是否完成
            if (0 == dwRead)
            {
                bDownloadFinish = true;
                lpDownloadPackage->dwStatus = WININET_PACKAGE_INFO::DownloadStatus::DS_DownloadFinish;
                break;
            }

            if (fFile)
            {
                // 写入到文件
                DWORD dwWritten = 0;
                if (!::WriteFile(hFile, lpBuffer, dwRead, &dwWritten, NULL))
                {
                    lpDownloadPackage->dwStatus = WININET_PACKAGE_INFO::DownloadStatus::DS_DownloadFailed;
                    _PrintError(_T("WriteFile"));
                    break;
                }

                // 记录当前线程已下载数据量
                lpDownloadPackage->ullDownloaded += dwWritten;
            }
            else
            {
                // 写入到缓冲
                memcpy(lpBufPos, lpBuffer, dwRead);
                lpBufPos += dwRead;

                lpDownloadPackage->ullDownloaded += dwRead;
            }

            // 中断下载检查
            if (m_bAbort)
            {
                lpDownloadPackage->dwStatus = WININET_PACKAGE_INFO::DownloadStatus::DS_DownloadCancel;
                break;
            }

        } while (bRet);

    } while (false);

    if (INVALID_HANDLE_VALUE != hFile)
    {
        ::CloseHandle(hFile);
    }

    if (lpBuffer)
    {
        ::HeapFree(::GetProcessHeap(), 0, lpBuffer);
    }

    return bDownloadFinish;
}

bool CWinInetHelper::_IsSupportAcceptRanges(HINTERNET hRequest)
{
    TCHAR szResponseBuffer[MAX_PATH] = { 0 };
    DWORD dwBufferLength = sizeof(szResponseBuffer);

    // 查询资源大小
    if (!::HttpQueryInfo(hRequest, HTTP_QUERY_ACCEPT_RANGES, szResponseBuffer, &dwBufferLength, NULL))
    {
        _PrintError(_T("_IsSupportAcceptRanges HttpQueryInfo"));
        return false;
    }

    if (0 == _tcsicmp(szResponseBuffer, _T("bytes")))
    {
        return true;
    }

    return false;
}

bool CWinInetHelper::_QueryContentLength(
    HINTERNET hRequest, 
    PULONGLONG lpUllContentLength
)
{
    TCHAR szResponseBuffer[MAX_PATH] = { 0 };
    DWORD dwBufferLength = sizeof(szResponseBuffer);

    // 查询资源大小
    if (!::HttpQueryInfo(hRequest, HTTP_QUERY_CONTENT_LENGTH, szResponseBuffer, &dwBufferLength, NULL))
    {
        m_bHasSize = false;
        _PrintError(_T("QueryContentLength HttpQueryInfo"));
        *lpUllContentLength = 0;
        return false;
    }

    m_bHasSize = true;
    *lpUllContentLength = _tcstoll(szResponseBuffer, NULL, 10);

    return true;
}

long CWinInetHelper::_GetStatusCode(HINTERNET hRequest)
{
    DWORD dwRespCode = 0;
    DWORD dwSize = sizeof(dwRespCode);

    // 查询请求状态码
    if (!::HttpQueryInfo(hRequest, HTTP_QUERY_STATUS_CODE | HTTP_QUERY_FLAG_NUMBER, &dwRespCode, &dwSize, NULL))
    {
        return dwRespCode;
    }

    return dwRespCode;
}

bool CWinInetHelper::_SendRequest(
    HINTERNET hRequest, 
    LPCTSTR lpszHeaders, 
    DWORD dwHeadersLength, 
    LPVOID lpData, 
    DWORD dwSize
)
{
    DWORD dwFlags = 0;
    DWORD dwFlagsSize = sizeof(dwFlags);

    dwFlags = INTERNET_FLAG_NO_AUTO_REDIRECT;
    if (!::InternetSetOption(hRequest, INTERNET_OPTION_SECURITY_FLAGS, &dwFlags, sizeof(dwFlags)))
    {
        _PrintError(_T("InternetSetOption"));
        return false;
    }

    // 将指定的请求发送到 HTTP 服务器
    if (::HttpSendRequest(hRequest, lpszHeaders, dwHeadersLength, lpData, dwSize))
    {
        return true;
    }

    // 检查错误是否为未知证书颁发机构
    if (ERROR_INTERNET_INVALID_CA != ::GetLastError())
    {
        return false;
    }

    // 获取INTERNET_OPTION_SECURITY_FLAGS标志 
    if (!::InternetQueryOption(hRequest, INTERNET_OPTION_SECURITY_FLAGS, &dwFlags, &dwFlagsSize))
    {
        _PrintError(_T("InternetQueryOption"));
        return false;
    }
    
    // 忽略未知证书颁发机构问题。
    dwFlags |= SECURITY_FLAG_IGNORE_UNKNOWN_CA;
    if (!::InternetSetOption(hRequest, INTERNET_OPTION_SECURITY_FLAGS, &dwFlags, sizeof(dwFlags)))
    {
        _PrintError(_T("InternetSetOption"));
        return false;
    }
    
    // 再次发送请求
    return ::HttpSendRequest(hRequest, lpszHeaders, dwHeadersLength, lpData, dwSize);
}

bool CWinInetHelper::_CrackUrl(
    const _tstring& strUrl, 
    PWININET_URL_INFO lpUrlParts
)
{
    // 将 URL 分解到其组件部件中
    if (!::InternetCrackUrl(strUrl.c_str(), 0, 0, &lpUrlParts->uc))
    {
        _PrintError(_T("InternetCrackUrl"));
        return false;
    }

    if (0 < ::_tcslen(lpUrlParts->uc.lpszExtraInfo))
    {
        ::_tcscat_s(lpUrlParts->uc.lpszUrlPath, _countof(lpUrlParts->szUrlPath), lpUrlParts->uc.lpszExtraInfo);
    }

    return true;
}

void CWinInetHelper::_PrintError(LPCTSTR lpszError) const
{
    ::_tprintf(_T("[Error] %s LastError[%d]\n"), lpszError, ::GetLastError());
}

void CWinInetHelper::_PrintWarn(LPCTSTR lpszError)  const
{
    ::_tprintf(_T("[Warn] %s LastError[%d]\n"), lpszError, ::GetLastError());
}

void CWinInetHelper::_InternetStatusCallback(
    HINTERNET hSession, 
    DWORD_PTR dwContext, 
    DWORD dwInternetStatus, 
    LPVOID lpvStatusInformation, 
    DWORD dwStatusInformationLength
)
{
    UNREFERENCED_PARAMETER(hSession);
    UNREFERENCED_PARAMETER(dwContext);
    UNREFERENCED_PARAMETER(dwStatusInformationLength);

    switch (dwInternetStatus)
    {
    //由 InternetConnect 用来指示它已创建新句柄。
    case INTERNET_STATUS_HANDLE_CREATED:
    {

    }
    break;
    // 此句柄值已终止。 pvStatusInformation 包含正在关闭的句柄的地址。 lpvStatusInformation 参数包含正在关闭的句柄的地址。
    case  INTERNET_STATUS_HANDLE_CLOSING:
    {

    }
    break;
    // 异步操作已完成。 lpvStatusInformation 参数包含INTERNET_ASYNC_RESULT结构的地址。
    case  INTERNET_STATUS_REQUEST_COMPLETE:
    {
        if (ERROR_SUCCESS == ((LPINTERNET_ASYNC_RESULT)
        (lpvStatusInformation))->dwError)
        {

        }
        else
        {

        }
    }
    break;
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值