WinHttp类封装:支持GET、POST请求及多线程文件下载的实现

内容目录

引言

在Windows平台上进行网络编程时,微软提供了WinHttp API供开发者使用。WinHttp API允许开发者发送HTTP请求,接收响应,并支持HTTPS协议。本文将介绍如何使用WinHttp API封装一个类,实现GET和POST请求,以及支持多线程文件下载的功能。

图片[1]-WinHttp类封装:支持GET、POST请求及多线程文件下载的实现-连界优站

WinHttp类设计

我们将创建一个名为WinHttpClient的类,该类将封装WinHttp API的关键功能,并提供易于使用的接口。

1. 类定义
#include <winhttp.h>
#include <string>
#include <vector>
#include <memory>

class WinHttpClient {
public:
    WinHttpClient();
    ~WinHttpClient();

    bool Initialize();
    void Uninitialize();

    std::string SendGetRequest(const std::string& url);
    std::string SendPostRequest(const std::string& url, const std::string& postData);

private:
    HINTERNET _session;
};
2. 初始化与销毁

在使用WinHttpClient类之前,我们需要先初始化会话,使用完毕后释放资源。

WinHttpClient::WinHttpClient() : _session(nullptr) {}

WinHttpClient::~WinHttpClient() {
    Uninitialize();
}

bool WinHttpClient::Initialize() {
    _session = WinHttpOpen(L"WinHttpClient Sample",
                           WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
                           WINHTTP_NO_PROXY_NAME,
                           WINHTTP_NO_PROXY_BYPASS,
                           0);
    return _session != nullptr;
}

void WinHttpClient::Uninitialize() {
    if (_session) {
        WinHttpCloseHandle(_session);
        _session = nullptr;
    }
}
3. 发送GET请求

发送GET请求相对简单,只需要创建连接,打开请求,并接收响应即可。

std::string WinHttpClient::SendGetRequest(const std::string& url) {
    if (!_session) {
        return "";
    }

    HINTERNET connection = WinHttpConnect(_session, url.c_str(), INTERNET_DEFAULT_HTTP_PORT, 0);
    if (!connection) {
        return "";
    }

    HINTERNET request = WinHttpOpenRequest(connection, L"GET", url.c_str(), nullptr, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, 0);
    if (!request) {
        WinHttpCloseHandle(connection);
        return "";
    }

    if (!WinHttpSendRequest(request, WINHTTP_NO_ADDITIONAL_HEADERS, 0, nullptr, 0, 0, 0)) {
        WinHttpCloseHandle(request);
        WinHttpCloseHandle(connection);
        return "";
    }

    if (!WinHttpReceiveResponse(request, nullptr)) {
        WinHttpCloseHandle(request);
        WinHttpCloseHandle(connection);
        return "";
    }

    DWORD dwStatusCode = 0;
    DWORD dwSize = sizeof(DWORD);
    if (!WinHttpQueryHeaders(request, WINHTTP_QUERY_STATUS_CODE, WINHTTP_HEADER_NAME_BY_INDEX, &dwStatusCode, &dwSize, WINHTTP_NO_HEADER_INDEX)) {
        WinHttpCloseHandle(request);
        WinHttpCloseHandle(connection);
        return "";
    }

    char buffer[4096];
    DWORD bytesRead = 0;
    std::string response;

    while (WinHttpReadData(request, reinterpret_cast<LPVOID>(buffer), sizeof(buffer) - 1, &bytesRead)) {
        buffer[bytesRead] = '\0';
        response.append(buffer, bytesRead);
    }

    WinHttpCloseHandle(request);
    WinHttpCloseHandle(connection);

    return response;
}
4. 发送POST请求

发送POST请求时,除了需要发送数据外,还需要设置正确的请求头。

std::string WinHttpClient::SendPostRequest(const std::string& url, const std::string& postData) {
    if (!_session) {
        return "";
    }

    HINTERNET connection = WinHttpConnect(_session, url.c_str(), INTERNET_DEFAULT_HTTP_PORT, 0);
    if (!connection) {
        return "";
    }

    HINTERNET request = WinHttpOpenRequest(connection, L"POST", url.c_str(), nullptr, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, 0);
    if (!request) {
        WinHttpCloseHandle(connection);
        return "";
    }

    if (!WinHttpAddRequestHeaders(request, L"Content-Type: application/x-www-form-urlencoded", -1, WINHTTP_ADDREQ_FLAG_ADD)) {
        WinHttpCloseHandle(request);
        WinHttpCloseHandle(connection);
        return "";
    }

    DWORD bytesWritten = 0;
    if (!WinHttpSendRequest(request, WINHTTP_NO_ADDITIONAL_HEADERS, 0, reinterpret_cast<const BYTE*>(postData.c_str()), static_cast<DWORD>(postData.size()), 0, 0)) {
        WinHttpCloseHandle(request);
        WinHttpCloseHandle(connection);
        return "";
    }

    if (!WinHttpReceiveResponse(request, nullptr)) {
        WinHttpCloseHandle(request);
        WinHttpCloseHandle(connection);
        return "";
    }

    DWORD dwStatusCode = 0;
    DWORD dwSize = sizeof(DWORD);
    if (!WinHttpQueryHeaders(request, WINHTTP_QUERY_STATUS_CODE, WINHTTP_HEADER_NAME_BY_INDEX, &dwStatusCode, &dwSize, WINHTTP_NO_HEADER_INDEX)) {
        WinHttpCloseHandle(request);
        WinHttpCloseHandle(connection);
        return "";
    }

    char buffer[4096];
    DWORD bytesRead = 0;
    std::string response;

    while (WinHttpReadData(request, reinterpret_cast<LPVOID>(buffer), sizeof(buffer) - 1, &bytesRead)) {
        buffer[bytesRead] = '\0';
        response.append(buffer, bytesRead);
    }

    WinHttpCloseHandle(request);
    WinHttpCloseHandle(connection);

    return response;
}

多线程文件下载

为了实现多线程文件下载,我们需要扩展上述类的功能,加入对分段下载的支持。

5. 多线程下载

这里我们简要介绍如何实现多线程下载的思路。具体实现细节可能需要根据实际需求定制。

// 假设定义了一个下载任务类
class DownloadTask {
public:
    void operator()(const std::string& url, const std::string& filePath, DWORD startOffset, DWORD endOffset);
};

// 下载任务执行
void DownloadTask::operator()(const std::string& url, const std::string& filePath, DWORD startOffset, DWORD endOffset) {
    // 使用WinHttpClient发送带有Range头的GET请求
    // 接收响应并将数据写入文件的相应位置
}
6. 主线程调度

在主线程中,我们需要计算文件分块,并为每个分块创建并启动一个下载线程。

#include <thread>
#include <vector>

int main() {
    // 初始化WinHttpClient
    WinHttpClient client;
    if (!client.Initialize()) {
        return -1;
    }

    // 获取文件大小
    DWORD fileSize = GetFileSizeFromUrl(url);
    DWORD chunkSize = 1024 * 1024; // 每个分块1MB

    // 创建任务向量
    std::vector<std::thread> threads;
    for (DWORD i = 0; i < fileSize; i += chunkSize) {
        DWORD endOffset = std::min(i + chunkSize - 1, fileSize - 1);
        threads.emplace_back(DownloadTask(), url, filePath, i, endOffset);
    }

    // 等待所有线程完成
    for (auto& thread : threads) {
        thread.join();
    }

    // 清理
    client.Uninitialize();

    return 0;
}

结语

通过上述步骤,我们封装了一个基于WinHttp API的客户端类,支持GET和POST请求,并且能够实现多线程文件下载。希望这篇教程能够帮助您更好地理解和使用WinHttp API进行网络编程。


本文介绍了如何使用WinHttp API封装一个支持GET、POST请求及多线程文件下载的类,并提供了完整的代码示例。希望对您在Windows平台上进行网络编程时有所帮助。

© 版权声明
THE END
喜欢就支持一下吧
点赞8赞赏 分享