使用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等调用未检查返回码,难以排查潜在错误。
修正后的代码示例
核心修改点
- 将文件流存储为easy handle的私有数据,确保生命周期与下载任务一致;
- 修复URL拼接逻辑;
- 添加错误检查;
- 在任务完成后正确关闭并释放文件流。
#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
相关产品推荐
相关产品推荐

