You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用C++ curl库异步多文件下载问题:curl_multi接口无法下载文件

问题分析与修复方案

核心错误:局部文件流提前销毁

add_transfer函数中声明的std::ofstream file_write是局部变量,函数执行完毕后会立即析构,对应的文件被关闭。而curl_multi是异步执行的,此时下载任务还未开始,后续curl调用write_data时,传入的userdata是已失效的指针,无法写入任何数据,最终生成空文件。

其他关键问题

  • URL拼接错误:代码中url + file_names[i]是将const char*与std::string做指针算术,不是字符串拼接,会生成错误的URL地址。正确做法是先将url转为std::string再拼接:std::string(url) + file_names[i]。
  • 未设置CURLOPT_PRIVATE却读取:download_files中调用curl_easy_getinfo(e, CURLINFO_PRIVATE, &url2),但从未通过curl_easy_setopt设置过该属性,会输出垃圾内存值。
  • download_file中res未初始化:函数内res仅声明未赋值,返回值是随机值,无法判断下载是否成功。
  • 缺少错误检查:curl_easy_perform、curl_multi_add_handle等调用未检查返回码,难以排查潜在错误。

修正后的代码示例

核心修改点

  1. 将文件流存储为easy handle的私有数据,确保生命周期与下载任务一致;
  2. 修复URL拼接逻辑;
  3. 添加错误检查;
  4. 在任务完成后正确关闭并释放文件流。
#include <iostream>
#include <vector>
#include <fstream>
#include <curl/curl.h>

static size_t write_data(const char *ptr, size_t size, size_t nmemb, std::ofstream *userdata);

static void add_transfer(CURLM *cm, int i, int *left, std::vector<std::string> &file_names, const std::string &file_path, const char *url);

bool download_file(const std::string &file_name, const std::string &file_path, const char *url) {
    CURL *curl = curl_easy_init();
    if (!curl) {
        std::cout << "curl init error!" << std::endl;
        return false;
    }

    std::ofstream file_write(file_path + file_name, std::ios::binary);
    if (!file_write.is_open()) {
        std::cout << "Failed to open file: " << file_path + file_name << std::endl;
        curl_easy_cleanup(curl);
        return false;
    }

    curl_easy_setopt(curl, CURLOPT_URL, (std::string(url) + file_name).c_str());
    curl_easy_setopt(curl, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_data);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &file_write);

    CURLcode res = curl_easy_perform(curl);
    curl_easy_cleanup(curl);
    file_write.close();

    if (res != CURLE_OK) {
        std::cout << "Download failed: " << curl_easy_strerror(res) << std::endl;
        return false;
    }
    return true;
}

void download_files(std::vector<std::string> file_names, const std::string &file_path, const char *url, int HANDLECOUNT) {
    curl_global_init(CURL_GLOBAL_ALL);
    CURLM *multi_handle = curl_multi_init();
    if (!multi_handle) {
        std::cout << "curl multi init error!" << std::endl;
        curl_global_cleanup();
        return;
    }

    curl_multi_setopt(multi_handle, CURLMOPT_MAXCONNECTS, 8);

    unsigned int transfers = 0;
    int left = 0;
    int msgs_left = 0;

    // 初始化第一批任务
    for (; transfers < 8 && transfers < HANDLECOUNT; transfers++) {
        add_transfer(multi_handle, transfers, &left, file_names, file_path, url);
    }

    do {
        int still_alive = 1;
        CURLMcode mc = curl_multi_perform(multi_handle, &still_alive);
        if (mc != CURLM_OK) {
            std::cout << "curl_multi_perform error: " << curl_multi_strerror(mc) << std::endl;
            break;
        }

        CURLMsg *msg;
        while ((msg = curl_multi_info_read(multi_handle, &msgs_left))) {
            if (msg->msg == CURLMSG_DONE) {
                CURL *e = msg->easy_handle;
                std::ofstream* file_write = nullptr;
                // 获取绑定的文件流指针
                curl_easy_getinfo(e, CURLINFO_PRIVATE, &file_write);

                std::string file_url;
                char* url_ptr = nullptr;
                curl_easy_getinfo(e, CURLINFO_EFFECTIVE_URL, &url_ptr);
                if (url_ptr) file_url = url_ptr;

                std::cout << "Download finished: " << file_url 
                          << " | Result: " << curl_easy_strerror(msg->data.result) << std::endl;

                // 关闭文件并释放
                if (file_write) {
                    file_write->close();
                    delete file_write;
                }

                curl_multi_remove_handle(multi_handle, e);
                curl_easy_cleanup(e);
                left--;

                // 添加新任务
                if (transfers < HANDLECOUNT) {
                    add_transfer(multi_handle, transfers++, &left, file_names, file_path, url);
                }
            }
        }

        if (left) {
            curl_multi_wait(multi_handle, NULL, 0, 1000, NULL);
        }
    } while (left);

    curl_multi_cleanup(multi_handle);
    curl_global_cleanup();
}

static size_t write_data(const char *ptr, size_t size, size_t nmemb, std::ofstream *userdata) {
    if (!userdata || !userdata->is_open()) {
        return 0;
    }
    size_t nbytes = size * nmemb;
    userdata->write(ptr, nbytes);
    return nbytes;
}

static void add_transfer(CURLM *cm, int i, int *left, std::vector<std::string> &file_names, const std::string &file_path, const char *url) {
    CURL *eh = curl_easy_init();
    if (!eh) {
        std::cout << "curl easy init error for task " << i << std::endl;
        return;
    }

    std::string full_file_path = file_path + file_names[i];
    std::string full_url = std::string(url) + file_names[i];

    // 动态分配文件流,确保生命周期与下载任务一致
    std::ofstream* file_write = new std::ofstream(full_file_path, std::ios::binary);
    if (!file_write->is_open()) {
        std::cout << "Failed to open file: " << full_file_path << std::endl;
        delete file_write;
        curl_easy_cleanup(eh);
        return;
    }

    std::cout << "Starting download: " << full_url << " -> " << full_file_path << std::endl;

    curl_easy_setopt(eh, CURLOPT_URL, full_url.c_str());
    curl_easy_setopt(eh, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2);
    curl_easy_setopt(eh, CURLOPT_WRITEFUNCTION, write_data);
    curl_easy_setopt(eh, CURLOPT_WRITEDATA, file_write);
    // 将文件流指针绑定到easy handle的私有数据
    curl_easy_setopt(eh, CURLOPT_PRIVATE, file_write);

    CURLMcode mc = curl_multi_add_handle(cm, eh);
    if (mc != CURLM_OK) {
        std::cout << "Failed to add handle: " << curl_multi_strerror(mc) << std::endl;
        file_write->close();
        delete file_write;
        curl_easy_cleanup(eh);
        return;
    }

    (*left)++;
}

额外说明

  • 动态分配的std::ofstream必须在下载任务完成后手动关闭并释放,避免内存泄漏;
  • 添加了完整的错误检查逻辑,方便排查初始化、添加任务、执行过程中的问题;
  • 修复了URL拼接错误,确保请求的是正确的资源地址。

内容的提问来源于stack exchange,提问作者Asgard

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.23 16:17:54