• 正文
  • 相关推荐
申请入驻 产业图谱

如何使用curl编程来下载文件

08/12 09:25
545
加入交流群
扫码加入
获取工程师必备礼包
参与热点资讯讨论

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

本篇来介绍libcul的C语言编程,实现一个文件下载的功能。

1 curl基础介绍

1.1 核心数据结构

1.1.1 CURL句柄

CURL是libcurl 的核心句柄,每个请求对应一个?CURL?实例,是配置请求参数、执行操作的主要载体。

其特性包括:

独立性:每个句柄都是独立的,配置互不影响

可复用性:一个句柄在完成一次请求后,可通过重新设置选项再次使用,无需重复创建和销毁

状态存储:句柄内部存储了请求的所有状态信息,直到被释放

1.1.2 CURLcode返回值

CURLcode是函数返回值类型,用于判断操作是否成功。

成功状态为:CURLE_OK

所有的返回值枚举如下:

typedef?enum?{
? 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?句柄资源

void?curl_easy_cleanup(CURL *curl);

1.2.4 curl_global_cleanup

清理 libcurl 全局环境,程序结束前调用

void?curl_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:执行文件下载
void?http_file_download(const?char?*url,?const?char?*name)
{
? ??printf("[%s] inn", __func__);
? ??
? ??int?fd = open(name, O_CREAT | O_RDWR | O_TRUNC, S_IRUSR | S_IWUSR);
? ??if?(-1?== fd)
? ? {
? ? ? ??printf("[%s] create file:%s failedn", __func__, name);
? ? ? ??return;
? ? }
? ??printf("[%s] create file:%s okn", __func__, name);
? ? ? ??
? ??printf("[%s] curl initn", __func__);
? ? CURL *curl = curl_easy_init();
? ??
? ??printf("[%s] curl setopt, url:%sn", __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 performtn", __func__);
? ? curl_easy_perform(curl);
? ??
? ??printf("[%s] close filen", __func__);
? ? close(fd);
? ??
? ??printf("[%s] outn", __func__); ??
}

2.2 下载数据的回调

下载数据的回调,需要根据回调函数的原型,自己实现具体的内容,这里就是将接收的数据通过write方法写入自己创建的文件。

文件的创建是在上面的http_file_download中通过open方法创建一个文件,并通过curl_easy_setopt的CURLOPT_WRITEDATA选项将文件

的fd句柄传给回调函数。

size_t?write_file_cb(char?*ptr,?size_t?size,?size_t?nmemb,?void?*userdata)
{
? ??static?int?i =?0;
? ??size_t?this_size = size * nmemb;
? ??printf("[%s] <%d> total size:%zun", __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";

int?main()
{
? ??printf("hellon");
? ??
? ??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 secn", 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方式
void?http_file_download(const?char?*url,?const?char?*name)
{
? ??printf("[%s] inn", __func__);
? ??
? ??//要下载的文件长度
? ??longlong?file_length = get_download_length(url);
? ??if?(-1?== file_length)
? ? {
? ? ? ??return;
? ? }
? ??printf("[%s] file_length:%lldn", __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 failedn", __func__, name);
? ? ? ??return;
? ? }
? ??printf("[%s] create file:%s okn", __func__, name);
? ??
? ??//先给文件开辟一块空间
? ??if?(-1?== lseek(fd, file_length?-1, SEEK_SET))
? ? {
? ? ? ??printf("[%s] lseek failedn", __func__);
? ? ? ??goto?DONE;
? ? }
? ??
? ??if?(1?!= write(fd,?"",?1))
? ? {
? ? ? ??printf("[%s] write failedn", __func__);
? ? ? ??goto?DONE;
? ? }
? ??
? ??//将文件映射到虚拟内存
? ??void?*fileptr = mmap(NULL, file_length, PROT_READ | PROT_WRITE, MAP_SHARED, fd,?0);
? ??if?(MAP_FAILED == fileptr)
? ? {
? ? ? ??printf("[%s] mmap failedn", __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 donen", __func__);
? ??
? ? munmap(fileptr, file_length);
? ??
DONE:
? ??printf("[%s] close filen", __func__);
? ? close(fd);
? ??
? ??printf("[%s] outn", __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来获取数据头中包含的数据长度信息。

long?long?get_download_length(const?char?*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] failedn", __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 initn", __func__, par->thrd_num);
? ? CURL *curl = curl_easy_init();
? ??
? ??printf("[%s] thrd<%d> curl setopt, url:%s, range:%sn", __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 performtn", __func__, par->thrd_num);
? ? CURLcode res = curl_easy_perform(curl); ??
? ??if?(CURLE_OK != res)
? ? {
? ? ? ??printf("[%s] thrd<%d> curl_easy_perform err:%dn", __func__, par->thrd_num, res);
? ? }
? ??printf("[%s] thrd<%d> curl performt donen", __func__, par->thrd_num);
}

这里curl_easy_setopt新增了对下载数据进度的回调逻辑,可以在下载文件的过程中,显示下载的进度

3.4 下载数据的回调

下载数据的回调,需要根据回调函数的原型,自己实现具体的内容,这里就是将接收的数据通过memcpy方法写入自己创建的文件。

size_t?write_file_cb(char?*ptr,?size_t?size,?size_t?nmemb,?void?*userdata)
{
? ??static?int?i =?0;
? ??size_t?this_size = size * nmemb;
? ??//printf("[%s] <%d> total size:%zun", __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 文件下载进度

下载进度的回调,需要根据回调函数的原型,自己实现具体的内容。

int?download_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_t?write_file_cb(char?*ptr,?size_t?size,?size_t?nmemb,?void?*userdata);
int?download_progress_cb(void?*userdata,?double?totalDownload,?double?nowDownload,?double?totalUpload,?double?nowUpload);
long?long?get_download_length(const?char?*url);
void?*thread_func(void?*arg);
void?http_file_download(const?char?*url,?const?char?*name);

int?main()
{
? ??printf("hellon");
? ??
? ??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 secn", esapsed);
? ??
? ??return0;
}

运行结果如下:

可以看到,3个线程的下载依次完成,进度最终显示到100%,文件下载完成。

不过,通过计时打印,多个线程下载,总耗时却没有减少,有待再研究。

4 总结

本篇介绍了如何使用curl编程实现文件的下载,首先介绍了curl编程的一些基础API的使用,然后通过实例编程,来实现一个文件下载的功能。

相关推荐

登录即可解锁
  • 海量技术文章
  • 设计资源下载
  • 产业链客户资源
  • 写文章/发需求
立即登录