如何使用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
英文地址,請注明出處:http://en.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;為數據處理和分析打下堅實…