C++多线程下载工具代码及制作

一、核心功能设计

  1. 多线程分块下载:将文件划分为多个块,每个线程负责下载一个块

  2. 断点续传支持:通过 HTTP Range 头部实现中断后继续下载

  3. 进度显示:实时显示下载进度和速度

  4. 错误处理:处理网络错误、文件写入失败等异常情况

  5. 文件合并:下载完成后合并所有分块为完整文件

二、完整代码实现

#include <iostream>
#include <fstream>
#include <vector>
#include <thread>
#include <mutex>
#include <atomic>
#include <chrono>
#include <curl/curl.h>

using namespace std;

// 全局变量
mutex g_mutex;  // 用于线程同步
atomic<size_t> g_downloaded(0);  // 已下载字节数
size_t g_file_size = 0;  // 文件总大小

// 写入回调函数
size_t write_data(void* ptr, size_t size, size_t nmemb, void* data) {
    ofstream* ofs = static_cast<ofstream*>(data);
    size_t total_size = size * nmemb;
    ofs->write(static_cast<char*>(ptr), total_size);
    
    // 更新下载进度
    g_downloaded += total_size;
    
    return total_size;
}

// 下载单个分块
void download_chunk(const string& url, const string& temp_file, size_t start, size_t end) {
    CURL* curl;
    CURLcode res;
    ofstream ofs(temp_file, ios::binary);
    
    if (!ofs) {
        lock_guard<mutex> lock(g_mutex);
        cerr << "Failed to open file: " << temp_file << endl;
        return;
    }

    curl_global_init(CURL_GLOBAL_DEFAULT);
    curl = curl_easy_init();
    
    if (curl) {
        string range = to_string(start) + "-" + to_string(end);
        
        curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
        curl_easy_setopt(curl, CURLOPT_RANGE, range.c_str());
        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_data);
        curl_easy_setopt(curl, CURLOPT_WRITEDATA, &ofs);
        curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
        curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 1L);
        
        res = curl_easy_perform(curl);
        
        if (res != CURLE_OK) {
            lock_guard<mutex> lock(g_mutex);
            cerr << "Download failed: " << curl_easy_strerror(res) << endl;
        }
        
        curl_easy_cleanup(curl);
    }
    
    curl_global_cleanup();
}

// 获取文件大小
bool get_file_size(const string& url, size_t& file_size) {
    CURL* curl;
    curl_global_init(CURL_GLOBAL_DEFAULT);
    curl = curl_easy_init();
    
    if (curl) {
        curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
        curl_easy_setopt(curl, CURLOPT_NOBODY, 1L);  // HEAD请求
        curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
        
        CURLcode res = curl_easy_perform(curl);
        
        if (res == CURLE_OK) {
            curl_easy_getinfo(curl, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &file_size);
            curl_easy_cleanup(curl);
            curl_global_cleanup();
            return true;
        }
        
        curl_easy_cleanup(curl);
    }
    
    curl_global_cleanup();
    return false;
}

// 显示下载进度
void show_progress() {
    while (g_downloaded < g_file_size) {
        double progress = static_cast<double>(g_downloaded) / g_file_size * 100;
        double speed = g_downloaded / (1024.0 * 1024.0);  // MB
        
        cout << "\rProgress: " << fixed << setprecision(2) << progress << "% ";
        cout << "Downloaded: " << speed << " MB";
        cout.flush();
        
        this_thread::sleep_for(chrono::milliseconds(500));
    }
    
    cout << "\rDownload completed: 100.00%" << endl;
}

// 合并分块文件
void merge_files(const string& output_file, int thread_count) {
    ofstream ofs(output_file, ios::binary);
    
    for (int i = 0; i < thread_count; ++i) {
        string part_file = output_file + ".part" + to_string(i);
        ifstream ifs(part_file, ios::binary);
        
        if (ifs) {
            ofs << ifs.rdbuf();
            ifs.close();
            remove(part_file.c_str());
        }
    }
}

int main(int argc, char* argv[]) {
    if (argc < 3) {
        cout << "Usage: " << argv[0] << " <URL> <output_file> [thread_count]" << endl;
        return 1;
    }
    
    string url = argv[1];
    string output_file = argv[2];
    int thread_count = argc > 3 ? stoi(argv[3](@ref) : 4;
    
    // 获取文件大小
    if (!get_file_size(url, g_file_size)) {
        cerr << "Failed to get file size from URL" << endl;
        return 1;
    }
    
    cout << "File size: " << g_file_size / (1024 * 1024) << " MB" << endl;
    cout << "Downloading using " << thread_count << " threads..." << endl;
    
    // 计算分块大小
    size_t chunk_size = g_file_size / thread_count;
    vector<thread> threads;
    
    // 启动进度显示线程
    thread progress_thread(show_progress);
    
    // 启动下载线程
    for (int i = 0; i < thread_count; ++i) {
        size_t start = i * chunk_size;
        size_t end = (i == thread_count - 1) ? g_file_size - 1 : start + chunk_size - 1;
        string temp_file = output_file + ".part" + to_string(i);
        
        threads.emplace_back(download_chunk, url, temp_file, start, end);
    }
    
    // 等待所有线程完成
    for (auto& t : threads) {
        t.join();
    }
    
    // 等待进度线程结束
    progress_thread.join();
    
    // 合并文件
    merge_files(output_file, thread_count);
    
    cout << "File saved as: " << output_file << endl;
    
    return 0;
}

三、关键实现技术

  1. libcurl 库使用

    • 用于处理 HTTP 请求和文件下载

    • 支持 Range 头部实现分块下载

    • 提供进度回调功能

  2. 多线程管理

    • 使用 C++11 的 std::thread 创建和管理线程

    • 通过互斥锁 (std::mutex) 保护共享资源

    • 使用原子变量 (std::atomic) 实现无锁进度更新

  3. 断点续传实现

    • 通过 HTTP Range 头部指定下载范围

    • 每个线程独立下载指定范围的数据

    • 下载完成后合并所有分块

  4. 错误处理机制

    • 检查 curl_easy_perform 返回值

    • 处理文件打开和写入错误

    • 捕获网络超时和服务器错误

四、编译与使用说明

  1. 依赖安装

    sudo apt-get install libcurl4-openssl-dev  # Debian/Ubuntu
  2. 编译命令

    g++ -std=c++11 downloader.cpp -o downloader -lcurl -lpthread
  3. 使用示例

    ./downloader https://example.com/largefile.zip  output.zip 8

    参数说明:

    • 第一个参数:下载 URL

    • 第二个参数:输出文件名

    • 第三个参数(可选):线程数,默认为 4

五、性能优化建议

  1. 动态调整线程数

    • 根据文件大小和网络条件自动调整线程数

    • 避免过多线程导致性能下降

  2. 线程池实现

    • 使用线程池管理下载线程

    • 减少线程创建和销毁的开销

  3. 超时设置

    • 为 curl 设置连接超时和传输超时

    • 添加重试机制处理临时网络问题

  4. 内存优化

    • 使用缓冲区减少磁盘 I/O 次数

    • 限制同时打开的文件描述符数量

这个实现提供了完整的多线程下载功能,您可以根据需要进一步扩展,如添加 HTTPS 支持、实现 GUI 界面或增加下载队列管理等功能。


C++多线程下载工具代码及制作
https://uniomo.com/archives/c-duo-xian-cheng-xia-zai-gong-ju-dai-ma-ji-zhi-zuo
作者
雨落秋垣
发布于
2025年08月21日
许可协议