libcurl 是一个功能强大的跨平台网络传输库,支持多种协议。

关注“码农爱学习”,设置“星标公众号”

本篇来介绍libcul的C语言编程,实现一个文件下载的功能。
1 curl基础介绍
1.1 核心数据结构
1.1.1 CURL句柄
CURL是libcurl 的核心句柄,每个请求对应一个 CURL 实例,是配置请求参数、执行操作的主要载体。
其特性包括:
独立性:每个句柄都是独立的,配置互不影响 可复用性:一个句柄在完成一次请求后,可通过重新设置选项再次使用,无需重复创建和销毁 状态存储:句柄内部存储了请求的所有状态信息,直到被释放
1.1.2 CURLcode返回值
CURLcode是函数返回值类型,用于判断操作是否成功。
成功状态为:CURLE_OK
所有的返回值枚举如下:
typedefenum { CURLE_OK = 0, /* 操作成功 */ CURLE_UNSUPPORTED_PROTOCOL, /* 不支持的协议 */ CURLE_FAILED_INIT, /* 初始化失败(如 curl_global_init 调用失败) */ CURLE_URL_MALFORMAT, /* URL 格式错误 */ CURLE_URL_MALFORMAT_USER, /* URL 中的用户名/密码格式错误 */ CURLE_COULDNT_RESOLVE_PROXY, /* 无法解析代理服务器 */ CURLE_COULDNT_RESOLVE_HOST, /* 无法解析主机名(DNS 失败) */ CURLE_COULDNT_CONNECT, /* 无法连接到服务器 */ CURLE_WEIRD_SERVER_REPLY, /* 服务器返回异常响应 */ CURLE_REMOTE_ACCESS_DENIED, /* 远程访问被拒绝(如权限不足) */ CURLE_FTP_ACCEPT_FAILED, /* FTP 接受连接失败 */ CURLE_FTP_WEIRD_PASS_REPLY, /* FTP 密码响应异常 */ CURLE_FTP_ACCEPT_TIMEOUT, /* FTP 接受超时 */ CURLE_FTP_WEIRD_PASV_REPLY, /* FTP PASV 命令响应异常 */ CURLE_FTP_WEIRD_227_FORMAT, /* FTP 227 响应格式错误 */ CURLE_FTP_CANT_GET_HOST, /* FTP 无法获取主机信息 */ CURLE_HTTP2, /* HTTP/2 相关错误 */ CURLE_FTP_COULDNT_SET_TYPE, /* FTP 无法设置传输类型(ASCII/BINARY) */ CURLE_PARTIAL_FILE, /* 只接收到部分文件(未完成全量下载) */ CURLE_FTP_COULDNT_RETR_FILE, /* FTP 无法检索文件 */ CURLE_OBSOLETE4, /* 已废弃(历史版本使用) */ CURLE_QUOTE_ERROR, /* FTP QUOTE 命令执行失败 */ CURLE_HTTP_RETURNED_ERROR, /* HTTP 服务器返回错误状态码(如 4xx、5xx) */ CURLE_WRITE_ERROR, /* 写入数据失败(如本地文件写入失败) */ CURLE_OBSOLETE9, /* 已废弃 */ CURLE_UPLOAD_FAILED, /* 上传失败 */ CURLE_READ_ERROR, /* 读取数据失败(如本地文件读取失败) */ CURLE_OUT_OF_MEMORY, /* 内存不足 */ CURLE_OPERATION_TIMEDOUT, /* 操作超时 */ CURLE_OBSOLETE14, /* 已废弃 */ CURLE_FTP_PORT_FAILED, /* FTP PORT 命令执行失败 */ CURLE_FTP_COULDNT_USE_REST, /* FTP REST 命令不被支持 */ CURLE_OBSOLETE17, /* 已废弃 */ CURLE_RANGE_ERROR, /* 范围请求错误(服务器不支持 Range 或范围无效) */ CURLE_HTTP_POST_ERROR, /* HTTP POST 数据传输错误 */ CURLE_SSL_CONNECT_ERROR, /* SSL/TLS 连接失败 */ CURLE_BAD_DOWNLOAD_RESUME, /* 断点续传失败(如文件不存在或偏移量无效) */ CURLE_FILE_COULDNT_READ_FILE, /* 无法读取本地文件 */ CURLE_LDAP_CANNOT_BIND, /* LDAP 绑定失败 */ CURLE_LDAP_SEARCH_FAILED, /* LDAP 搜索失败 */ CURLE_OBSOLETE24, /* 已废弃 */ CURLE_FUNCTION_NOT_FOUND, /* 未找到指定的回调函数 */ CURLE_ABORTED_BY_CALLBACK, /* 被回调函数中止(如进度回调返回非零值) */ CURLE_BAD_FUNCTION_ARGUMENT, /* 回调函数参数错误 */ CURLE_OBSOLETE28, /* 已废弃 */ CURLE_OBSOLETE29, /* 已废弃 */ CURLE_PARTIAL_FILE_FROM_cache, /* 从缓存获取到部分文件 */ CURLE_OBSOLETE31, /* 已废弃 */ CURLE_SSL_ENGINE_NOTFOUND, /* 未找到 SSL 引擎 */ CURLE_SSL_ENGINE_SETFAILED, /* SSL 引擎初始化失败 */ CURLE_SEND_ERROR, /* 发送数据失败(网络发送错误) */ CURLE_RECV_ERROR, /* 接收数据失败(网络接收错误) */ CURLE_OBSOLETE36, /* 已废弃 */ CURLE_SSL_CERTPROBLEM, /* SSL 证书问题(如无效、过期) */ CURLE_SSL_CIPHER, /* 无法协商 SSL 加密套件 */ CURLE_SSL_CACERT, /* 无法验证 SSL CA 证书 */ CURLE_BAD_CONTENT_ENCODING, /* 内容编码错误(如服务器返回无效的 gzip 数据) */ CURLE_LDAP_INVALID_URL, /* LDAP URL 无效 */ CURLE_FILESIZE_EXCEEDED, /* 文件大小超出限制 */ CURLE_USE_SSL_FAILED, /* 启用 SSL 失败 */ CURLE_SEND_FAIL_REWIND, /* 发送数据前重绕(rewind)失败 */ CURLE_SSL_ENGINE_INITFAILED, /* SSL 引擎初始化失败 */ CURLE_LOGIN_DENIED, /* 登录被拒绝(如用户名/密码错误) */ CURLE_TFTP_NOTFOUND, /* TFTP 文件未找到 */ CURLE_TFTP_PERM, /* TFTP 权限错误 */ CURLE_REMOTE_DISK_FULL, /* 远程磁盘满 */ CURLE_TFTP_ILLEGAL, /* TFTP 非法操作 */ CURLE_TFTP_UNKNOWNID, /* TFTP 未知传输 ID */ CURLE_REMOTE_FILE_EXISTS, /* 远程文件已存在(不允许覆盖) */ CURLE_TFTP_NOSUCHUSER, /* TFTP 无此用户 */ CURLE_CONV_FAILED, /* 字符编码转换失败 */ CURLE_CONV_REQD, /* 需要字符编码转换但未启用 */ CURLE_SSL_CACERT_BADFILE, /* SSL CA 证书文件不存在或无法读取 */ CURLE_REMOTE_FILE_NOT_FOUND, /* 远程文件未找到 */ CURLE_SSH, /* SSH 相关错误 */ CURLE_SSL_SHUTDOWN_FAILED, /* SSL 关闭失败 */ CURLE_AGAIN, /* 操作需要重试(非错误,用于异步操作) */ CURLE_SSL_CRL_BADFILE, /* CRL 文件错误 */ CURLE_SSL_ISSUER_ERROR, /* SSL issuer 证书验证失败 */ CURLE_FTP_PRET_FAILED, /* FTP PRET 命令失败 */ CURLE_RTSP_CSEQ_ERROR, /* RTSP CSEQ 不匹配 */ CURLE_RTSP_SESSION_ERROR, /* RTSP 会话错误 */ CURLE_FTP_BAD_FILE_LIST, /* FTP 文件列表格式错误 */ CURLE_CHUNK_FAILED, /* HTTP 分块传输编码错误 */ CURLE_NO_CONNECTION_AVAILABLE, /* 无可用连接(连接池耗尽) */ CURLE_SSL_PINNEDPUBKEYNOTMATCH, /* SSL 固定公钥不匹配 */ CURLE_SSL_INVALIDCERTSTATUS, /* SSL 证书状态无效 */ CURLE_HTTP2_STREAM, /* HTTP/2 流错误 */ CURLE_RECURSIVE_API_CALL, /* 递归调用 libcurl API */ CURLE_AUTH_ERROR, /* 认证错误 */ CURLE_HTTP3, /* HTTP/3 相关错误 */ CURLE_QUIC_CONNECT_ERROR, /* QUIC 连接错误 */ CURLE_PROXY, /* 代理相关错误 */ CURLE_SSL_CLIENTCERT, /* 客户端证书错误 */ CURLE_UNRECOVERABLE_POLL, /* 无法恢复的轮询错误 */ CURLE_LAST /* 标记枚举结束(无实际意义) */} CURLcode;1.2 初始化与清理
1.2.1 curl_global_init
初始化 libcurl 全局环境,必须在所有其他 libcurl 函数之前调用
CURLcode curl_global_init(long flags);参数: flags通常使用CURL_GLOBAL_DEFAULT(默认初始化)返回值: CURLcode类型,成功返回CURLE_OK
curl_global_init 通常需要手动调用,如果不调用,在第一次调用curl_easy_init时,libcurl 会隐式调用
curl_global_init(CURL_GLOBAL_DEFAULT),但这种行为不可靠,可能存在以下问题:
无法指定初始化选项(只能使用默认配置) 无法捕获 curl_global_init的返回值(若初始化失败,难以排查原因)可能导致多线程环境下的竞争条件(非线程安全) 部分平台或 libcurl 编译选项可能禁用此功能
如果代码逻辑较简单,或刚开始学习curl,简化逻辑可先只调用curl_easy_init。
1.2.2 curl_easy_init
创建一个 CURL 句柄,用于配置单次请求
CURL *curl_easy_init(void);返回值:成功返回 CURL*句柄,失败返回NULL
1.2.3 curl_easy_cleanup
释放 CURL 句柄资源
voidcurl_easy_cleanup(CURL *curl);1.2.4 curl_global_cleanup
清理 libcurl 全局环境,程序结束前调用
voidcurl_global_cleanup(void);1.3 下载核心配置 API
1.3.1 curl_easy_setopt
设置 CURL 句柄的各种选项(核心函数,几乎所有配置都通过它完成)
函数原型:
CURLcode curl_easy_setopt(CURL *curl, CURLoption option, parameter);参数:
CURL *curl:指向 CURL 句柄的指针,由
curl_easy_init()创建,用于标识一个 curl 会话CURLoption option:枚举类型的选项参数,指定要设置的具体配置(如 URL、超时时间、回调函数等),例如:
CURLOPT_URL(设置请求的 URL)CURLOPT_WRITEFUNCTION(设置数据接收回调函数)parameter:选项对应的参数值,类型根据option不同而变化:
可能是字符串(如 URL 地址)、整数(如超时时间)、函数指针(如回调函数)等。 若选项不需要参数,此值可设为 0或NULL(根据具体选项要求)
1.3.2 与文件下载相关的一些选项
CURLOPT_URL:设置下载的 URL(必填)示例:
curl_easy_setopt(curl, CURLOPT_URL, "https://example.com/file.zip");CURLOPT_WRITEFUNCTION:设置回调函数,用于处理下载的文件数据回调函数原型:
size_t (*curl_write_callback)(void *ptr, size_t size, size_t nmemb, void *userp);ptr:下载到的数据缓冲区;size:每个数据块的大小;nmemb:数据块数量;userp:自定义参数(通常是文件指针等);返回值:实际处理的字节数(需返回 size * nmemb,否则可能中断下载)。CURLOPT_WRITEDATA:设置传递给CURLOPT_WRITEFUNCTION回调的自定义参数(如文件指针)示例:
curl_easy_setopt(curl, CURLOPT_WRITEDATA, file_ptr);CURLOPT_FOLLOWLOCATION:设置为1L时,自动跟随 HTTP 重定向(如 301/302)CURLOPT_SSL_VERIFYPEER:设置为0L时,忽略 SSL 证书验证(仅测试用,生产环境不推荐)CURLOPT_PROGRESSFUNCTION:设置进度回调函数,用于跟踪下载进度。回调原型:
int (*progress_callback)(void *clientp, double dltotal, double dlnow, double ultotal, double ulnow);dltotal:总下载大小dlnow:已下载大小CURLOPT_NOPROGRESS:设置为0L时,启用进度回调(需配合CURLOPT_PROGRESSFUNCTION使用)
1.4 执行下载与获取信息
1.4.1 curl_easy_perform
执行配置好的请求(开始数据传输)
CURLcode curl_easy_perform(CURL *curl);参数:指向已配置好的 CURL 句柄的指针 返回值: CURLcode类型,CURLE_OK表示下载成功
1.4.2 curl_easy_getinfo
获取下载相关信息(需在 curl_easy_perform 执行后调用)
CURLcode curl_easy_getinfo(CURL *curl, CURLINFO info, ...);参数:
CURL *curl:指向已执行过请求的 CURL 句柄的指针CURLINFO info:枚举类型的参数,指定要获取的信息类型,例如:CURLINFO_RESPONSE_CODE(HTTP 响应码)CURLINFO_SIZE_DOWNLOAD(下载总字节数)...:可变参数,指向用于存储结果的变量指针,类型根据info不同而变化(如整数指针、双精度指针、字符串指针等)
2 文件下载基础示例
2.1 curl下载主逻辑
主要调用的接口如下:
curl_easy_init:初始化curl句柄 curl_easy_setopt:设置选项(文件下载地址,下载的回调函数) curl_easy_perform:执行文件下载
voidhttp_file_download(constchar *url, constchar *name){printf("[%s] in\n", __func__);int fd = open(name, O_CREAT | O_RDWR | O_TRUNC, S_IRUSR | S_IWUSR);if (-1 == fd) {printf("[%s] create file:%s failed\n", __func__, name);return; }printf("[%s] create file:%s ok\n", __func__, name);printf("[%s] curl init\n", __func__); CURL *curl = curl_easy_init();printf("[%s] curl setopt, url:%s\n", __func__, url); curl_easy_setopt(curl, CURLOPT_URL, url); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_file_cb); curl_easy_setopt(curl, CURLOPT_WRITEDATA, &fd); printf("[%s] curl performt\n", __func__); curl_easy_perform(curl);printf("[%s] close file\n", __func__); close(fd);printf("[%s] out\n", __func__); }2.2 下载数据的回调
下载数据的回调,需要根据回调函数的原型,自己实现具体的内容,这里就是将接收的数据通过write方法写入自己创建的文件。
文件的创建是在上面的http_file_download中通过open方法创建一个文件,并通过curl_easy_setopt的CURLOPT_WRITEDATA选项将文件
的fd句柄传给回调函数。
size_twrite_file_cb(char *ptr, size_t size, size_t nmemb, void *userdata){staticint i = 0;size_t this_size = size * nmemb;printf("[%s] <%d> total size:%zu\n", __func__, ++i, this_size);int fd = *((int *)userdata); write(fd, ptr, this_size);return this_size;}2.3 主函数
要测试文件下载,需要先找一个文件下载连接,可以先通过浏览器下载一个文件,然后在下载记录中,复制其下载链接:

文件下载的测试主函数中,就是调用上面的curl函数接口,传入下载链接:
// gcc download_file.c -o download_file -l curl#include<stdio.h>#include<curl/curl.h>#include<unistd.h>#include<sys/types.h>#include<sys/stat.h>#include<fcntl.h>#include<time.h>constchar *file_url = "https://vscode.download.prss.microsoft.com/dbazure/download/stable/488a1f239235055e34e673291fb8d8c810886f81/code_1.102.3-1753759567_amd64.deb";constchar *file_name = "my_download.deb";intmain(){printf("hello\n");time_t start;time_t end; time(&start); http_file_download(file_url, file_name); time(&end);int esapsed = difftime(end, start);printf("esapsed time:%d sec\n", esapsed);return0;}运行结果下:

通过linux系统自带的md5sum功能,对比通过浏览器下载的文件,和通过curl编写的程序下载的文件,MD5的值是一样的,说明下载的文件是对的。
3 多线程下载
curl下载文件,主要时间是在执行curl_easy_perform时阻塞,直到文件下载完成。
使用多线程下载的基本思路,就是创建多个curl句柄,对要下载的文件进行分段下载,在多个线程中,各自执行curl_easy_perform,来下载文件的各个部分的数据。
3.1 curl下载主逻辑
在下载主逻辑中,使用多线程的下载,与刚才单线程的主要区别是:
需要先知道要下载的文件长度,便于进行分段下载的任务分配,这个通过编写一个get_download_length来实现 根据文件大小,提前为文件开辟空间,用来进行分段写入,这个通过lseek方法来实现 将文件映射到虚拟内存,这个通过mmap方法来实现 创建多个线程,在每个线程中,执行各自数据的下载,创建线程可通过pthread_create方式
voidhttp_file_download(constchar *url, constchar *name){printf("[%s] in\n", __func__);//要下载的文件长度longlong file_length = get_download_length(url);if (-1 == file_length) {return; }printf("[%s] file_length:%lld\n", __func__, file_length);//创建一个文件int fd = open(name, O_CREAT | O_RDWR | O_TRUNC, S_IRUSR | S_IWUSR);if (-1 == fd) {printf("[%s] create file:%s failed\n", __func__, name);return; }printf("[%s] create file:%s ok\n", __func__, name);//先给文件开辟一块空间if (-1 == lseek(fd, file_length -1, SEEK_SET)) {printf("[%s] lseek failed\n", __func__);goto DONE; }if (1 != write(fd, "\0", 1)) {printf("[%s] write failed\n", __func__);goto DONE; }//将文件映射到虚拟内存void *fileptr = mmap(NULL, file_length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);if (MAP_FAILED == fileptr) {printf("[%s] mmap failed\n", __func__);goto DONE; }//创建线程pthread_t threads[MULTI_DOWNLOAD_THEAD_NUM];//ThreadParam threadParam[MULTI_DOWNLOAD_THEAD_NUM];for (int i = 0; i < MULTI_DOWNLOAD_THEAD_NUM; i++) { threadParam[i].thrd_num = (i+1); threadParam[i].url = (char *)url; threadParam[i].range[0] = (file_length / MULTI_DOWNLOAD_THEAD_NUM) * i; threadParam[i].range[1] = (MULTI_DOWNLOAD_THEAD_NUM - 1 == i) ? (file_length - 1) : (file_length / MULTI_DOWNLOAD_THEAD_NUM) * (i + 1) - 1; threadParam[i].ptr = fileptr + threadParam[i].range[0]; threadParam[i].offset = 0; pthread_create(&threads[i], NULL, thread_func, &threadParam[i]); }for (int i = 0; i < MULTI_DOWNLOAD_THEAD_NUM; i++) { pthread_join(threads[i], NULL); }printf("[%s] all download done\n", __func__); munmap(fileptr, file_length);DONE:printf("[%s] close file\n", __func__); close(fd);printf("[%s] out\n", __func__); }3.1.1 mmap的介绍
mmap 是 Unix/Linux 系统中用于将文件或设备映射到进程虚拟内存空间的系统调用,其函数原型如下:
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);参数:
void *addr:期望的映射起始地址(通常设为NULL,由操作系统自动选择合适的地址)size_t length:要映射的字节数(必须大于 0),通常是文件的大小int prot:映射区域的保护权限(内存访问权限),可组合以下值:PROT_READ:可读(必须有,否则无法访问映射区域)PROT_WRITE:可写(修改映射区域会同步到文件)PROT_EXEC:可执行(映射区域的内容可作为代码运行)PROT_NONE:不可访问(无任何权限)int flags:映射类型和属性,关键取值(必选其一):MAP_ANONYMOUS:匿名映射(无关联文件,用于进程间共享内存),此时fd需设为-1MAP_FIXED:强制使用addr作为起始地址(若无法映射会失败,不推荐随意使用)MAP_SHARED:共享映射。修改映射区域会同步到文件,且对其他映射该文件的进程可见MAP_PRIVATE:私有映射。修改映射区域不会同步到文件,仅创建进程可见(写时复制)其他常用标志:
int fd:要映射的文件描述符(通过open打开的文件句柄)off_t offset:从文件的哪个偏移量开始映射(必须是系统页大小的整数倍,通常为 4KB 或 8KB)
返回值:
成功:返回映射区域的起始地址( void*指针)失败:返回 MAP_FAILED(通常是(void*)-1),并设置errno指示错误原因,例如:EBADF表示文件描述符无效EACCES表示权限不足
3.1.2 关于数据分段的计算
假设数据的长度为file_length,有N个线程进行同时下载,则将数据平均分为N份(0、1、...、N-1),由于可能无法正好平均分,则最后一份剩余多少就是多少。

3.2 获取文件的长度
获取文件长度的原理,也是通过curl来执行一次数据传输,只不过,不是获取数据本身,只是获取数据头数据,然后通过curl_easy_getinfo来获取数据头中包含的数据长度信息。
longlongget_download_length(constchar *url){ CURL *curl = curl_easy_init(); curl_easy_setopt(curl, CURLOPT_URL, url); curl_easy_setopt(curl, CURLOPT_USERAGENT, "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36"); //opt(about://version) curl_easy_setopt(curl, CURLOPT_HEADER, 1); //opt curl_easy_setopt(curl, CURLOPT_NOBODY, 1);if (CURLM_OK != curl_easy_perform(curl)) {printf("[%s] failed\n", __func__);return-1; }double res = 0; //double类型 curl_easy_getinfo(curl, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &res); download_len = (longlong )res;return download_len;}3.3 线程函数
线程函数的内容,其实就是在单线程下载的示例程序中的那些curl的接口调用逻辑。
主要调用的接口如下:
curl_easy_init:初始化curl句柄 curl_easy_setopt:设置选项(文件下载地址,下载的回调函数) curl_easy_perform:执行文件下载
void *thread_func(void *arg){ ThreadParam *par = (ThreadParam *)arg;char range[64] = {0};sprintf(range, "%lld-%lld", par->range[0], par->range[1]);//创建一个curl对象 printf("[%s] thrd<%d> curl init\n", __func__, par->thrd_num); CURL *curl = curl_easy_init();printf("[%s] thrd<%d> curl setopt, url:%s, range:%s\n", __func__, par->thrd_num, par->url, range); curl_easy_setopt(curl, CURLOPT_URL, par->url); curl_easy_setopt(curl, CURLOPT_RANGE, range); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_file_cb); curl_easy_setopt(curl, CURLOPT_WRITEDATA, par); curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0); curl_easy_setopt(curl, CURLOPT_PROGRESSFUNCTION, download_progress_cb); curl_easy_setopt(curl, CURLOPT_PROGRESSDATA, par); //执行网络请求printf("[%s] thrd<%d> curl performt\n", __func__, par->thrd_num); CURLcode res = curl_easy_perform(curl); if (CURLE_OK != res) {printf("[%s] thrd<%d> curl_easy_perform err:%d\n", __func__, par->thrd_num, res); }printf("[%s] thrd<%d> curl performt done\n", __func__, par->thrd_num);}这里curl_easy_setopt新增了对下载数据进度的回调逻辑,可以在下载文件的过程中,显示下载的进度
3.4 下载数据的回调
下载数据的回调,需要根据回调函数的原型,自己实现具体的内容,这里就是将接收的数据通过memcpy方法写入自己创建的文件。
size_twrite_file_cb(char *ptr, size_t size, size_t nmemb, void *userdata){staticint i = 0;size_t this_size = size * nmemb;//printf("[%s] <%d> total size:%zu\n", __func__, ++i, this_size); ThreadParam *par = (ThreadParam *)userdata;memcpy(par->ptr + par->offset, ptr, this_size); par->offset += this_size;return this_size;}3.5 文件下载进度
下载进度的回调,需要根据回调函数的原型,自己实现具体的内容。
intdownload_progress_cb(void *userdata, double totalDownload, double nowDownload, double totalUpload, double nowUpload){ ThreadParam *par = (ThreadParam *)userdata; par->download = nowDownload;int percent = 0;staticint lastPercent = -1;if (totalDownload > 0) {double allDownload = 0;for (int i = 0 ;i < MULTI_DOWNLOAD_THEAD_NUM; i++) { allDownload += threadParam[i].download; } percent = (int)(allDownload / download_len * 100); }if (lastPercent != percent) { lastPercent = percent;printf("[%s] percent:%d%%\n", __func__, percent); }return0;}3.6 主函数
主函数的内容如下:
// gcc download_file_multi_thread.c -o download_file_multi_thread -lcurl -lpthread#include<stdio.h>#include<stdlib.h>#include<string.h>#include<curl/curl.h>#include<unistd.h>#include<sys/types.h>#include<sys/stat.h>#include<sys/mman.h>#include<fcntl.h>#include<time.h>#include<pthread.h>constchar *file_url = "https://vscode.download.prss.microsoft.com/dbazure/download/stable/488a1f239235055e34e673291fb8d8c810886f81/code_1.102.3-1753759567_amd64.deb";constchar *file_name = "my_multithread_download.deb";#define MULTI_DOWNLOAD_THEAD_NUM (3)typedefstruct{int thrd_num;char *url;longlong range[2];char *ptr;int offset;double download;}ThreadParam;ThreadParam threadParam[MULTI_DOWNLOAD_THEAD_NUM];longlong download_len = -1;size_twrite_file_cb(char *ptr, size_t size, size_t nmemb, void *userdata);intdownload_progress_cb(void *userdata, double totalDownload, double nowDownload, double totalUpload, double nowUpload);longlongget_download_length(constchar *url);void *thread_func(void *arg);voidhttp_file_download(constchar *url, constchar *name);intmain(){printf("hello\n");time_t start;time_t end; time(&start); http_file_download(file_url, file_name); time(&end);int esapsed = difftime(end, start);printf("esapsed time:%d sec\n", esapsed);return0;}运行结果如下:


可以看到,3个线程的下载依次完成,进度最终显示到100%,文件下载完成。
不过,通过计时打印,多个线程下载,总耗时却没有减少,有待再研究。
4 总结
本篇介绍了如何使用curl编程实现文件的下载,首先介绍了curl编程的一些基础API的使用,然后通过实例编程,来实现一个文件下载的功能。
夜雨聆风