C++ Libcurl写访问违例问题:多URL并行下载代码报错修复
解决Libcurl多URL下载时的write access violation错误
错误根源
你的代码触发写访问违规的核心原因是:
- 在
add_transfer函数中,CURLOPT_WRITEDATA被错误赋值为&write_cb(写回调函数的地址),而非用于存储下载数据的std::string对象地址。当write_cb尝试将该地址强制转换为std::string*并调用append时,实际操作的是函数指针所在的非法内存区域,直接引发内存访问错误。 - 额外问题:重复调用
curl_multi_perform、传输完成后未清理句柄、未继续添加剩余URL任务。
修改后的完整代码
#include <errno.h> #include <stdlib.h> #include <string.h> #include <curl/curl.h> #include <string> #include <vector> static const char* urls[] = { "http://www.example.com", "http://www.example1.com", }; #define MAX_PARALLEL 10 /* 同时传输的数量 */ #define NUM_URLS sizeof(urls)/sizeof(char *) // 写回调:将下载的数据追加到std::string中 static size_t write_cb(void* ptr, size_t size, size_t nmemb, void* buffer) { std::string* str = static_cast<std::string*>(buffer); str->append(static_cast<char*>(ptr), size * nmemb); return size * nmemb; // 返回实际处理的字节数,原代码只返回nmemb是错误的 } // 添加一个新的传输任务 static void add_transfer(CURLM* cm, const char* url, std::string* data_buf, int* left) { CURL* eh = curl_easy_init(); if (!eh) return; curl_easy_setopt(eh, CURLOPT_WRITEFUNCTION, write_cb); curl_easy_setopt(eh, CURLOPT_URL, url); // 将数据缓冲区地址传给WRITEDATA,供回调使用 curl_easy_setopt(eh, CURLOPT_WRITEDATA, data_buf); curl_easy_setopt(eh, CURLOPT_VERBOSE, 1L); // 将数据缓冲区地址存在PRIVATE中,方便传输完成后访问 curl_easy_setopt(eh, CURLOPT_PRIVATE, data_buf); curl_multi_add_handle(cm, eh); (*left)++; } int main(void) { CURLM* cm; unsigned int next_url = 0; int left = 0; // 为每个URL分配独立的缓冲区存储下载数据 std::vector<std::string> download_buffers(NUM_URLS); curl_global_init(CURL_GLOBAL_ALL); cm = curl_multi_init(); // 设置最大同时连接数 curl_multi_setopt(cm, CURLMOPT_MAXCONNECTS, (long)MAX_PARALLEL); // 初始化第一批传输任务 for (; next_url < MAX_PARALLEL && next_url < NUM_URLS; next_url++) { add_transfer(cm, urls[next_url], &download_buffers[next_url], &left); } do { int still_alive = 1; CURLMcode mc = curl_multi_perform(cm, &still_alive); if (mc != CURLM_OK) { fprintf(stderr, "curl_multi_perform failed: %s\n", curl_multi_strerror(mc)); break; } // 等待活动、超时或无操作 mc = curl_multi_poll(cm, NULL, 0, 1000, NULL); if (mc != CURLM_OK) { fprintf(stderr, "curl_multi_poll failed: %s\n", curl_multi_strerror(mc)); break; } // 处理完成的传输 CURLMsg* msg; int queued; while ((msg = curl_multi_info_read(cm, &queued))) { if (msg->msg == CURLMSG_DONE) { CURL* eh = msg->easy_handle; std::string* data_buf = nullptr; // 获取传输对应的缓冲区 curl_easy_getinfo(eh, CURLINFO_PRIVATE, &data_buf); fprintf(stderr, "Transfer completed for URL: "); const char* url; curl_easy_getinfo(eh, CURLINFO_EFFECTIVE_URL, &url); fprintf(stderr, "%s\n", url); // 可以在这里使用data_buf中的下载数据,比如打印长度 if (data_buf) { fprintf(stderr, "Downloaded data length: %zu bytes\n", data_buf->size()); } // 清理完成的传输句柄 curl_multi_remove_handle(cm, eh); curl_easy_cleanup(eh); left--; // 如果还有未处理的URL,添加新的传输任务 if (next_url < NUM_URLS) { add_transfer(cm, urls[next_url], &download_buffers[next_url], &left); next_url++; } } } } while (left > 0); curl_multi_cleanup(cm); curl_global_cleanup(); return EXIT_SUCCESS; }
关键修改点说明
- 修复WRITEDATA赋值:将
CURLOPT_WRITEDATA设置为实际存储数据的std::string对象地址,确保回调函数能正确访问合法内存。 - 独立数据缓冲区:为每个URL分配单独的
std::string,避免多传输任务下的数据竞争,同时保证每个任务的数据独立存储。 - 修正回调返回值:原代码只返回
nmemb,正确的返回值应该是size * nmemb,否则Libcurl会认为数据未被正确接收,可能中断传输。 - 完善传输完成逻辑:传输结束后移除并清理CURL句柄,同时自动添加剩余的URL任务,直到所有URL处理完成。
- 移除重复调用:删除了原代码中重复的
curl_multi_perform调用,避免不必要的重复处理。
内容的提问来源于stack exchange,提问作者Volk Molk
相关产品推荐
相关产品推荐

