掌握分片上传:优化大文件传输的关键策略 【C++】【WinHttp】【curl】

2023-12-18 05:15:00

目录

引言

第一部分:分片上传的基本概念

1. 分片上传以及它的工作原理

2. 为什么选择分片上传

第二部分:实现分片上传的关键步骤

1. 文件分片的方法,如何选择合适的分片大小

文件分片的基本步骤:

如何选择合适的分片大小:

2. 讨论建立稳定的文件传输协议,如HTTP多部分上传。? ??

HTTP多部分上传的基本概念:

建立稳定的HTTP多部分上传:

? ? ??

3. 介绍如何在客户端和服务器端管理文件片段,包括排序和重组。

客户端管理:

服务器端管理:

第三部分:编码实践与示例

1. 如何在C++中实现【winhttp】【curl】

2. Windows环境下使用特定的API或库进行优化

优化后的代码:

第四部分:处理常见问题与挑战

1. 讨论如何处理网络错误和重试机制

1. 识别网络错误

2. 错误分类

3. 实现重试策略

4. 重试限制

5. 记录和监控

2. 解释如何实现断点续传和进度跟踪

断点续传

进度跟踪

3. 讨论安全性问题,如使用HTTPS和数据校验

使用HTTPS

数据校验

上述汇总代码

winhttp版本

curl版本

面向对象修改代码

结论


引言

????????在现代应用开发中,处理大文件上传是一个普遍且复杂的挑战。随着数字媒体质量的提高和企业数据量的增长,用户和系统经常需要上传大型文件,如视频、高清图片集、大型文档或数据库备份。这些文件可能大小从几十兆字节到几个吉比甚至更大,其上传过程面临着多方面的挑战。

????????

  1. 网络不稳定性:大文件上传可能耗时较长,期间网络的任何不稳定性都可能导致上传失败,需重新上传整个文件,这不仅耗时而且效率低下。

  2. 带宽限制:在带宽有限的网络环境中,大文件上传可能导致其他网络活动受阻,影响用户体验。

  3. 服务器负担:一次性处理大量数据会给服务器带来巨大负担,尤其是在高并发的情况下,可能导致服务器响应缓慢或崩溃。

  4. 数据完整性与安全性:在长时间的上传过程中,文件数据更容易受到损坏或安全威胁。

????????分片上传技术在这些情况下显得尤为重要。通过将大文件分割成小的数据块(或“分片”)进行上传,分片上传技术有效地解决了上述挑战:

  • 提高可靠性:即使在网络不稳定的情况下,只需要重新上传受影响的小片段,而不是整个大文件。

  • 优化带宽使用:通过控制每个分片的大小,可以更有效地管理带宽使用,避免对网络造成过大压力。

  • 减轻服务器负担:服务器可以逐个处理小片段,而不是一次性处理整个大文件,从而降低了崩溃的风险。

  • 支持断点续传:如果上传过程中断,用户可以从上次中断的地方继续上传,而不是重新开始。

  • 增强数据安全:分片可以单独加密和校验,提高数据传输过程中的安全性和完整性。

第一部分:分片上传的基本概念

1. 分片上传以及它的工作原理

????????分片上传是一种文件上传技术,主要用于处理大型文件的传输。在这种方法中,大文件被切割成多个较小的数据块(称为“分片”),然后这些分片分别上传到服务器。这种方法的核心优势在于提高了上传过程的效率、可靠性和管理性。

?工作原理:

  1. 文件分割:

    ????????当上传一个大文件时,首先将文件分割成许多小块。这些小块的大小可以固定(例如,每块1MB)或根据网络状况和服务器能力动态调整。
  2. 逐块上传:

    ? ? ? ? 每个分片作为一个独立的单元被上传。这意味着即使其中一部分上传失败,也只需重新上传那个特定的分片,而非整个文件。
  3. 并发与顺序控制:

    ? ? ? ?分片允许并发上传,即多个片段可以同时上传,从而加快整体上传速度。同时,保持分片的顺序是重要的,以便在服务器端正确重组文件。
  4. 错误处理和重试:

    ? ? ? ? 如果某个分片上传失败(如因网络中断),系统可以自动重试上传该分片,而无需从头开始上传整个文件。
  5. 数据完整性验证:

    ? ? ? ? 上传完成后,可以对分片进行校验(例如,通过MD5或其他哈希算法),以确保数据的完整性和准确性。
  6. 文件重组:

    ? ? ? ? 所有分片上传完成后,服务器端的软件将这些分片重新组合成原始文件。这通常涉及校验分片的完整性和顺序,确保文件的准确重建。

2. 为什么选择分片上传

? ? ? ? 网络不稳定、大文件、带宽等因素。

????????

  1. 网络环境不稳定:

    • 在网络连接不稳定的情况下,传统的大文件上传更容易失败。如果在上传过程中发生中断,整个文件可能需要从头开始重新上传,这不仅耗时而且效率低下。
    • 分片上传通过将大文件分成小片段来降低每次上传的风险。如果某个分片由于网络问题上传失败,只需重新上传那个特定的分片,而不是整个文件,这大大降低了因网络不稳定造成的时间和资源浪费。
  2. 大文件处理:

    • 大文件上传是一个挑战,因为它们更容易受到网络波动的影响,且在传输过程中占用大量内存和带宽资源。
    • 分片上传允许逐块处理大文件,使得上传过程更加可管理。服务器可以逐个接收和处理这些较小的文件片段,而不是一次性处理整个庞大的文件。这种方法降低了服务器崩溃的风险,并提高了处理大文件的整体效率。
  3. 带宽优化:

    • 在带宽有限的环境中,一次性上传一个大文件可能会占用过多带宽,影响其他网络服务。
    • 分片上传可以更有效地利用带宽。通过控制每个片段的大小和上传速率,可以避免过度占用网络资源。此外,它还允许在网络条件较好时加快上传速度,在条件较差时减慢速度,从而实现带宽的动态优化。
  4. 断点续传:

    • 对于长时间的大文件上传,用户可能需要中断和恢复上传过程。
    • 分片上传支持断点续传功能,即在上传过程中断后可以从上次中断的地方继续,而不需要重新上传已完成的部分。这对用户来说非常方便,特别是在移动设备上或在网络条件频繁变化的环境中。
  5. 安全性和数据完整性:

    • 传输大文件过程中,文件数据更容易受到损坏或遭受安全威胁。
    • 分片上传每个分片可以单独加密和校验,提高了数据传输过程中的安全性和完整性。如果某个分片受损,只需重新上传该分片,而不是整个文件。

第二部分:实现分片上传的关键步骤

1. 文件分片的方法,如何选择合适的分片大小

文件分片的基本步骤:

  1. 读取原始文件:

    • 使用文件读取函数(如在C++中的ifstream)打开要上传的文件。
    • 确定文件的总大小,以便计算分片的数量。
  2. 确定分片数量:

    • 根据文件总大小和预定的分片大小,计算需要划分的分片数量。
    • 如果文件大小不能被分片大小整除,最后一个分片会小于其他分片。
  3. 划分分片:

    • 循环地从文件中读取固定大小的数据块,直到文件结束。
    • 每读取一块数据,就创建一个分片,并将读取的数据存储在该分片中。
  4. 存储分片信息:

    • 为每个分片分配一个唯一标识符,以便于上传和重组时进行追踪。
    • 记录每个分片的大小和在原文件中的位置。

如何选择合适的分片大小:

  1. 网络稳定性:

    • 在网络稳定性较差的环境中,建议使用较小的分片大小。这样,即使遇到上传失败,需要重新上传的数据量也较小。
  2. 上传效率:

    • 较大的分片可以减少管理开销和HTTP请求的数量,从而在网络稳定的环境中提高上传效率。但过大的分片可能增加单次上传失败的风险。
  3. 服务器限制:

    • 考虑服务器端可能对上传文件的大小有限制。确保单个分片的大小不会超过这些限制。
  4. 带宽优化:

    • 在带宽受限的环境中,使用较小的分片可以避免长时间占用大量带宽,从而减少对其他网络活动的影响。
  5. 实验与调整:

    • 分片大小的选择可能需要根据实际情况进行实验和调整。可以开始于一个中等大小的分片(如1-5MB),根据实际上传效果进行调整。
  6. 应用场景:

    • 考虑应用的具体场景。例如,对于需要实时处理的文件(如视频流),可能需要更小的分片以减少延迟。

????????选择合适的分片大小是一个平衡过程,需要根据具体的应用场景、网络环境和服务器能力来确定。通过合理设置分片大小,可以在保证上传成功率的同时,优化上传速度和用户体验。

2. 讨论建立稳定的文件传输协议,如HTTP多部分上传。? ??

????????建立稳定的文件传输协议是实现高效、可靠文件上传的关键,特别是在涉及到分片上传时。HTTP多部分上传是一种常用的协议,用于优化和稳定文件传输。以下是关于HTTP多部分上传的一些重要讨论点:

HTTP多部分上传的基本概念:

  1. 多部分MIME类型:

    • HTTP多部分上传使用multipart/form-data MIME类型,它允许在一个单独的HTTP请求中发送多个数据部分。
    • 每个部分可以有自己的HTTP头和内容类型,使其适合传输文件和相关的元数据。
  2. 请求结构:

    • 在HTTP请求中,每个分片作为请求的一个部分发送。这些部分由边界字符串分隔,该字符串在请求的头部定义。
  3. 效率与兼容性:

    • 使用HTTP多部分上传可以提高数据传输的效率,因为它避免了为每个文件分片建立单独的HTTP连接。
    • 多部分上传广泛支持于各种HTTP客户端和服务器,确保了良好的兼容性。

建立稳定的HTTP多部分上传:

  1. 错误处理:

    • 稳定的文件传输协议需要能够妥善处理传输过程中的错误,例如网络中断或服务器错误。
    • 实现机制应包括错误检测、日志记录和重试策略。
  2. 数据完整性:

    • 保证上传数据的完整性是至关重要的。可以通过在上传完成后对文件进行校验(如计算和验证哈希值)来实现。
  3. 安全性考虑:

    • 使用HTTPS协议来加密数据传输,保护数据免受中间人攻击。
    • 对敏感数据进行加密,尤其是在传输过程中。
  4. 并发控制:

    • 对于分片上传,合理地控制并发上传的分片数量可以提高效率,同时避免过载服务器或网络。
  5. 带宽管理:

    • 根据网络状况动态调整上传速率,可以优化带宽使用并提高上传稳定性。
  6. 用户体验:

    • 提供进度反馈和可暂停及恢复的上传选项,以提高用户体验。
  7. 服务器端处理:

    • 服务器端应有能力处理并重组上传的文件分片。这包括验证每个分片的完整性和顺序,并在所有分片上传完成后重构原始文件。

? ? ??

3. 介绍如何在客户端和服务器端管理文件片段,包括排序和重组。

????????在分片上传的过程中,正确管理文件片段是确保数据完整性和有效重组的关键。这包括在客户端进行有效的分片和上传管理,以及在服务器端正确地排序和重组这些分片。

客户端管理:

  1. 分片创建:

    • 客户端首先需要将大文件分割成小的片段。这通常通过读取文件的一部分到缓冲区,然后将这部分数据保存为一个独立的分片。
  2. 分片标识:

    • 每个分片应该有一个唯一标识,如序号或哈希值,以帮助服务器端正确地识别和排序。
  3. 并发上传与错误处理:

    • 客户端可以并发上传多个分片以提高效率。同时,必须实现错误处理机制,如在上传失败时重试。
  4. 进度跟踪和用户反馈:

    • 客户端应跟踪每个分片的上传进度,并提供给用户相应的反馈,如进度条或完成百分比。
  5. 数据完整性检查:

    • 在上传前,客户端可以计算每个分片的哈希值,并在上传完成后验证以确保数据完整性。

服务器端管理:

  1. 分片接收:

    • 服务器需要处理来自客户端的分片上传请求,并存储接收到的分片。
  2. 分片排序和校验:

    • 使用客户端提供的标识(如序号或哈希值)对分片进行排序和校验,确保它们的顺序正确,且未被损坏或篡改。
  3. 重组文件:

    • 一旦所有分片都被成功上传和验证,服务器端应将这些分片按照正确的顺序组合成原始文件。
  4. 处理不完整的上传:

    • 如果上传过程中断或某些分片丢失,服务器需要能够识别这种情况,并通知客户端哪些分片需要重新上传。
  5. 最终验证:

    • 文件重组后,服务器应进行最终验证,比如比对文件的总大小或总哈希值,以确保重组后的文件与原始文件一致。
  6. 资源管理:

    • 服务器需要有效管理资源,例如定期清理未完成的上传或过期的分片,以避免资源浪费。

第三部分:编码实践与示例

1. 如何在C++中实现【winhttp】【curl】

使用 winhttp?的示例:

#include <Windows.h>
#include <winhttp.h>
#include <fstream>
#include <vector>
#include <string>
#include <iostream>
#include <algorithm>
#pragma comment(lib, "winhttp.lib")


const size_t CHUNK_SIZE = 10 * 1024 * 1024; // 分片大小设置为10MB
const LPCWSTR UPLOAD_URL = L"http://www.example.com/upload";
const LPCWSTR HOST_NAME = L"www.example.com";
std::vector<std::vector<char>> SplitFileIntoChunks(const std::string& filePath) {
    std::ifstream file(filePath, std::ios::binary | std::ios::ate);
    if (!file.is_open()) {
        throw std::runtime_error("Unable to open file");
    }

    size_t fileSize = file.tellg();
    file.seekg(0, std::ios::beg);

    std::vector<std::vector<char>> chunks;
    size_t remainingSize = fileSize;

    while (remainingSize > 0) 
    {
        size_t currentChunkSize = (CHUNK_SIZE < remainingSize) ? CHUNK_SIZE : remainingSize;
        std::vector<char> buffer(currentChunkSize);

        file.read(buffer.data(), currentChunkSize);
        chunks.push_back(std::move(buffer));
        remainingSize -= currentChunkSize;
    }

    file.close();
    return chunks;
}

void UploadChunk(HINTERNET hConnect, const std::vector<char>& chunkData, int chunkNumber) {
    HINTERNET hRequest = WinHttpOpenRequest(hConnect, L"POST", UPLOAD_URL, NULL, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, 0);

    std::wstring headers = L"Content-Type: application/octet-stream\r\n";
    headers += L"Chunk-Number: " + std::to_wstring(chunkNumber) + L"\r\n";

    WinHttpAddRequestHeaders(hRequest, headers.c_str(), -1, WINHTTP_ADDREQ_FLAG_ADD);

    WinHttpSendRequest(hRequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0, const_cast<char*>(chunkData.data()), chunkData.size(), chunkData.size(), 0);
    WinHttpReceiveResponse(hRequest, NULL);

    // 检查响应和错误处理...

    WinHttpCloseHandle(hRequest);
}

int main() {
    try {
        // 分割文件为分片
        auto chunks = SplitFileIntoChunks("path/to/your/largefile");

        // 初始化WinHTTP
        HINTERNET hSession = WinHttpOpen(L"A WinHTTP Example Program/1.0", WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0);
        HINTERNET hConnect = WinHttpConnect(hSession, HOST_NAME, INTERNET_DEFAULT_HTTP_PORT, 0);

        // 上传每个分片
        for (size_t i = 0; i < chunks.size(); ++i) {
            UploadChunk(hConnect, chunks[i], i);
        }

        // 清理
        WinHttpCloseHandle(hConnect);
        WinHttpCloseHandle(hSession);
    }
    catch (const std::exception& e) {
        std::cerr << e.what() << std::endl;
    }

    return 0;
}

使用 curl 的示例:

#include <iostream>
#include <fstream>
#include <vector>
#include <curl/curl.h>


const size_t CHUNK_SIZE = 10 * 1024 * 1024; // 分片大小设置为10MB

std::vector<std::vector<char>> SplitFileIntoChunks(const std::string& filePath) {
    std::ifstream file(filePath, std::ios::binary | std::ios::ate);
    if (!file.is_open()) {
        throw std::runtime_error("Unable to open file");
    }

    size_t fileSize = file.tellg();
    file.seekg(0, std::ios::beg);

    std::vector<std::vector<char>> chunks;
    size_t remainingSize = fileSize;

    while (remainingSize > 0) {
        size_t currentChunkSize = (CHUNK_SIZE < remainingSize) ? CHUNK_SIZE : remainingSize;
        std::vector<char> buffer(currentChunkSize);

        file.read(buffer.data(), currentChunkSize);
        chunks.push_back(std::move(buffer));
        remainingSize -= currentChunkSize;
    }

    file.close();
    return chunks;
}

size_t ReadMemoryCallback(void* ptr, size_t size, size_t nmemb, void* userp) {
    auto& uploadData = *static_cast<std::vector<char>*>(userp);

    size_t toCopy = std::min(size * nmemb, uploadData.size());
    memcpy(ptr, uploadData.data(), toCopy);
    uploadData.erase(uploadData.begin(), uploadData.begin() + toCopy);

    return toCopy;
}

void UploadChunkCurl(const std::vector<char>& chunkData, int chunkNumber) {
    CURL* curl = curl_easy_init();
    if (curl) {
        curl_easy_setopt(curl, CURLOPT_URL, "http://www.example.com/upload");
        curl_easy_setopt(curl, CURLOPT_POST, 1L);

        curl_easy_setopt(curl, CURLOPT_READFUNCTION, ReadMemoryCallback);
        curl_easy_setopt(curl, CURLOPT_READDATA, &chunkData);
        curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, static_cast<long>(chunkData.size()));

        struct curl_slist* headers = NULL;
        headers = curl_slist_append(headers, "Content-Type: application/octet-stream");
        std::string chunkHeader = "Chunk-Number: " + std::to_string(chunkNumber);
        headers = curl_slist_append(headers, chunkHeader.c_str());
        curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);

        CURLcode res = curl_easy_perform(curl);
        // 检查响应和错误处理...

        curl_slist_free_all(headers);
        curl_easy_cleanup(curl);
    }
}
int main() {
    curl_global_init(CURL_GLOBAL_DEFAULT);

    try {
        // 分割文件为分片
        auto chunks = SplitFileIntoChunks("path/to/your/largefile");

        // 上传每个分片
        for (size_t i = 0; i < chunks.size(); ++i) {
            UploadChunkCurl(chunks[i], i);
        }
    }
    catch (const std::exception& e) {
        std::cerr << e.what() << std::endl;
    }

    curl_global_cleanup();
    return 0;
}

?

2. Windows环境下使用特定的API或库进行优化

????????

  1. 选择合适的Windows API:

    • 使用Windows提供的网络编程接口,如WinINet或WinHTTP,这些API专为Windows环境设计,能够更有效地处理网络请求。
    • 利用Windows的文件系统API(如File Management Functions),这些函数可以高效地处理文件操作,包括读取和写入大文件的分片。
  2. 利用多线程和异步编程:

    • 利用Windows的多线程功能,如Windows线程池(ThreadPool API)或者使用C++11的std::thread,来并行处理文件的不同分片,以提高上传效率。
    • 使用异步编程模式(例如,Windows的Overlapped I/O),可以在不阻塞主线程的情况下,进行文件的读写和网络通信,提高应用程序的响应性和效率。
  3. 内存管理和优化:

    • 使用Windows内存管理API(如VirtualAlloc)来有效地分配和管理用于存储文件分片的内存。
    • 注意处理内存碎片问题,确保高效利用内存资源。
  4. 使用特定的库和工具:

    • 利用像Boost.Asio这样的C++库,它提供了强大的异步IO功能,适用于处理复杂的网络通信和数据传输。
    • 考虑使用微软的Azure Storage SDK,如果你的应用与Azure云服务集成,这个SDK提供了许多优化的功能,特别是在处理大规模文件传输时。
  5. 安全性和错误处理:

    • 确保使用安全的通信协议,例如HTTPS,特别是在使用Windows网络API时。
    • 实现有效的错误处理机制,以应对网络中断或文件传输错误,确保上传过程的稳定性和可靠性。

优化后的代码:

#include <Windows.h>
#include <winhttp.h>
#include <fstream>
#include <vector>
#include <string>
#include <iostream>
#include <algorithm>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <queue>
#pragma comment(lib, "winhttp.lib")

const size_t CHUNK_SIZE = 10 * 1024 * 1024; // 分片大小设置为10MB
const LPCWSTR UPLOAD_URL = L"https://www.example.com/upload"; // 使用HTTPS
const LPCWSTR HOST_NAME = L"www.example.com";
const int MAX_THREADS = 4; // 最大线程数

std::mutex queueMutex;
std::condition_variable conditionVariable;
std::queue<std::pair<std::vector<char>, int>> uploadQueue;


void UploadChunk(HINTERNET hConnect, const std::vector<char>& chunkData, int chunkNumber) {
    // 创建HTTP请求
    HINTERNET hRequest = WinHttpOpenRequest(hConnect, L"POST", UPLOAD_URL, NULL, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, 0);

    // 添加请求头
    std::wstring headers = L"Content-Type: application/octet-stream\r\n";
    headers += L"Chunk-Number: " + std::to_wstring(chunkNumber) + L"\r\n";
    WinHttpAddRequestHeaders(hRequest, headers.c_str(), -1, WINHTTP_ADDREQ_FLAG_ADD);

    // 发送请求
    if (!WinHttpSendRequest(hRequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0, const_cast<char*>(chunkData.data()), chunkData.size(), chunkData.size(), 0)) {
        std::cerr << "WinHttpSendRequest failed: " << GetLastError() << std::endl;
        WinHttpCloseHandle(hRequest);
        return;
    }

    // 接收响应
    if (!WinHttpReceiveResponse(hRequest, NULL)) {
        std::cerr << "WinHttpReceiveResponse failed: " << GetLastError() << std::endl;
        WinHttpCloseHandle(hRequest);
        return;
    }

    // 读取响应(如果需要)
    DWORD dwSize = 0;
    DWORD dwDownloaded = 0;
    LPSTR pszOutBuffer;
    BOOL bResults = FALSE;

    do {
        dwSize = 0;
        if (!WinHttpQueryDataAvailable(hRequest, &dwSize)) {
            std::cerr << "WinHttpQueryDataAvailable failed: " << GetLastError() << std::endl;
            break;
        }

        pszOutBuffer = new char[dwSize + 1];
        if (!pszOutBuffer) {
            std::cerr << "Out of memory" << std::endl;
            dwSize = 0;
            break;
        }
        else {
            ZeroMemory(pszOutBuffer, dwSize + 1);

            if (!WinHttpReadData(hRequest, (LPVOID)pszOutBuffer, dwSize, &dwDownloaded)) {
                std::cerr << "WinHttpReadData failed: " << GetLastError() << std::endl;
            }
            else {
                // 处理接收到的数据
                std::cout << "Response: " << pszOutBuffer << std::endl;
            }

            delete[] pszOutBuffer;
        }
    } while (dwSize > 0);

    WinHttpCloseHandle(hRequest);
}






void UploadThread(HINTERNET hConnect) {
    while (true) {
        std::pair<std::vector<char>, int> chunkData;
        {
            std::unique_lock<std::mutex> lock(queueMutex);
            conditionVariable.wait(lock, [] { return !uploadQueue.empty(); });
            chunkData = uploadQueue.front();
            uploadQueue.pop();
        }

        // 如果数据为空,线程结束
        if (chunkData.first.empty()) break;

        // 上传逻辑...
        // 调用上传函数
        UploadChunk(hConnect, chunkData.first, chunkData.second);
    }
}

std::vector<std::vector<char>> SplitFileIntoChunks(const std::string& filePath) {
    std::ifstream file(filePath, std::ios::binary | std::ios::ate);
    if (!file.is_open()) {
        throw std::runtime_error("Unable to open file");
    }

    size_t fileSize = file.tellg();
    file.seekg(0, std::ios::beg);

    std::vector<std::vector<char>> chunks;
    size_t remainingSize = fileSize;

    while (remainingSize > 0)
    {
        size_t currentChunkSize = (CHUNK_SIZE < remainingSize) ? CHUNK_SIZE : remainingSize;
        std::vector<char> buffer(currentChunkSize);

        file.read(buffer.data(), currentChunkSize);
        chunks.push_back(std::move(buffer));
        remainingSize -= currentChunkSize;
    }

    file.close();
    return chunks;
}





void QueueChunkForUpload(const std::vector<char>& chunk, int chunkNumber) {
    std::unique_lock<std::mutex> lock(queueMutex);
    uploadQueue.emplace(chunk, chunkNumber);
    conditionVariable.notify_one();
}



int main() {
    try {
        // 分割文件为分片
    

        HINTERNET hSession = WinHttpOpen(L"A WinHTTP Example Program/1.0", WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0);
        HINTERNET hConnect = WinHttpConnect(hSession, HOST_NAME, INTERNET_DEFAULT_HTTPS_PORT, 0); // 使用HTTPS端口

        // 创建线程
        std::vector<std::thread> threads;
        for (int i = 0; i < MAX_THREADS; ++i) {
            threads.emplace_back(UploadThread, hConnect);
        }

        // 分割文件为分片并加入队列
        auto chunks = SplitFileIntoChunks("path/to/your/largefile");
        for (size_t i = 0; i < chunks.size(); ++i) {
            QueueChunkForUpload(chunks[i], i);
        }

        // 添加空块以通知线程结束
        for (int i = 0; i < MAX_THREADS; ++i) {
            QueueChunkForUpload({}, -1);
        }

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

        // 清理
        WinHttpCloseHandle(hConnect);
        WinHttpCloseHandle(hSession);
    }
    catch (const std::exception& e) {
        std::cerr << e.what() << std::endl;
    }

    return 0;
}

第四部分:处理常见问题与挑战

1. 讨论如何处理网络错误和重试机制

1. 识别网络错误

????????网络错误可以包括各种情况,如服务器无响应、连接超时、数据传输中断等。在Windows网络编程中,这些错误通常会通过特定的错误码表示,比如使用 GetLastError() 函数获取的错误码。

2. 错误分类

????????不是所有错误都应该触发重试。例如,身份验证失败或请求的资源不存在等错误通常不应重试。而连接超时、服务器错误(如HTTP 500系列错误)等则可能是暂时性的,可以考虑重试。

3. 实现重试策略

????????重试策略应该是可配置的,包括重试次数和重试间隔。常见的策略包括:

  • 固定间隔重试:每次重试之间等待固定的时间。
  • 指数退避:每次重试等待的时间逐渐增加,可以减少对服务器的负担。

4. 重试限制

????????设置合理的重试次数限制,以防止无限重试。通常,3到5次重试是一个合理的范围。

5. 记录和监控

????????记录重试日志,包括每次重试的原因、时间和结果,这对于问题诊断和性能监控非常重要。

?以下是一个简化的重试机制实现示例,这个示例集成在之前的上传函数中:

bool UploadChunkWithRetry(HINTERNET hConnect, const std::vector<char>& chunkData, int chunkNumber) {
    const int maxRetries = 3;
    const int retryInterval = 2000; // 毫秒
    for (int attempt = 0; attempt < maxRetries; ++attempt) {
        if (attempt > 0) {
            std::cout << "Retrying (" << attempt << "/" << maxRetries << ")..." << std::endl;
            Sleep(retryInterval); // 等待一段时间再重试
        }

        if (UploadChunk(hConnect, chunkData, chunkNumber)) {
            return true; // 上传成功
        }
        // 根据错误码判断是否需要重试
        DWORD error = GetLastError();
        if (error != ERROR_INTERNET_TIMEOUT && error != ERROR_INTERNET_CONNECTION_RESET) {
            break; // 对于非暂时性错误,不进行重试
        }
    }
    return false; // 最终重试失败
}

void UploadThread(HINTERNET hConnect) {
    // ... 之前的逻辑 ...
    if (chunkData.first.empty()) break; // 如果数据为空,线程结束

    // 调用带有重试机制的上传函数
    if (!UploadChunkWithRetry(hConnect, chunkData.first, chunkData.second)) {
        std::cerr << "Failed to upload chunk after retries." << std::endl;
    }
}

2. 解释如何实现断点续传和进度跟踪

????????实现断点续传和进度跟踪是在处理大文件上传时的重要特性,尤其在网络不稳定或需要暂停和恢复上传时尤为关键。以下是实现这两个功能的关键步骤:

断点续传

????????断点续传允许上传在中断后从上次中断的地方继续,而不是重新开始。这通常涉及以下步骤:

  1. 记录上传进度

    • 在上传过程中,需要记录每个分片的上传状态(未上传、正在上传、已上传)。
    • 可以使用数据库、文件或内存中的数据结构来跟踪每个分片的状态。
  2. 恢复上传

    • 在重新开始上传时,先检查哪些分片已经上传。
    • 可以通过查询服务器或检查本地记录的状态来实现。
    • 只上传那些尚未成功上传的分片。
  3. 服务器支持

    • 服务器端需要支持接收部分上传的文件。
    • 服务器可能需要提供接口以查询文件的当前上传状态。

进度跟踪

????????进度跟踪是让用户知道当前上传进度的重要功能。实现这一功能的关键点包括:

  1. 实时更新

    • 随着每个分片的上传,实时更新上传进度。
    • 可以通过计算已上传的分片与总分片数的比例来实现。
  2. 用户界面反馈

    • 在用户界面上显示进度条或百分比来反映当前进度。
    • 需要确保进度更新不会过于频繁地刷新界面,导致性能下降。
  3. 错误和重试的处理

    • 在进度跟踪中也要考虑到错误和重试。
    • 当分片上传失败并重试时,进度应该保持不变或相应更新。
std::vector<bool> uploadedChunks(chunks.size(), false); // 跟踪每个分片的上传状态

void UploadThread(HINTERNET hConnect, size_t totalChunks) {
    size_t uploadedCount = 0; // 已上传的分片数量

    while (true) {
        std::pair<std::vector<char>, int> chunkData;
        // ... 获取分片数据的逻辑 ...

        if (chunkData.first.empty()) break; // 如果数据为空,线程结束

        if (!uploadedChunks[chunkData.second]) {
            if (UploadChunkWithRetry(hConnect, chunkData.first, chunkData.second)) {
                uploadedChunks[chunkData.second] = true;
                ++uploadedCount;
                std::cout << "Progress: " << (uploadedCount * 100 / totalChunks) << "%" << std::endl; // 显示进度
            } else {
                std::cerr << "Failed to upload chunk after retries." << std::endl;
            }
        }
    }
}

int main() {
    // ... 之前的逻辑 ...

    // 创建线程并传递总分片数
    std::vector<std::thread> threads;
    for (int i = 0; i < MAX_THREADS; ++i) {
        threads.emplace_back(UploadThread, hConnect, chunks.size());
    }

    // ... 其他逻辑 ...
}

????????在这个示例中,我们使用一个布尔型数组 uploadedChunks 来跟踪每个分片的上传状态。每当一个分片成功上传后,我们更新 uploadedChunks 并打印当前的上传进度。这个例子只提供了一个基础的框架,实际应用中可能需要更复杂的逻辑来处理网络错误、暂停和恢复上传等情况。?

3. 讨论安全性问题,如使用HTTPS和数据校验

????????在实现文件上传功能时,考虑安全性是至关重要的。主要的安全考虑包括确保数据传输的安全性和完整性。以下是关于使用HTTPS和数据校验的一些重要考虑因素:

使用HTTPS

  1. 加密传输

    • HTTPS是HTTP协议的安全版本,它在HTTP和TCP之间增加了一个安全层(通常是SSL/TLS),为数据传输提供加密。
    • 这保证了传输的数据对中间人不可见,防止了数据泄露和篡改。
  2. 服务器身份验证

    • HTTPS还包括对服务器身份的验证,确保客户端与预期的服务器通信,防止DNS欺骗等攻击。
  3. 配置和更新

    • 使用HTTPS时,需要正确配置服务器和客户端。
    • 定期更新SSL/TLS库和证书,以避免已知的安全漏洞。

数据校验

  1. 完整性校验

    • 为确保数据在传输过程中未被篡改,可以在客户端计算文件或分片的哈希(如MD5、SHA256)并随数据一起发送。
    • 服务器接收后计算接收到的数据的哈希,并与客户端提供的哈希进行比较。
  2. 校验失败的处理

    • 如果发现数据校验失败,应拒绝接收该数据,并通知客户端重新传输。
  3. 防篡改

    • 数据校验确保了数据的完整性,防止了数据在传输过程中的篡改。
  4. 校验与性能

    • 数据校验可能会增加额外的计算负担。在设计系统时需要平衡安全性和性能。

????????使用HTTPS和数据校验是保护文件上传安全的重要措施。它们帮助保证数据在传输过程中的安全性和完整性,对抵御多种网络攻击至关重要。然而,这些措施也带来了额外的计算和配置负担,因此在设计和实现时需要考虑到这些因素的平衡。

简单代码示例:

void UploadChunk(HINTERNET hConnect, const std::vector<char>& chunkData, int chunkNumber) {
    // 计算哈希值
    std::string chunkHash = CalculateSHA256(chunkData);

    // 创建HTTPS请求
    HINTERNET hRequest = WinHttpOpenRequest(hConnect, L"POST", UPLOAD_URL, NULL, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, WINHTTP_FLAG_SECURE);

    // 忽略SSL错误 - 仅在测试环境中使用
    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;
    WinHttpSetOption(hRequest, WINHTTP_OPTION_SECURITY_FLAGS, &dwFlags, sizeof(dwFlags));

    // 添加请求头
    std::wstring headers = L"Content-Type: application/octet-stream\r\n";
    headers += L"Chunk-Number: " + std::to_wstring(chunkNumber) + L"\r\n";
    headers += L"Chunk-Hash: " + std::wstring(chunkHash.begin(), chunkHash.end()) + L"\r\n"; // 添加哈希值头部
    WinHttpAddRequestHeaders(hRequest, headers.c_str(), -1, WINHTTP_ADDREQ_FLAG_ADD);

    // 发送请求和上传数据
    // ... 发送和接收逻辑 ...
}

上述汇总代码

winhttp版本

? ? ? ? vs2022编译通过,具体自己调试。

#include <Windows.h>
#include <winhttp.h>
#include <wininet.h>
#include <fstream>
#include <vector>
#include <string>
#include <iostream>
#include <algorithm>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <queue>
#pragma comment(lib, "winhttp.lib")

const size_t CHUNK_SIZE = 10 * 1024 * 1024; // 分片大小设置为10MB
const LPCWSTR UPLOAD_URL = L"https://www.example.com/upload"; // 使用HTTPS
const LPCWSTR HOST_NAME = L"www.example.com";
const int MAX_THREADS = 4; // 最大线程数

std::mutex queueMutex;
std::condition_variable conditionVariable;
std::queue<std::pair<std::vector<char>, int>> uploadQueue;

std::mutex progressMutex;
size_t uploadedCount = 0; // 全局变量

std::vector<std::vector<char>> SplitFileIntoChunks(const std::string& filePath) {
    std::ifstream file(filePath, std::ios::binary | std::ios::ate);
    if (!file.is_open()) {
        throw std::runtime_error("Unable to open file");
    }

    size_t fileSize = file.tellg();
    file.seekg(0, std::ios::beg);

    std::vector<std::vector<char>> chunks;
    size_t remainingSize = fileSize;

    while (remainingSize > 0)
    {
        size_t currentChunkSize = (CHUNK_SIZE < remainingSize) ? CHUNK_SIZE : remainingSize;
        std::vector<char> buffer(currentChunkSize);

        file.read(buffer.data(), currentChunkSize);
        chunks.push_back(std::move(buffer));
        remainingSize -= currentChunkSize;
    }

    file.close();
    return chunks;
}


//全局
auto chunks = SplitFileIntoChunks("path/to/your/largefile");
std::vector<bool> uploadedChunks(chunks.size(), false);
auto totalChunks = chunks.size();

bool UploadChunk(HINTERNET hConnect, const std::vector<char>& chunkData, int chunkNumber) {
    HINTERNET hRequest = WinHttpOpenRequest(hConnect, L"POST", UPLOAD_URL, NULL, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, 0);

    if (!hRequest) {
        std::cerr << "WinHttpOpenRequest failed: " << GetLastError() << std::endl;
        return false;
    }

    std::wstring headers = L"Content-Type: application/octet-stream\r\n";
    headers += L"Chunk-Number: " + std::to_wstring(chunkNumber) + L"\r\n";

    if (!WinHttpAddRequestHeaders(hRequest, headers.c_str(), -1, WINHTTP_ADDREQ_FLAG_ADD)) {
        std::cerr << "WinHttpAddRequestHeaders failed: " << GetLastError() << std::endl;
        WinHttpCloseHandle(hRequest);
        return false;
    }

    if (!WinHttpSendRequest(hRequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0, const_cast<char*>(chunkData.data()), chunkData.size(), chunkData.size(), 0)) {
        std::cerr << "WinHttpSendRequest failed: " << GetLastError() << std::endl;
        WinHttpCloseHandle(hRequest);
        return false;
    }

    if (!WinHttpReceiveResponse(hRequest, NULL)) {
        std::cerr << "WinHttpReceiveResponse failed: " << GetLastError() << std::endl;
        WinHttpCloseHandle(hRequest);
        return false;
    }

    DWORD dwSize = 0;
    DWORD dwDownloaded = 0;
    LPSTR pszOutBuffer;
    BOOL bResults = FALSE;

    do {
        dwSize = 0;
        if (!WinHttpQueryDataAvailable(hRequest, &dwSize)) {
            std::cerr << "WinHttpQueryDataAvailable failed: " << GetLastError() << std::endl;
            break;
        }

        pszOutBuffer = new char[dwSize + 1];
        if (!pszOutBuffer) {
            std::cerr << "Out of memory" << std::endl;
            dwSize = 0;
            break;
        }
        else {
            ZeroMemory(pszOutBuffer, dwSize + 1);

            if (!WinHttpReadData(hRequest, (LPVOID)pszOutBuffer, dwSize, &dwDownloaded)) {
                std::cerr << "WinHttpReadData failed: " << GetLastError() << std::endl;
                delete[] pszOutBuffer;
                WinHttpCloseHandle(hRequest);
                return false;
            }
            else {
                std::cout << "Response: " << pszOutBuffer << std::endl;
            }

            delete[] pszOutBuffer;
        }
    } while (dwSize > 0);

    WinHttpCloseHandle(hRequest);
    return true; // Return true if the entire process completes without any errors
}


bool UploadChunkWithRetry(HINTERNET hConnect, const std::vector<char>& chunkData, int chunkNumber) {
    const int maxRetries = 3;
    const int retryInterval = 2000; // 毫秒
    for (int attempt = 0; attempt < maxRetries; ++attempt) {
        if (attempt > 0) {
            std::cout << "Retrying (" << attempt << "/" << maxRetries << ")..." << std::endl;
            Sleep(retryInterval); // 等待一段时间再重试
        }

        if (UploadChunk(hConnect, chunkData, chunkNumber)) {
            return true; // 上传成功
        }
        // 根据错误码判断是否需要重试
        DWORD error = GetLastError();
        if (error != ERROR_INTERNET_TIMEOUT && error != ERROR_INTERNET_CONNECTION_RESET) {
            break; // 对于非暂时性错误,不进行重试
        }
    }
    return false; // 最终重试失败
}

void UpdateProgress(size_t chunkCount, size_t totalChunks) {
    std::lock_guard<std::mutex> lock(progressMutex);
    uploadedCount += chunkCount;
    std::cout << "Progress: " << (uploadedCount * 100 / totalChunks) << "%" << std::endl; // 显示进度
}


void UploadThread(HINTERNET hConnect) {
    size_t uploadedCount = 0;
    while (true) {
        std::pair<std::vector<char>, int> chunkData;
        {
            std::unique_lock<std::mutex> lock(queueMutex);
            conditionVariable.wait(lock, [] { return !uploadQueue.empty(); });
            chunkData = uploadQueue.front();
            uploadQueue.pop();
        }

        // 如果数据为空,线程结束
        if (chunkData.first.empty()) break;

        // 上传逻辑...
        // 调用上传函数
        if (!uploadedChunks[chunkData.second]) {
            if (UploadChunkWithRetry(hConnect, chunkData.first, chunkData.second)) {
                uploadedChunks[chunkData.second] = true;
                UpdateProgress(1, totalChunks); // 更新进度
            }
            else {
                std::cerr << "Failed to upload chunk after retries." << std::endl;
            }
        }
    }
}


void QueueChunkForUpload(const std::vector<char>& chunk, int chunkNumber) {
    std::unique_lock<std::mutex> lock(queueMutex);
    uploadQueue.emplace(chunk, chunkNumber);
    conditionVariable.notify_one();
}



int main() {
    try {
        // 分割文件为分片
    

        HINTERNET hSession = WinHttpOpen(L"A WinHTTP Example Program/1.0", WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0);
        HINTERNET hConnect = WinHttpConnect(hSession, HOST_NAME, INTERNET_DEFAULT_HTTPS_PORT, 0); // 使用HTTPS端口

        // 创建线程
        std::vector<std::thread> threads;
        for (int i = 0; i < MAX_THREADS; ++i) {
            threads.emplace_back(UploadThread, hConnect);
        }

        // 分割文件为分片并加入队列
        auto chunks = SplitFileIntoChunks("path/to/your/largefile");
        for (size_t i = 0; i < chunks.size(); ++i) {
            QueueChunkForUpload(chunks[i], i);
        }

        // 添加空块以通知线程结束
        for (int i = 0; i < MAX_THREADS; ++i) {
            QueueChunkForUpload({}, -1);
        }

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

        // 清理
        WinHttpCloseHandle(hConnect);
        WinHttpCloseHandle(hSession);
    }
    catch (const std::exception& e) {
        std::cerr << e.what() << std::endl;
    }

    return 0;
}

? ??

curl版本

? ? ? ? 未尝试编译。

#include <iostream>
#include <fstream>
#include <vector>
#include <string>
#include <mutex>
#include <condition_variable>
#include <queue>
#include <thread>
#include <curl/curl.h>

const size_t CHUNK_SIZE = 10 * 1024 * 1024; // 分片大小10MB
const char* UPLOAD_URL = "https://www.example.com/upload";
const int MAX_THREADS = 4; // 最大线程数

std::mutex queueMutex;
std::condition_variable conditionVariable;
std::queue<std::pair<std::vector<char>, int>> uploadQueue;

std::mutex progressMutex;
size_t uploadedCount = 0;
size_t totalChunks = 0;

std::vector<std::vector<char>> SplitFileIntoChunks(const std::string& filePath) {
    std::ifstream file(filePath, std::ios::binary | std::ios::ate);
    if (!file.is_open()) {
        throw std::runtime_error("Unable to open file");
    }

    size_t fileSize = file.tellg();
    file.seekg(0, std::ios::beg);

    std::vector<std::vector<char>> chunks;
    size_t remainingSize = fileSize;

    while (remainingSize > 0) {
        size_t currentChunkSize = std::min(CHUNK_SIZE, remainingSize);
        std::vector<char> buffer(currentChunkSize);

        file.read(buffer.data(), currentChunkSize);
        chunks.push_back(std::move(buffer));
        remainingSize -= currentChunkSize;
    }

    file.close();
    return chunks;
}

size_t WriteCallback(void *ptr, size_t size, size_t nmemb, void *stream) {
    (void)ptr;  // 未使用
    (void)stream; // 未使用
    return size * nmemb;
}

bool UploadChunk(const std::vector<char>& chunkData, int chunkNumber) {
    CURL *curl;
    CURLcode res;
    curl = curl_easy_init();
    if (!curl) {
        std::cerr << "curl_easy_init() failed" << std::endl;
        return false;
    }

    struct curl_slist *headers = NULL;
    headers = curl_slist_append(headers, "Content-Type: application/octet-stream");
    std::string chunkHeader = "Chunk-Number: " + std::to_string(chunkNumber);
    headers = curl_slist_append(headers, chunkHeader.c_str());

    curl_easy_setopt(curl, CURLOPT_URL, UPLOAD_URL);
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_POST, 1L);
    curl_easy_setopt(curl, CURLOPT_POSTFIELDS, chunkData.data());
    curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, chunkData.size());
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback);

    res = curl_easy_perform(curl);
    if (res != CURLE_OK) {
        std::cerr << "curl_easy_perform() failed: " << curl_easy_strerror(res) << std::endl;
        curl_easy_cleanup(curl);
        curl_slist_free_all(headers);
        return false;
    }

    curl_easy_cleanup(curl);
    curl_slist_free_all(headers);
    return true;
}

void UpdateProgress(size_t chunkCount) {
    std::lock_guard<std::mutex> lock(progressMutex);
    uploadedCount += chunkCount;
    std::cout << "Progress: " << (uploadedCount * 100 / totalChunks) << "%" << std::endl;
}

void UploadThread() {
    while (true) {
        std::pair<std::vector<char>, int> chunkData;
        {
            std::unique_lock<std::mutex> lock(queueMutex);
            conditionVariable.wait(lock, [] { return !uploadQueue.empty(); });
            chunkData = uploadQueue.front();
            uploadQueue.pop();
        }

        if (chunkData.first.empty()) break; // 空块表示线程结束

        // 上传分片
        if (UploadChunk(chunkData.first, chunkData.second)) {
            UpdateProgress(1);
        } else {
            std::cerr << "Failed to upload chunk number " << chunkData.second << std::endl;
        }
    }
}

void QueueChunkForUpload(const std::vector<char>& chunk, int chunkNumber) {
    std::unique_lock<std::mutex> lock(queueMutex);
    uploadQueue.emplace(chunk, chunkNumber);
    conditionVariable.notify_one();
}

int main() {
    curl_global_init(CURL_GLOBAL_ALL);

    try {
        auto chunks = SplitFileIntoChunks("path/to/your/largefile");
        totalChunks = chunks.size();

        std::vector<std::thread> threads;
        for (int i = 0; i < MAX_THREADS; ++i) {
            threads.emplace_back(UploadThread);
        }

        for (size_t i = 0; i < chunks.size(); ++i) {
            QueueChunkForUpload(chunks[i], i);
        }

        for (int i = 0; i < MAX_THREADS; ++i) {
            QueueChunkForUpload({}, -1); // 添加空块以通知线程结束
        }

        for (auto& thread : threads) {
            thread.join();
        }
    }
    catch (const std::exception& e) {
        std::cerr << e.what() << std::endl;
    }

    curl_global_cleanup();
    return 0;
}

面向对象修改代码

#include <Windows.h>
#include <winhttp.h>
#include <wininet.h>
#include <fstream>
#include <vector>
#include <string>
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <queue>

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

const size_t CHUNK_SIZE = 10 * 1024 * 1024; // 分片大小设置为10MB
const LPCWSTR UPLOAD_URL = L"https://www.example.com/upload"; // 使用HTTPS
const LPCWSTR HOST_NAME = L"www.example.com";
const int MAX_THREADS = 4; // 最大线程数

class FileChunker {
public:
    static std::vector<std::vector<char>> SplitFileIntoChunks(const std::string& filePath) {
        std::ifstream file(filePath, std::ios::binary | std::ios::ate);
        if (!file.is_open()) {
            throw std::runtime_error("Unable to open file");
        }

        size_t fileSize = file.tellg();
        file.seekg(0, std::ios::beg);

        std::vector<std::vector<char>> chunks;
        size_t remainingSize = fileSize;

        while (remainingSize > 0) {
            size_t currentChunkSize = (CHUNK_SIZE < remainingSize) ? CHUNK_SIZE : remainingSize;
            std::vector<char> buffer(currentChunkSize);

            file.read(buffer.data(), currentChunkSize);
            chunks.push_back(std::move(buffer));
            remainingSize -= currentChunkSize;
        }

        file.close();
        return chunks;
    }
};

class WinHttpWrapper {
public:
    static HINTERNET InitializeHttpSession(const std::wstring& userAgent) {
        return WinHttpOpen(userAgent.c_str(), WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0);
    }

    static HINTERNET ConnectToServer(HINTERNET hSession, const std::wstring& serverName, int port) {
        return WinHttpConnect(hSession, serverName.c_str(), port, 0);
    }

    static bool CloseHandle(HINTERNET hHandle) {
        return WinHttpCloseHandle(hHandle) == TRUE;
    }
};

class ChunkUploader {
public:
    static bool UploadChunk(HINTERNET hConnect, const std::vector<char>& chunkData, int chunkNumber) {
        HINTERNET hRequest = WinHttpOpenRequest(hConnect, L"POST", UPLOAD_URL, NULL, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, 0);

        if (!hRequest) {
            std::cerr << "WinHttpOpenRequest failed: " << GetLastError() << std::endl;
            return false;
        }

        std::wstring headers = L"Content-Type: application/octet-stream\r\n";
        headers += L"Chunk-Number: " + std::to_wstring(chunkNumber) + L"\r\n";

        if (!WinHttpAddRequestHeaders(hRequest, headers.c_str(), -1, WINHTTP_ADDREQ_FLAG_ADD)) {
            std::cerr << "WinHttpAddRequestHeaders failed: " << GetLastError() << std::endl;
            WinHttpCloseHandle(hRequest);
            return false;
        }

        if (!WinHttpSendRequest(hRequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0, const_cast<char*>(chunkData.data()), chunkData.size(), chunkData.size(), 0)) {
            std::cerr << "WinHttpSendRequest failed: " << GetLastError() << std::endl;
            WinHttpCloseHandle(hRequest);
            return false;
        }

        if (!WinHttpReceiveResponse(hRequest, NULL)) {
            std::cerr << "WinHttpReceiveResponse failed: " << GetLastError() << std::endl;
            WinHttpCloseHandle(hRequest);
            return false;
        }

        DWORD dwSize = 0;
        DWORD dwDownloaded = 0;
        LPSTR pszOutBuffer;
        BOOL bResults = FALSE;

        do {
            dwSize = 0;
            if (!WinHttpQueryDataAvailable(hRequest, &dwSize)) {
                std::cerr << "WinHttpQueryDataAvailable failed: " << GetLastError() << std::endl;
                break;
            }

            pszOutBuffer = new char[dwSize + 1];
            if (!pszOutBuffer) {
                std::cerr << "Out of memory" << std::endl;
                dwSize = 0;
                break;
            }
            else {
                ZeroMemory(pszOutBuffer, dwSize + 1);

                if (!WinHttpReadData(hRequest, (LPVOID)pszOutBuffer, dwSize, &dwDownloaded)) {
                    std::cerr << "WinHttpReadData failed: " << GetLastError() << std::endl;
                    delete[] pszOutBuffer;
                    WinHttpCloseHandle(hRequest);
                    return false;
                }
                else {
                    std::cout << "Response: " << pszOutBuffer << std::endl;
                }

                delete[] pszOutBuffer;
            }
        } while (dwSize > 0);

        WinHttpCloseHandle(hRequest);
        return true; // Return true if the entire process completes without any errors
    }

    static bool UploadChunkWithRetry(HINTERNET hConnect, const std::vector<char>& chunkData, int chunkNumber) {
        const int maxRetries = 3;
        const int retryInterval = 2000; // 毫秒
        for (int attempt = 0; attempt < maxRetries; ++attempt) {
            if (attempt > 0) {
                std::cout << "Retrying (" << attempt << "/" << maxRetries << ")..." << std::endl;
                Sleep(retryInterval); // 等待一段时间再重试
            }

            if (UploadChunk(hConnect, chunkData, chunkNumber)) {
                return true; // 上传成功
            }
            // 根据错误码判断是否需要重试
            DWORD error = GetLastError();
            if (error != ERROR_INTERNET_TIMEOUT && error != ERROR_INTERNET_CONNECTION_RESET) {
                break; // 对于非暂时性错误,不进行重试
            }
        }
        return false; // 最终重试失败
    }
};

class UploadQueue {
private:
    std::mutex queueMutex;
    std::condition_variable conditionVariable;
    std::queue<std::pair<std::vector<char>, int>> uploadQueue;

public:
    void QueueChunkForUpload(const std::vector<char>& chunk, int chunkNumber) {
        std::unique_lock<std::mutex> lock(queueMutex);
        uploadQueue.emplace(chunk, chunkNumber);
        conditionVariable.notify_one();
    }

    std::pair<std::vector<char>, int> GetNextChunk() {
        std::unique_lock<std::mutex> lock(queueMutex);
        conditionVariable.wait(lock, [this] { return !uploadQueue.empty(); });
        auto chunk = uploadQueue.front();
        uploadQueue.pop();
        return chunk;
    }

    bool IsEmpty() {
        std::lock_guard<std::mutex> lock(queueMutex);
        return uploadQueue.empty();
    }
};

class UploadManager {
private:
    std::vector<std::thread> threads;
    UploadQueue uploadQueue;
    std::vector<bool> uploadedChunks;
    size_t totalChunks;
    HINTERNET hConnect;
    std::mutex progressMutex;
    size_t uploadedCount = 0;

    void UpdateProgress(size_t chunkCount) {
        std::lock_guard<std::mutex> lock(progressMutex);
        uploadedCount += chunkCount;
        std::cout << "Progress: " << (uploadedCount * 100 / totalChunks) << "%" << std::endl; // 显示进度
    }

    void UploadThread() {
        while (true) {
            auto chunkData = uploadQueue.GetNextChunk();

            // 如果数据为空,线程结束
            if (chunkData.first.empty()) break;

            // 上传逻辑...
            if (!uploadedChunks[chunkData.second]) {
                if (ChunkUploader::UploadChunkWithRetry(hConnect, chunkData.first, chunkData.second)) {
                    uploadedChunks[chunkData.second] = true;
                    UpdateProgress(1); // 更新进度
                }
                else {
                    std::cerr << "Failed to upload chunk after retries." << std::endl;
                }
            }
        }
    }

public:
    UploadManager(HINTERNET conn, const std::vector<std::vector<char>>& chunks) : hConnect(conn) {
        totalChunks = chunks.size();
        uploadedChunks.resize(totalChunks, false);

        for (const auto& chunk : chunks) {
            uploadQueue.QueueChunkForUpload(chunk, &chunk - &chunks[0]);
        }
    }

    void StartUpload() {
        for (int i = 0; i < MAX_THREADS; ++i) {
            threads.emplace_back(&UploadManager::UploadThread, this);
        }

        for (auto& thread : threads) {
            thread.join();
        }
    }
};


int main() {
    try {
        // 使用类和方法重构的主函数逻辑
        auto chunks = FileChunker::SplitFileIntoChunks("path/to/your/largefile");

        HINTERNET hSession = WinHttpWrapper::InitializeHttpSession(L"A WinHTTP Example Program/1.0");
        HINTERNET hConnect = WinHttpWrapper::ConnectToServer(hSession, HOST_NAME, INTERNET_DEFAULT_HTTPS_PORT);

        UploadManager manager(hConnect, chunks);
        manager.StartUpload();

        WinHttpWrapper::CloseHandle(hConnect);
        WinHttpWrapper::CloseHandle(hSession);
    }
    catch (const std::exception& e) {
        std::cerr << e.what() << std::endl;
    }

    return 0;
}

结论

  1. 分片上传的重要性

    • 在处理大文件上传时,分片上传是一种有效的策略。它不仅提高了数据传输的效率,还允许在出现网络问题时进行恢复,而不必从头开始。
  2. 多线程和性能优化

    • 通过使用多线程技术,我们可以并行上传多个文件分片,显著提高了上传速度。同时,需要注意线程间资源共享和同步的问题,确保线程安全。
  3. 安全性和数据校验

    • 在上传过程中,使用HTTPS确保数据传输的安全性,防止数据泄露或被篡改。此外,通过对文件分片进行数据校验(如SHA256哈希),可以进一步确保数据完整性。
  4. 断点续传和进度跟踪

    • 实现断点续传功能增加了上传过程的可靠性,允许在网络中断或其他问题发生后继续上传。进度跟踪为用户提供了可视化的上传进度,增强了用户体验。
  5. 挑战和未来方向

    • 虽然这种方法提高了大文件上传的效率和安全性,但它也带来了额外的复杂性,如错误处理、线程管理和性能优化。未来的工作可能集中在进一步优化性能、增强用户界面和提高系统的可扩展性和健壮性。

总的来说,分片上传是一个复杂但强大的技术,对于现代网络应用来说至关重要。它不仅提高了大文件处理的效率,还通过提供诸如断点续传、进度跟踪和安全数据传输等功能,极大地增强了用户体验和系统的可靠性。

????????以上内容为本人从chatgpt4.0? C++Programing......插件中获取!经本人整理而得,首发于CSDN 2023年12月17日。

文章来源:https://blog.csdn.net/weixin_44120785/article/details/135040356
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。