news 2026/4/30 0:35:29

curl使用读回调来分块上报文件

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
curl使用读回调来分块上报文件

之前的文章《curl编程实例-上传文件》,介绍过如何使用curl编程来上传文件,是通过指定文件路径的方式来上传,关键的特征如下:

constchar*file_path="./test.txt";// 要上传的本地文件// 打开待上传的文件(二进制模式)FILE*fp=fopen(file_path,"rb");curl_easy_setopt(curl,CURLOPT_READDATA,fp);curl_easy_perform(curl);

在有些情况下,可能需要对文件进行分段上传,这种情况,可以使用curl的读回调机制,通过多次的回调函数的调用,每次上传部分内容,最终上传整个文件。

1 读回调的编写

1.1 分段上传参数

需要先定义一个参数,用来在回调函数中,记录上传的数据信息:

  • const char *filename;:要上传的文件名
  • size_t totalSize;:要上传的总大小
  • size_t uploadedSize;:目前已上传的大小
typedefstruct{constchar*filename;size_ttotalSize;size_tuploadedSize;}UploadInfo_t;

1.2 读回调的定义

curl的读回调,会自动给出每次需要上传的数据大小,回调中需要做的,就是根据回调参数中指定的需要上传的大小,将文件的分段内容,写入指定的缓冲区,然后将已上传的大小,记录到前面定义的UploadInfo_t结构中。

size_tfile_read_cb(char*buf,size_tsize,size_tn,void*uploadInfo){if(!uploadInfo){return0;}UploadInfo_t*info=(UploadInfo_t*)uploadInfo;size_tbufferSize=size*n;// 此次需要上传的大小curl_off_tremaining=info->totalSize-info->uploadedSize;if(remaining<=0){return0;// 已读完,结束传输}if(bufferSize>(size_t)remaining){bufferSize=(size_t)remaining;}size_tbytesRead=custom_read_file(info->filename,info->uploadedSize,bufferSize,buf);if(bytesRead==0){return0;}info->uploadedSize+=bytesRead;floatprogress=(float)info->uploadedSize/info->totalSize*100;printf("[%s] read:%zu bytes, progress:%.1f%%\n",__func__,bytesRead,progress);returnbytesRead;}

1.3 分段读取举例

分段读取的实现形式有很多,比如通过自定义的接口,从自定义的内存缓冲区,或其它自定义的方式,进行读取。

这里只是演示分段读取的过程,就还以fopen读文件的方式举例,再通过fseeko进行偏移,从而实现从文件的指定位置读取指定长度的内容。

size_tcustom_read_file(constchar*file_path,size_toffset,size_tread_len,char*buffer){size_tactual_read=0;// 这里只是使用fopen举例,实际可以是任何形式的文件读取FILE*fp=fopen(file_path,"rb");if(!fp){printf("fopen:%s err\n",file_path);return0;}// 定位到指定偏移量:SEEK_SET 表示从文件开头计算偏移intseek_ret=fseeko(fp,offset,SEEK_SET);if(seek_ret!=0){printf("fseeko failed: offset=%lld\n",(longlong)offset);gotoEND;}// 读取指定长度的数据到缓冲区actual_read=fread(buffer,1,read_len,fp);if(actual_read!=read_len){// 读取不完整:可能是到文件末尾,或读取错误if(feof(fp)){printf("Warning: only read %zu bytes(expect:%zu)\n",actual_read,read_len);}elseif(ferror(fp)){printf("fread failed");}}END:fclose(fp);returnactual_read;}

2 完整代码

完整代码如下,是在之前那篇《curl编程实例-上传文件》的基础上进行修改的。

// gcc file_upload2.c -o file_upload2 -lcurl#include<stdio.h>#include<stdlib.h>#include<sys/stat.h>#include<curl/curl.h>#include<string.h>#include<libgen.h>// 用于提取原始文件名// 进度回调函数staticintupload_progress(void*p,curl_off_tdltotal,curl_off_tdlnow,curl_off_tultotal,curl_off_tulnow){if(ultotal>0){// 计算进度百分比,限制最大值为100%(实际的上传数据包含了HTTP头部等数据)floatprogress=(ulnow*100.0)/ultotal;if(progress>100.0)progress=100.0;printf("progress: %lld/%lld (%.2f%%)\n",(longlong)ulnow,(longlong)ultotal,progress);}return0;}// 提取文件路径中的原始文件名(兼容绝对/相对路径)char*get_original_filename(constchar*file_path){if(!file_path){returnstrdup("unknown_file.dat");}char*path_copy=strdup(file_path);char*filename=basename(path_copy);char*result=strdup(filename);free(path_copy);returnresult;}// 获取文件实际大小(字节)curl_off_tget_file_size(constchar*file_path){if(!file_path){return-1;}structstatst;if(stat(file_path,&st)==-1){printf("stat %s failed\n",file_path);return-1;}return(curl_off_t)st.st_size;}typedefstruct{constchar*filename;size_ttotalSize;size_tuploadedSize;}UploadInfo_t;size_tcustom_read_file(constchar*file_path,size_toffset,size_tread_len,char*buffer){size_tactual_read=0;// 这里只是使用fopen举例,实际可以是任何形式的文件读取FILE*fp=fopen(file_path,"rb");if(!fp){printf("fopen:%s err\n",file_path);return0;}// 定位到指定偏移量:SEEK_SET 表示从文件开头计算偏移intseek_ret=fseeko(fp,offset,SEEK_SET);if(seek_ret!=0){printf("fseeko failed: offset=%lld\n",(longlong)offset);gotoEND;}// 读取指定长度的数据到缓冲区actual_read=fread(buffer,1,read_len,fp);if(actual_read!=read_len){// 读取不完整:可能是到文件末尾,或读取错误if(feof(fp)){printf("Warning: only read %zu bytes(expect:%zu)\n",actual_read,read_len);}elseif(ferror(fp)){printf("fread failed");}}END:fclose(fp);returnactual_read;}size_tfile_read_cb(char*buf,size_tsize,size_tn,void*uploadInfo){if(!uploadInfo){return0;}UploadInfo_t*info=(UploadInfo_t*)uploadInfo;size_tbufferSize=size*n;// 此次需要上传的大小curl_off_tremaining=info->totalSize-info->uploadedSize;if(remaining<=0){return0;// 已读完,结束传输}if(bufferSize>(size_t)remaining){bufferSize=(size_t)remaining;}size_tbytesRead=custom_read_file(info->filename,info->uploadedSize,bufferSize,buf);if(bytesRead==0){return0;}info->uploadedSize+=bytesRead;floatprogress=(float)info->uploadedSize/info->totalSize*100;printf("[%s] read:%zu bytes, progress:%.1f%%\n",__func__,bytesRead,progress);returnbytesRead;}intmain(intargc,char*argv[]){CURL*curl=NULL;CURLcode res;FILE*fp=NULL;constchar*upload_url="http://192.168.5.104:8080/upload";// 文件服务器的地址constchar*file_path="./test.jpg";// 要上传的本地文件structcurl_slist*headers=NULL;// 自定义请求头// 文件上传的信息UploadInfo_t uploadInfo={0};uploadInfo.filename=file_path;// 获取文件大小size_tfile_size=get_file_size(file_path);if(file_size<0){printf("%s file_size:%zu err\n",file_path,file_size);gotocleanup;}printf("%s file_size: %zu\n",file_path,file_size);uploadInfo.totalSize=file_size;// 初始化libcurlcurl_global_init(CURL_GLOBAL_ALL);curl=curl_easy_init();if(!curl){printf("curl_easy_init, err\n");gotocleanup;}// 构建自定义请求头(传递原始文件名)char*original_filename=get_original_filename(file_path);charheader_buf[256];snprintf(header_buf,sizeof(header_buf),"X-File-Name: %s",original_filename);headers=curl_slist_append(headers,header_buf);// 禁用Expect头,解决POST上传阻塞问题headers=curl_slist_append(headers,"Expect:");// 设置上传URLcurl_easy_setopt(curl,CURLOPT_URL,upload_url);// 设置自定义请求头curl_easy_setopt(curl,CURLOPT_HTTPHEADER,headers);// 设置文件上传curl_easy_setopt(curl,CURLOPT_UPLOAD,1L);curl_easy_setopt(curl,CURLOPT_INFILESIZE_LARGE,(curl_off_t)file_size);curl_easy_setopt(curl,CURLOPT_READFUNCTION,file_read_cb);curl_easy_setopt(curl,CURLOPT_READDATA,&uploadInfo);// 执行上传printf("start uoload file: %s\n",file_path);res=curl_easy_perform(curl);if(res!=CURLE_OK){printf("upload fail: %s\n",curl_easy_strerror(res));}else{printf("upload success\n");}// 资源清理(统一出口)cleanup:if(original_filename)free(original_filename);if(headers)curl_slist_free_all(headers);if(curl)curl_easy_cleanup(curl);curl_global_cleanup();returnres==CURLE_OK?0:1;}

3 运行结果

测试环境:

  • 在windows电脑上启动一个文件服务器,可参考之前的文章《curl编程实例-上传文件》
  • 在ubuntu虚拟机上启动文件上传程序

可以看到,文件通过回调的方式,多次分段上传,最终的windows电脑的文件服务器的指定目录,可以看到上传的文件

4 总结

本篇介绍了如何使用curl的C语言编程,读回调的方式,实现文件的分段上传,并通过代码实例,验证上传的结果。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/25 14:10:09

原神帧率解锁终极指南:3个技巧实现丝滑游戏体验

原神帧率解锁终极指南&#xff1a;3个技巧实现丝滑游戏体验 【免费下载链接】genshin-fps-unlock unlocks the 60 fps cap 项目地址: https://gitcode.com/gh_mirrors/ge/genshin-fps-unlock 还在为原神60fps的帧率限制而烦恼吗&#xff1f;想要体验更流畅的战斗和更精美…

作者头像 李华
网站建设 2026/4/29 11:28:39

视频字幕提取终极指南:从入门到精通的全流程教程

视频字幕提取终极指南&#xff1a;从入门到精通的全流程教程 【免费下载链接】video-subtitle-extractor 视频硬字幕提取&#xff0c;生成srt文件。无需申请第三方API&#xff0c;本地实现文本识别。基于深度学习的视频字幕提取框架&#xff0c;包含字幕区域检测、字幕内容提取…

作者头像 李华
网站建设 2026/4/29 2:10:26

原神帧率解锁终极教程:三步突破60帧限制,畅享丝滑游戏体验

原神帧率解锁终极教程&#xff1a;三步突破60帧限制&#xff0c;畅享丝滑游戏体验 【免费下载链接】genshin-fps-unlock unlocks the 60 fps cap 项目地址: https://gitcode.com/gh_mirrors/ge/genshin-fps-unlock 想要在原神中体验极致流畅的战斗画面吗&#xff1f;这款…

作者头像 李华
网站建设 2026/4/25 23:20:54

AI办公效率提升:AutoGen Studio+Qwen3-4B实战案例

AI办公效率提升&#xff1a;AutoGen StudioQwen3-4B实战案例 1. 背景与目标 随着大模型技术的快速发展&#xff0c;AI智能体&#xff08;Agent&#xff09;正逐步从研究走向实际应用。传统单一大模型调用已难以满足复杂任务处理需求&#xff0c;而多代理协同系统则展现出强大…

作者头像 李华
网站建设 2026/4/28 17:23:49

Fun-ASR-MLT-Nano-2512优化指南:模型缓存策略优化

Fun-ASR-MLT-Nano-2512优化指南&#xff1a;模型缓存策略优化 1. 引言 1.1 技术背景与问题提出 Fun-ASR-MLT-Nano-2512 是阿里通义实验室推出的多语言语音识别大模型&#xff0c;支持 31 种语言的高精度识别&#xff0c;在跨语言语音处理场景中展现出强大的泛化能力。该模型…

作者头像 李华
网站建设 2026/4/18 21:19:10

原神帧率解锁终极指南:三步告别60fps限制!

原神帧率解锁终极指南&#xff1a;三步告别60fps限制&#xff01; 【免费下载链接】genshin-fps-unlock unlocks the 60 fps cap 项目地址: https://gitcode.com/gh_mirrors/ge/genshin-fps-unlock 还在为原神60fps的帧率限制而烦恼吗&#xff1f;这款专为《原神》设计的…

作者头像 李华