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

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 地址)、整数(如超时时间)、函数指针(如回调函数)等。
    • 若选项不需要参数,此值可设为 0NULL(根据具体选项要求)

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] 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_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:%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>const char *file_url = "https://vscode.download.prss.microsoft.com/dbazure/download/stable/488a1f239235055e34e673291fb8d8c810886f81/code_1.102.3-1753759567_amd64.deb";
const char *file_name = "my_download.deb";int main()
{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);return 0;
}

运行结果下:

通过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] in\n", __func__);//要下载的文件长度long long 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_SHARED:共享映射。修改映射区域会同步到文件,且对其他映射该文件的进程可见

    • MAP_PRIVATE:私有映射。修改映射区域不会同步到文件,仅创建进程可见(写时复制)

    • 其他常用标志:

      • MAP_ANONYMOUS:匿名映射(无关联文件,用于进程间共享内存),此时 fd 需设为 -1
      • MAP_FIXED:强制使用 addr 作为起始地址(若无法映射会失败,不推荐随意使用)
  • 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); //optcurl_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 = (long long )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_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:%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 文件下载进度

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

int download_progress_cb(void *userdata, double totalDownload, double nowDownload, double totalUpload, double nowUpload)
{ThreadParam *par = (ThreadParam *)userdata;par->download = nowDownload;int percent = 0;static int 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);}return 0;
}

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>const char *file_url = "https://vscode.download.prss.microsoft.com/dbazure/download/stable/488a1f239235055e34e673291fb8d8c810886f81/code_1.102.3-1753759567_amd64.deb";
const char *file_name = "my_multithread_download.deb";#define MULTI_DOWNLOAD_THEAD_NUM (3)typedef struct
{int thrd_num;char *url;long long range[2];char *ptr;int offset;double download;
}ThreadParam;ThreadParam threadParam[MULTI_DOWNLOAD_THEAD_NUM];long long 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("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);return 0;
}

运行结果如下:

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

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

4 总结

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/bicheng/92892.shtml
繁体地址,请注明出处:http://hk.pswp.cn/bicheng/92892.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

大语言模型提示工程与应用:ChatGPT提示工程技术指南

ChatGPT提示工程 学习目标 在本课程中&#xff0c;我们将学习更多关于ChatGPT的最新提示工程技术。 相关知识点 ChatGPT提示工程 学习内容 1 ChatGPT提示工程 ChatGPT是OpenAI研发的新型对话模型&#xff0c;具备多轮对话能力。该模型通过人类反馈强化学习(RLHF)训练&am…

能力评估:如何系统评估你的技能和经验

能力评估&#xff1a;如何系统评估你的技能和经验 作为一名38岁的互联网研发老兵&#xff0c;你已经积累了丰富的经验&#xff0c;包括技术深度、项目管理、团队协作等。但能力评估不是一次性事件&#xff0c;而是持续过程&#xff0c;帮助你识别优势、短板&#xff0c;并为职业…

鸿蒙开发中所有自定义装饰器的完整案例解析--涵盖 16 个核心装饰器的详细用法和实战场景

以下是鸿蒙开发中 所有自定义装饰器的完整案例解析 和 终极总结指南&#xff0c;涵盖 16 个核心装饰器的详细用法和实战场景&#xff1a; 一、终极总结表&#xff1a;16大装饰器全景图 装饰器类别V1V2核心作用典型场景Component组件定义✅❌创建标准组件业务UI组件ComponentV2…

【C++】哈希表的实现(unordered_map和unordered_set的底层)

文章目录 目录 文章目录 前言 一、unordered_set和unordered_map介绍 二、哈希表的介绍 三、哈希冲突的解决方法 1.开放定址法 2.链地址法 四、两种哈希表代码实现 总结 前言 前面我们学习了红黑树&#xff0c;红黑树就是map和set的底层&#xff0c;本篇文章带来的是unordered…

欧拉公式的意义

欧拉公式的意义 欧拉公式&#xff08;Euler’s Formula&#xff09;是数学中最重要的公式之一&#xff0c;它将复数、指数函数和三角函数紧密联系在一起。其基本形式为&#xff1a; eiθcos⁡θisin⁡θ e^{i\theta} \cos \theta i \sin \theta eiθcosθisinθ 当 θπ\thet…

Linux Docker 运行SQL Server

在Linux操作系统&#xff0c;已安装docker&#xff0c;现在以docker compose方式&#xff0c;安装一个最新版SQL Server 2022的数据库。 # 建个目录&#xff08;请不要照抄&#xff0c;我的数据盘在/data&#xff0c;你可以改为/opt&#xff09; mkdir /data/sqlserver# 进入目…

C++:stack_queue(2)实现底层

文章目录一.容器适配器1. 本质&#xff1a;2. 接口&#xff1a;3. 迭代器&#xff1a;4. 功能&#xff1a;二.deque的简单介绍1.概念与特性2.结构与底层逻辑2.1 双端队列&#xff08;deque&#xff09;结构&#xff1a;2.2 deque的内部结构2.3 deque的插入与删除操作&#xff1…

Lightroom 安卓版 + Windows 版 + Mac 版全适配,编辑管理一站式,专业摄影后期教程

软件是啥样的​ Adobe Lightroom 这软件&#xff0c;在安卓手机、Windows 电脑和 Mac 电脑上都能用。不管是喜欢拍照的人&#xff0c;还是专门搞摄影的&#xff0c;用它都挺方便&#xff0c;能一站式搞定照片编辑、整理和分享这些事儿。 ****下载地址 分享文件&#xff1a;【Li…

office卸载不干净?Office356卸载不干净,office强力卸载软件下载

微软官方认可的卸载工具&#xff0c;支持彻底清除Office组件及注册表残留。需要以管理员身份运行&#xff0c;选择“移除Office”功能并确认操作。 Office Tool Plus安装地址获取 点击这里获取&#xff1a;Office Tool Plus 1、双击打开软件 image 2、选择左右的工具箱&…

互联网企业慢性死亡的招聘视角分析:从岗位割裂看战略短视

内容简介&#xff1a; 一个猎头和HR的简单拒绝&#xff0c;揭示了中国互联网企业人才观念的深层问题。通过分析岗位过度细分现象&#xff0c;本文探讨了战略短视、内斗文化和核心竞争力缺失如何导致企业慢性死亡&#xff0c;并提出了系统性的解决方案。#互联网企业 #人才招聘 #…

OpenBMC中phosphor-dbus-interfaces深度解析:架构、原理与应用实践

引言 在OpenBMC生态系统中&#xff0c;phosphor-dbus-interfaces作为D-Bus接口定义的核心组件&#xff0c;扮演着系统各模块间通信"契约"的关键角色。本文将基于OpenBMC源码&#xff0c;从架构设计、实现原理到实际应用三个维度&#xff0c;全面剖析这一基础组件的技…

驾驶场景玩手机识别准确率↑32%:陌讯动态特征融合算法实战解析

原创声明本文为原创技术解析文章&#xff0c;核心技术参数与架构设计参考自《陌讯技术白皮书》&#xff0c;转载请注明出处。一、行业痛点&#xff1a;驾驶场景行为识别的现实挑战根据交通运输部道路运输司发布的《驾驶员不安全行为研究报告》显示&#xff0c;驾驶过程中使用手…

Mysql——单表最多数据量多少需要分表

目录 一、MySql单表最多数据量多少需要分表 1.1、阿里开发公约 1.2、一个三层的B+树,它最多可以存储多少数据量 1.3、示例 1.3.1、示例表中一行的数据占多少字节数 1.3.2、示例表中一页里面最多可以存多少条记录 1.3.3、按示例表计算,一个三层的B+树,可以放多少条100字节的数…

scikit-learn/sklearn学习|岭回归解读

【1】引言 前序学习进程中&#xff0c;对用scikit-learn表达线性回归进行了初步解读。 线性回归能够将因变量yyy表达成由自变量xxx、线性系数矩阵www和截距bbb组成的线性函数式&#xff1a; y∑i1nwi⋅xibwTxby\sum_{i1}^{n}w_{i}\cdot x_{i}bw^T{x}byi1∑n​wi​⋅xi​bwTxb实…

基于Django的图书馆管理系统的设计与实现

基于Django的图书馆管理系统的设计与实现、

ComfyUI版本更新---解决ComfyUI的节点不兼容问题

前言&#xff1a; 新版本的COMFYUI与节点容易出现不兼容的问题,会导致整个系统崩掉。 目录 一、前期准备工作&#xff1a;虚拟环境配置 为什么需要虚拟环境&#xff1f; 具体操作步骤 二、常见问题解决方案 1、工作流输入输出图像不显示问题 2、工作流不能拖动&#xff0…

生产管理ERP系统|物联及生产管理ERP系统|基于SprinBoot+vue的制造装备物联及生产管理ERP系统设计与实现(源码+数据库+文档)

生产管理ERP系统 目录 基于SprinBootvue的制造装备物联及生产管理ERP系统设计与实现 一、前言 二、系统设计 三、系统功能设计 四、数据库设计 五、核心代码 六、论文参考 七、最新计算机毕设选题推荐 八、源码获取&#xff1a; 博主介绍&#xff1a;✌️大厂码农|毕…

Numpy科学计算与数据分析:Numpy数组创建与应用入门

Numpy数组创建实战 学习目标 通过本课程的学习&#xff0c;学员将掌握使用Numpy库创建不同类型的数组的方法&#xff0c;包括一维数组、多维数组、全零数组、全一阵列、空数组等。本课程将通过理论讲解与实践操作相结合的方式&#xff0c;帮助学员深入理解Numpy数组的创建过程…

如何回收内存对象,有哪些回收算法?

它的主要不足有两个&#xff1a; 效率问题&#xff0c;标记和清除两个过程的效率都不高。 空间问题&#xff0c;标记清除之后会产生大量不连续的内存碎片&#xff0c;空间碎片太多可能会导致以后在程序运行过程中需 要分配较大对象时&#xff0c;无法找到足够的连续内存而不得不…

Numpy科学计算与数据分析:Numpy文件操作入门之数组数据的读取和保存

Numpy文件读写实战 学习目标 通过本课程&#xff0c;学员将深入了解如何使用Numpy库进行数组数据的读取和保存&#xff0c;包括文本文件和二进制文件的处理方法。通过本课程的学习&#xff0c;学员将能够熟练掌握Numpy在文件操作中的应用&#xff0c;为数据处理和分析打下坚实…