curl使用小记(三)——获取远端数据到内存缓冲区
目录
1. 概述
我在博文《curl使用小记(二)——远程下载一张图片》中介绍了如何通过Curl获取远端的文件。不过在那个例子中,将获取远端数据与写入数据的步骤混杂到一起了。在多线程的场景下,这样做可能会造成读写冲突的问题。理论上,远端访问数据是先保存到内存中,在写出到文件中。而远端访问数据到内存可以看作是读操作,是不会读冲突的。所以一个很好的策略是,一次性将数据读取到内存Buf中,再写出到文件。
2. 实现
将《curl使用小记(二)——远程下载一张图片》中的代码改进一下,具体的代码实例如下:
#include <iostream> #include <curl/curl.h> using namespace std; //内存块结构体 struct MemoryStruct { char *memory; size_t size; MemoryStruct() { memory = (char *)malloc(1); size = 0; } ~MemoryStruct() { free(memory); memory = NULL; } }; //回调函数实现:一次请求可能多次调回调函数 size_t HttpPostWriteBack(void *contents, size_t size, size_t nmemb, void *userp) { size_t realsize = size * nmemb;//一次回调返回的数据量 struct MemoryStruct *mem = (struct MemoryStruct *)userp; char *ptr = (char *)realloc(mem->memory, mem->size + realsize); if (ptr == NULL) { printf("not enough memory (realloc returned NULL)\n"); return 0; } mem->memory = ptr; memcpy(&(mem->memory[mem->size]), contents, realsize); mem->size += realsize; return realsize;//必须返回真实的数据 } int main() { const char *netlink = "http://cn.bing.com/th?id=OHR.GrandsCausses_EN-CN3335882379_800x480.jpg"; const char *output = "D:/dst1.jpg"; curl_global_init(CURL_GLOBAL_ALL); //初始化全局资源 CURL *curl = curl_easy_init(); //初始化句柄 //需要的话,可以设置代理 //curl_easy_setopt(curl, CURLOPT_PROXY, "127.0.0.1:7890"); //访问网址 curl_easy_setopt(curl, CURLOPT_URL, netlink); //设置用户代理 curl_easy_setopt(curl, CURLOPT_USERAGENT, "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36"); //获取数据 MemoryStruct chunk; curl_easy_setopt(curl, CURLOPT_WRITEDATA, &chunk); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, HttpPostWriteBack); ////实现下载进度 //curl_easy_setopt(curl, CURLOPT_NOPROGRESS, false); //curl_easy_setopt(curl, CURLOPT_PROGRESSFUNCTION, progress_callback); //curl_easy_setopt(curl, CURLOPT_PROGRESSDATA, nullptr); //运行 curl_easy_perform(curl); curl_easy_cleanup(curl); //释放句柄 curl_global_cleanup(); //释放全局资源 //写出数据 FILE *fp = nullptr; if (fopen_s(&fp, output, "wb") != 0) { curl_easy_cleanup(curl); return 0; } fwrite(chunk.memory, chunk.size, 1, fp); fclose(fp); return 1; }
这段代码其中一个关键改进在于,通过自定义结构体MemoryStruct,实现了一个类似于动态数组的设计。由于远端访问文件的数据量在一开始并不能确定,所以需要先访问一部分,然后将容器扩容,再访问一部分,再扩容。这个申请内存的扩容操作是通过C的realloc()函数来实现的。这个结构体MemoryStruct还利用了C++的RAII机制做内存管理。
另外一个关键就是CURLOPT_WRITEDATA于CURLOPT_WRITEFUNCTION的配合使用了。CURLOPT_WRITEFUNCTION用来设置回调函数,CURLOPT_WRITEDATA用来设置回调函数的出参,这个其实是C的编程思维,万物皆指针,所有的操作都被抽象成同一个函数接口,其实不是同一个东西。
3. 参考
分类: Web开发技术