libcurl中CURLOPT_WRITEFUNCTION设置回调函数

本文介绍如何使用libcurl库正确地下载并保存整个文件。通过分析一个错误示例,指出在接收数据过程中不当关闭文件导致数据丢失的问题,并提供修正后的代码实现。
文档:

Let’s assume for a while that you want to receive data as the URL identifies a remote resource you want to get here. Since you write a sort of application that needs this transfer, I assume that you would like to get the data passed to you directly instead of simply getting it passed to stdout. So, you write your own function that matches this prototype:
size_t write_data(void *buffer, size_t size, size_t nmemb, void *userp);
You tell libcurl to pass all data to this function by issuing a function similar to this:
curl_easy_setopt(easyhandle, CURLOPT_WRITEFUNCTION, write_data);



文档只是简单的说
你可以通过设置curl_easy_setopt(easyhandle, CURLOPT_WRITEFUNCTION, write_data)这个函数来告诉libcurl,传递所有的数据到上面的size_t write_data(void *buffer, size_t size, size_t nmemb, void *userp);这个自己定义的满足这个格式的函数就行了。
BUT,我们来看一个例子:

#include <iostream>
#include <curl/curl.h>
using namespace std;

size_t receive_data(void *buffer, size_t size, size_t nmemb, FILE *file);
int main(){
    char url[] = "http://www.sina.com.cn";
    char path[] = "save_file.txt";
    FILE *file = fopen(path,"w");
    curl_global_init(CURL_GLOBAL_ALL);
    CURL *handle = curl_easy_init();
    curl_easy_setopt(handle, CURLOPT_URL, url);
    curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, receive_data);
    curl_easy_setopt(handle, CURLOPT_WRITEDATA, file);
    cout << curl_easy_perform(handle);


    curl_easy_cleanup(handle);
    curl_global_cleanup();

    return 0;
}

size_t receive_data(void *buffer, size_t size, size_t nmemb, FILE *file){
    size_t r_size = fwrite(buffer, size, nmemb, file);
    fclose(file);
    return r_size;
}

:)是不是觉得完全没有任何问题对不对,我下载了sina的html,然后保存到save_file.txt里。
BUT,当我打开save_file.txt发现里面只有不全的一段html,并没有全部在里面。为什么呢?
因为curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, receive_data);这个设置的回调函数的调用是在每次socket接收到数据之后,并不是socket接收了所有的数据,然后才调用设定的回调函数
当socket才接收到一部分数据的时候,就调用了回调函数。回调函数将接收到的不完全数据保存到文件里,然后关闭文件。然后socket又接收到了下一部分的数据,调用回调函数。但是此时文件已经被关闭了,程序出错,所以后续的内容不能够被保存到文件中。所以看到的文件也就是不完整的。

修改后的代码:
#include <iostream>
#include <curl/curl.h>
using namespace std;

size_t receive_data(void *buffer, size_t size, size_t nmemb, FILE *file);

int main() {
    char url[] = "http://www.sina.com.cn";
    char path[] = "save_file.txt";
    FILE *file = fopen(path, "w");
    curl_global_init(CURL_GLOBAL_ALL);
    CURL *handle = curl_easy_init();
    curl_easy_setopt(handle, CURLOPT_URL, url);
    curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, receive_data);
    curl_easy_setopt(handle, CURLOPT_WRITEDATA, file);
    cout << curl_easy_perform(handle);

    fclose(file);
    curl_easy_cleanup(handle);
    curl_global_cleanup();
    return 0;
}

size_t receive_data(void *buffer, size_t size, size_t nmemb, FILE *file) {
    size_t r_size = fwrite(buffer, size, nmemb, file);
    return r_size;
}
<think> 我们正在查找关于CURLOPT_WRITEFUNCTION的使用实例。根据引用,我们知道CURLOPT_WRITEFUNCTION用于设置一个回调函数,该函数在libcurl接收到数据时被调用。回调函数的原型为: size_t function(char *ptr, size_t size, size_t nmemb, void *userdata); 其中: - `ptr`:指向接收到的数据的指针。 - `size`:总是1(根据引用[4]的描述,但实际中size表示每个数据块的大小,通常为1,而nmemb表示块数,所以总数据大小为size*nmemb)。 - `nmemb`:接收到的数据块的数量。 - `userdata`:通过CURLOPT_WRITEDATA设置的用户数据。 回调函数应返回实际处理的数据字节数(通常为size*nmemb),如果返回的值与传入的大小不一致,libcurl会认为出现错误并中止传输。 引用[4]提供了一个例子,展示了如何将接收到的数据累积到一个缓冲区中。我们也可以将数据写入文件或进行其他处理。 下面是一个使用实例的步骤: 1. 定义一个回调函数,用于处理接收到的数据。 2. 使用`curl_easy_setopt`设置CURLOPT_WRITEFUNCTION,将这个回调函数传入。 3. 使用`curl_easy_setopt`设置CURLOPT_WRITEDATA,传入一个用户自定义数据的指针(例如,一个文件指针或一个结构体指针),这个指针将作为回调函数的userdata参数。 示例代码(C语言): ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <curl/curl.h> // 定义一个结构体来存储累积的数据 struct MemoryStruct { char *memory; size_t size; }; // 回调函数 static size_t WriteMemoryCallback(void *contents, size_t size, size_t nmemb, void *userp) { size_t realsize = size * nmemb; struct MemoryStruct *mem = (struct MemoryStruct *)userp; // 重新分配内存,包括额外的空字符空间 char *ptr = realloc(mem->memory, mem->size + realsize + 1); if(ptr == NULL) { // 内存分配失败 return 0; } mem->memory = ptr; memcpy(&(mem->memory[mem->size]), contents, realsize); mem->size += realsize; mem->memory[mem->size] = 0; // 以空字符结尾,方便作为字符串使用 return realsize; } int main(void) { CURL *curl; CURLcode res; struct MemoryStruct chunk; chunk.memory = malloc(1); // 初始化为空字符串 chunk.size = 0; curl_global_init(CURL_GLOBAL_DEFAULT); curl = curl_easy_init(); if(curl) { // 设置URL curl_easy_setopt(curl, CURLOPT_URL, "http://example.com"); // 设置回调函数 curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteMemoryCallback); // 传递我们的结构体指针给回调函数 curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&chunk); // 执行请求 res = curl_easy_perform(curl); // 检查错误 if(res != CURLE_OK) { fprintf(stderr, "curl_easy_perform() failed: %s\n", curl_easy_strerror(res)); } else { // 现在chunk.memory指向一个内存块,大小为chunk.size字节,并且以空字符结尾 printf("%lu bytes retrieved\n", (unsigned long)chunk.size); // 可以像使用字符串一样使用chunk.memory,但注意它可能包含二进制数据,所以如果需要处理二进制数据,不要依赖空字符结束。 // 这里我们打印前100个字符(或直到空字符) printf("%.*s\n", 100, chunk.memory); } // 清理 curl_easy_cleanup(curl); free(chunk.memory); } curl_global_cleanup(); return 0; } ``` 在这个例子中: - 我们定义了一个结构体`MemoryStruct`来保存累积的数据(一个动态分配的内存块和当前大小)。 - 回调函数`WriteMemoryCallback`将每次接收到的数据追加到结构体的内存中,并更新大小。 - 在`main`函数中,我们初始化这个结构体,然后设置回调函数和用户数据(结构体指针)。 - 执行请求后,我们可以使用累积的数据(这里打印了前100个字节)。 - 最后记得释放分配的内存。 注意事项: - 回调函数必须返回实际处理的数据大小(即realsize),否则libcurl会认为有错误。 - 我们为字符串添加了空字符结尾,但这仅适用于文本数据。如果是二进制数据,则不需要空字符结尾,并且要注意数据中可能包含0。 - 在程序结束时,要释放动态分配的内存。 这个例子展示了如何将整个响应内容存储在内存中。如果你需要写入文件,可以使用文件指针作为userdata,并在回调函数中使用fwrite。 文件写入的简单回调函数示例: ```c static size_t write_file(void *ptr, size_t size, size_t nmemb, void *stream) { return fwrite(ptr, size, nmemb, (FILE *)stream); } ``` 然后在设置时: ```c FILE *fp = fopen("output.txt", "wb"); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_file); curl_easy_setopt(curl, CURLOPT_WRITEDATA, fp); ``` 最后别忘了关闭文件。 希望这个实例对你有帮助! </think>下面我将详细解释`CURLOPT_WRITEFUNCTION`的使用方法并提供完整示例代码。这个选项用于设置libcurl接收数据时的回调函数,是处理网络响应的核心机制。 ### 回调函数原型 ```c size_t write_callback(char *ptr, size_t size, size_t nmemb, void *userdata); ``` 参数说明: - `ptr`:指向接收数据的缓冲区指针 - `size`:数据块大小(通常为1) - `nmemb`:数据块数量(实际数据长度 = size * nmemb) - `userdata`:通过`CURLOPT_WRITEDATA`设置的自定义指针 ### 完整使用示例 ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <curl/curl.h> // 定义存储响应的数据结构 struct ResponseData { char *data; size_t size; }; // 回调函数实现 size_t write_callback(void *ptr, size_t size, size_t nmemb, void *userdata) { size_t realsize = size * nmemb; struct ResponseData *res = (struct ResponseData *)userdata; // 扩展内存空间 char *new_ptr = realloc(res->data, res->size + realsize + 1); if (!new_ptr) return 0; // 内存分配失败 res->data = new_ptr; // 追加新数据 memcpy(&(res->data[res->size]), ptr, realsize); res->size += realsize; res->data[res->size] = '\0'; // 添加字符串终止符 return realsize; // 必须返回实际处理的数据量 } int main(void) { CURL *curl; CURLcode res; struct ResponseData response = {0}; curl_global_init(CURL_GLOBAL_DEFAULT); curl = curl_easy_init(); if(curl) { // 设置目标URL curl_easy_setopt(curl, CURLOPT_URL, "https://example.com"); // 设置回调函数 curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback); // 设置用户数据指针 curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&response); // 执行请求 res = curl_easy_perform(curl); // 检查结果 if(res != CURLE_OK) { fprintf(stderr, "请求失败: %s\n", curl_easy_strerror(res)); } else { printf("接收 %zu 字节数据:\n%.500s...\n", response.size, response.data); } // 清理资源 curl_easy_cleanup(curl); free(response.data); } curl_global_cleanup(); return 0; } ``` ### 关键点解析 1. **数据累积机制**: - 回调函数会被多次调用(每次接收数据片段) - 使用`realloc`动态扩展内存空间 - 通过`memcpy`拼接待处理数据[^4] 2. **返回值要求**: - 必须返回实际处理的数据量(`size * nmemb`) - 返回小于传入值会导致传输中止(CURLE_WRITE_ERROR) 3. **内存管理**: - 初始化时:`struct ResponseData response = {0};` - 每次回调后添加终止符:`res->data[res->size] = '\0'` - 结束后必须释放内存:`free(response.data);` 4. **二进制数据处理**: - 对于非文本数据,移除添加终止符的操作 - 使用`size`字段记录实际数据长度 - 保存为二进制文件时使用`fwrite(ptr, 1, realsize, file)` ### 文件写入示例 ```c size_t write_file(void *ptr, size_t size, size_t nmemb, FILE *stream) { return fwrite(ptr, size, nmemb, stream); } // 使用方式 FILE *fp = fopen("output.jpg", "wb"); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_file); curl_easy_setopt(curl, CURLOPT_WRITEDATA, fp); ``` ### 常见问题解决 1. **数据不完整**: - 确保返回`size * nmemb`值 - 检查网络错误码`curl_easy_strerror(res)` 2. **内存泄漏**: - 每次`realloc`后检查返回值 - 结束时必须`free`分配的内存 3. **多线程问题**: - 为每个线程创建独立的`ResponseData` - 使用互斥锁保护共享资源[^1]
评论 3
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值