curl是Linux下一個非常著名的下載庫,通過這個庫,可以很簡單的實現文件的下載等操作。
看一個簡單的例子:
1 #include <curl/curl.h> 2 #include <stdio.h> 3 #include <string.h> 4 5 CURL *curl; 6 CURLcode res; 7 8 size_t write_data(void *ptr, size_t size, size_t nmemb, void *stream) 9 { 10 if (strlen((char *)stream) + strlen((char *)ptr) > 999999) return 0; 11 strcat(stream, (char *)ptr); 12 return size*nmemb; 13 } 14 15 char *down_file(char *filename) 16 { 17 static char str[10000000]; 18 strcpy(str, “”); 19 //return “”; 20 21 curl_easy_setopt(curl, CURLOPT_URL, filename); //設置下載地址 22 23 curl_easy_setopt(curl, CURLOPT_TIMEOUT, 3);//設置超時時間 24 25 curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_data);//設置寫數據的函數 26 27 curl_easy_setopt(curl, CURLOPT_WRITEDATA, str);//設置寫數據的變量 28 29 res = curl_easy_perform(curl);//執行下載 30 31 str[9999999] = ‘\0′; 32 if(CURLE_OK != res) return NULL;//判斷是否下載成功 33 34 return str; 35 } 36 37 int main() 38 { 39 char url[200]; 40 curl = curl_easy_init();//對curl進行初始化 41 42 char *result; 43 while(fgets(url, 200, stdin)){ 44 result = down_file(url); 45 if (result) puts(result); 46 else puts(“Get Error!”); 47 printf(“\nPlease Input a url:”); 48 49 } 50 curl_easy_cleanup(curl);//釋放curl資源 51 52 53 return 0; 54 }
下面是轉載的curl詳細使用:
curl->libcurl的手冊可以查看
http://curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTWRITEDATA
譯者:JGood(http://blog.csdn.net/JGood )
譯者注:這是一篇介紹如何使用libcurl的入門教程。文檔不是逐字逐句按原文翻譯,而是根據筆者對libcurl的理解,參考原文寫成。文中用到的一 些例子,可能不是出自原文,而是筆者在學習過程中,寫的一些示例程序(筆者使用的libcurl版本是:7.19.6)。出現在這里主要是為了更好的說明 libcurl的某些api函數的使用。許多例子都參考libcurl提供的example代碼。原文example中的提供的示例程序完全使用C語言, 而這里筆者提供的例子使用C++語言。因為能力有限,對於libcurl的某些理解和使用可能有誤,歡迎批評指正。
目標本文檔介紹了在應用程序開發過程中,如何正確使用libcurl的基本方式和指導原則。文檔使用C語言來調用libcurl的接口,當然也適用於其他與C語言接近的語言。
文檔主要針對使用libcurl來進行開發的人員。文檔所摜的應用程序泛指你寫的源代碼,這些代碼使用了libcurl進行數據傳輸。
更多關於libcurl的功能和接口信息,可以在相關的主頁上查閱。
編譯源碼有很多種不同的方式來編譯C語言代碼。這里使用UNIX平台下的編譯方式。即使你使用的是其他的操作系統,你仍然可以通過閱讀本文檔來獲取許多有用的信息。
編譯你的編譯器必須知道libcurl頭文件的位置。所以在編譯的時候,你要設置頭文件的包含路徑。可以使用curl-config工具來獲取這方面的信息:
$ curl-config –cflags
鏈接編譯完源碼(這時的源代碼不是指libcurl的源代碼,你是你自己寫的程序代碼)之后,你還必須把目標文件鏈接成單個可執行文件。你要鏈接 libcurl庫,以及libcurl所依賴的其他庫,例如OpenSLL庫。當然可能還需要一些其他的操作系統庫。最后你還要設置一些編譯選項,當然可 以使用curl-config工具簡化操作:
$curl-config –libs
是否使用SSL定制編譯libcurl。與其他庫不同的是,libcurl可以定制編譯,根據實際需要是否支持某些特性,如是否支持SSL傳輸,像HTTPS和 FTPS。如果決定需要支持SSL,必須在編譯時正確的設置。可以使用’curl-config’來判斷libcurl庫是否支持SSL:
$ curl-config –feature
autoconf宏當你編寫配置腳本來檢測libcurl及其相應設置時,你可以使用預定義宏。文檔docs/libcurl/libcurl.m4告訴你如何使用這些宏。
跨平台的可移植的代碼libcurl的開發人員花費很大的努力,使libcurl盡可能在大多數平台上正常運行。
全局初始化應用程序在使用libcurl之前,必須先初始化libcurl。libcurl只需初始化一次。可以使用以下語句進行初始化:
curl_global_init()接收一個參數,告訴libcurl如何初始化。參數CURL_GLOBAL_ALL 會使libcurl初始化所有的子模塊和一些默認的選項,通常這是一個比較好的默認參數值。還有兩個可選值:
CURL_GLOBAL_WIN32只能應用於Windows平台。它告訴libcurl初始化winsock庫。如果winsock庫沒有正確地初始化,應用程序就不能使用socket。在應用程序中,只要初始化一次即可。
CURL_GLOBAL_SSL如果libcurl在編譯時被設定支持SSL,那么該參數用於初始化相應的SSL庫。同樣,在應用程序中,只要初始化一次即可。
libcurl有默認的保護機制,如果在調用curl_easy_perform時它檢測到還沒有通過curl_global_init進行初始 化,libcurl會根據當前的運行時環境,自動調用全局初始化函數。但必須清楚的是,讓系統自已初始化不是一個好的選擇。
當應用程序不再使用libcurl的時候,應該調用curl_global_cleanup來釋放相關的資源。
在程序中,應當避免多次調用curl_global_init和curl_global_cleanup。它們只能被調用一次。
libcurl提供的功能在運行時根據libcurl支持的特性來進行開發,通常比編譯時更好。可以通過調用curl_version_info函數返回的結構體來獲取運行時的具 體信息,從而確定當前環境下libcurl支持的一些特性。下面是筆者在visual studio2008中調用相關函數獲取libcurl版本信息的截圖:
首先介紹libcurl中被稱為easy interface的api函數,所有這些函數都是有相同的前綴:curl_easy 。
當前版本的libcurl也提供了multi interface,關於這些接口的詳細使用,在下面的章節中會有介紹。在使用multi interface之前,你首先應該理解如何使用easy interface。
要使用easy interface,首先必須創建一個easy handle,easy handle用於執行每次操作。基本上,每個線程都應該有自己的easy handle用於數據通信(如果需要的話)。千萬不要在多線程之間共享同一個easy handle。下面的函數用於獲取一個easy handle :
CURL *easy_handle = curl_easy_init();
在easy handle上可以設置屬性和操作(action)。easy handle就像一個邏輯連接,用於接下來要進行的數據傳輸。
使用curl_easy_setopt函數可以設 置easy handle的屬性和操作,這些屬性和操作控制libcurl如何與遠程主機進行數據通信。一旦在easy handle中設置了相應的屬性和操作,它們將一直作用該easy handle。也就是說,重復使用easy hanle向遠程主機發出請求,先前設置的屬性仍然生效。
easy handle的許多屬性使用字符串(以\0結尾的字節數組)來設置。通過curl_easy_setopt函數設置字符串屬性時,libcurl內部會自動拷貝這些字符串,所以在設置完相關屬性之后,字符串可以直接被釋放掉(如果需要的話)。
easy handle最基本、最常用的屬性是URL。你應當通過CURLOPT_URL屬性提供適當的URL:
curl_easy_setopt(easy_handle, CURLOPT_URL, “http://blog.csdn.net/JGood “);
假設你要獲取URL所表示的遠程主機上的資源。你需要寫一段程序用來完成數據傳輸,你可能希望直接保存接收到的數據而不是簡單的在輸出窗口中打印它們。所以,你必須首先寫一個回調函數用來保存接收到的數據。回調函數的原型如下:
size_t write_data(void *buffer, size_t size, size_t nmemb, void *userp);
可以使用下面的語句來注冊回調函數,回調函數將會在接收到數據的時候被調用:
curl_easy_setopt(easy_handle, CURLOPT_WRITEFUNCTION, write_data);
可以給回調函數提供一個自定義參數,libcurl不處理該參數,只是簡單的傳遞:
curl_easy_setopt(easy_handle, CURLOPT_WRITEDATA, &internal_struct);
如果你沒有通過CURLOPT_WRITEFUNCTION屬性給easy handle設置回調函數,libcurl會提供一個默認的回調函數,它只是簡單的將接收到的數據打印到標准輸出。你也可以通過 CURLOPT_WRITEDATA屬性給默認回調函數傳遞一個已經打開的文件指針,用於將數據輸出到文件里。
下面是一些平台相關的注意點。在一些平台上,libcurl不能直接操作由應用程序打開的文件。所以,如果使用默認的回調函數,同時通過 CURLOPT_WRITEDATA屬性給easy handle傳遞一個文件指針,應用程序可能會執行失敗。如果你希望自己的程序能跑在任何系統上,你必須避免出現這種情況。
如果以win32動態連接庫的形式來使用libcurl,在設置CURLOPT_WRITEDATA屬性時,你必須同時 使用CURLOPT_WRITEFUNCTION來注冊回調函數。否則程序會執行失敗(筆者嘗試只傳遞一個打開的文件指針而不顯式設置回調函數,程序並沒有崩潰。可能是我使用的方式不正確。)。
當然,libcurl還支持許多其他的屬性,在接下來的篇幅里,你將會逐步地接觸到它們。調用下面的函數,將執行真正的數據通信:
success = curl_easy_perform(easy_handle);
curl_easy_perfrom將連接到遠程主機,執行必要的命令,並接收數據。當接收到數據時,先前設置的回調函數將被調用。libcurl可能一 次只接收到1字節的數據,也可能接收到好幾K的數據,libcurl會盡可能多、及時的將數據傳遞給回調函數。回調函數返回接收的數據長度。如果回調函數 返回的數據長度與傳遞給它的長度不一致(即返回長度 != size * nmemb),libcurl將會終止操作,並返回一個錯誤代碼。
當數據傳遞結束的時候,curl_easy_perform將返回一個代碼表示操作成功或失敗。如果需要獲取更多有關通信細節的信息,你可以設置CURLOPT_ERRORBUFFER屬性,讓libcurl緩存許多可讀的錯誤信息。
easy handle在完成一次數據通信之后可以被重用。這里非常建議你重用一個已經存在的easy handle。如果在完成數據傳輸之后,你創建另一個easy handle來執行其他的數據通信,libcurl在內部會嘗試着重用上一次創建的連接。
對於有些協議,下載文件可能包括許多復雜的子過程:日志記錄、設置傳輸模式、選擇當前文件夾,最后下載文件數據。使用libcurl,你不需要關心這一切,你只需簡單地提供一個URL,libcurl會給你做剩余所有的工作。
下面的這個例子演示了如何獲取網頁源碼,將其保存到本地文件,並同時將獲取的源碼輸出到控制台上。
1 /** 2 * @brief libcurl接收到數據時的回調函數 3 * 4 * 將接收到的數據保存到本地文件中,同時顯示在控制台上。 5 * 6 * @param [in] buffer 接收到的數據所在緩沖區 7 * @param [in] size 數據長度 8 * @param [in] nmemb 數據片數量 9 * @param [in/out] 用戶自定義指針 10 * @return 獲取的數據長度 11 */ 12 13 size_t process_data(void *buffer, size_t size, size_t nmemb, void *user_p) 14 { 15 FILE *fp = (FILE *)user_p; 16 size_t return_size = fwrite(buffer, size, nmemb, fp); 17 cout << (char *)buffer << endl; 18 return return_size; 19 } 20 21 int main(int argc, char **argv) 22 { 23 // 初始化libcurl 24 25 CURLcode return_code; 26 return_code = curl_global_init(CURL_GLOBAL_WIN32); 27 if (CURLE_OK != return_code) 28 { 29 cerr << "init libcurl failed." << endl; 30 return -1; 31 } 32 33 // 獲取easy handle 34 35 CURL *easy_handle = curl_easy_init(); 36 if (NULL == easy_handle) 37 { 38 cerr << "get a easy handle failed." << endl; 39 curl_global_cleanup(); 40 return -1; 41 } 42 43 FILE *fp = fopen("data.html", "ab+"); // 44 45 // 設置easy handle屬性 46 47 curl_easy_setopt(easy_handle, CURLOPT_URL, http://blog.csdn.net/JGood); 48 49 curl_easy_setopt(easy_handle, CURLOPT_WRITEFUNCTION, &process_data); 50 curl_easy_setopt(easy_handle, CURLOPT_WRITEDATA, fp); 51 52 // 執行數據請求 53 54 curl_easy_perform(easy_handle); 55 56 // 釋放資源 57 58 59 fclose(fp); 60 curl_easy_cleanup(easy_handle); 61 curl_global_cleanup(); 62 63 return 0; 64 }
多線程問題
首先一個基本原則就是:絕對不應該在線程之間共享同一個libcurl handle,不管是easy handle還是multi handle(將在下文中介紹)。一個線程每次只能使用一個handle。
libcurl是線程安全的,但有兩點例外:信號(signals)和SSL/TLS handler。 信號用於超時失效名字解析(timing out name resolves)。libcurl依賴其他的庫來支持SSL/STL,所以用多線程的方式訪問HTTPS或FTPS的URL時,應該滿足這些庫對多線程 操作的一些要求。詳細可以參考:
OpenSSL: http://www.openssl.org/docs/crypto/threads.html#DESCRIPTION
GnuTLS: http://www.gnu.org/software/gnutls/manual/html_node/Multi_002dthreaded-applications.html
NSS: 宣稱是多線程安全的。
什么時候libcurl無法正常工作
傳輸失敗總是有原因的。你可能錯誤的設置了一些libcurl的屬性或者沒有正確的理解某些屬性的含義,或者是遠程主機返回一些無法被正確解析的內容。
這里有一個黃金法則來處理這些問題:將CURLOPT_VERBOSE屬性設置為1,libcurl會輸出通信過程中的一些細節。如果使用的是http協議,請求頭/響應頭也會被輸出。將CURLOPT_HEADER設為1,這些頭信息將出現在消息的內容中。
當然不可否認的是,libcurl還存在bug。當你在使用libcurl的過程中發現bug時,希望能夠提交給我們,好讓我們能夠修復這些bug。你在 提交bug時,請同時提供詳細的信息:通過CURLOPT_VERBOSE屬性跟蹤到的協議信息、libcurl版本、libcurl的客戶代碼、操作系 統名稱、版本、編譯器名稱、版本等等。
如果你對相關的協議了解越多,在使用libcurl時,就越不容易犯錯。
上傳數據到遠程站點
libcurl提供協議無關的方式進行數據傳輸。所以上傳一個文件到FTP服務器,跟向HTTP服務器提交一個PUT請求的操作方式是類似的:
1. 創建easy handle或者重用先前創建的easy handle。
2. 設置CURLOPT_URL屬性。
3. 編寫回調函數。在執行上傳的時候,libcurl通過回調函數讀取要上傳的數據。(如果要從遠程服務器下載數據,可以通過回調來保存接收到的數據。)回調函數的原型如下:
bufptr指針表示緩沖區,用於保存要上傳的數據,size * nitems是緩沖區數據的長度,userp是一個用戶自定義指針,libcurl不對該指針作任何操作,它只是簡單的傳遞該指針。可以使用該指針在應用程序與libcurl之間傳遞信息。
4. 注冊回調函數,設置自定義指針。語法如下:
5. 告訴libcurl,執行的是上傳操作。
有些協議在沒有預先知道上傳文件大小的情況下,可能無法正確判斷上傳是否結束,所以最好預先使用CURLOPT_INFILESIZE_LARGE屬性:告訴它要上傳文件的大小:
6. 調用curl_easy_perform。
接下來,libcurl將會完成剩下的所有工作。在上傳文件過程中,libcurl會不斷調用先前設置的回調函數,用於將要上傳的數據讀入到緩沖區,並執行上傳。
下面的例子演示如何將文件上傳到FTP服務器。筆者使用的是IIS自帶的FTP服務,同時在FTP上設置了可寫權限。
1 /** 2 * @brief 讀取數據的回調。 3 */ 4 size_t read_data(void *buffer, size_t size, size_t nmemb, void *user_p) 5 { 6 return fread(buffer, size, nmemb, (FILE *)user_p); 7 } 8 9 int main(int argc, char **argv) 10 { 11 // 初始化libcurl 12 13 CURLcode code; 14 code = curl_global_init(CURL_GLOBAL_WIN32); 15 if (code != CURLE_OK) 16 { 17 cerr << "init libcurl failed." << endl; 18 return -1; 19 } 20 21 FILE *fp = fopen("a.html", "rb"); 22 if (NULL == fp) 23 { 24 cout << "can't open file." << endl; 25 curl_global_cleanup(); 26 return -1; 27 } 28 29 // 獲取文件大小 30 31 fseek(fp, 0, 2); 32 int file_size = ftell(fp); 33 rewind(fp); 34 35 // 獲取easy handle 36 37 38 CURL *easy_handle = NULL; 39 easy_handle = curl_easy_init(); 40 if (NULL == easy_handle) 41 { 42 cerr << "get a easy handle failed." << endl; 43 fclose(fp); 44 curl_global_cleanup(); 45 return -1; 46 } 47 48 // 設置eash handle屬性 49 50 curl_easy_setopt(easy_handle, CURLOPT_URL, ftp://127.0.0.1/upload.html); 51 52 curl_easy_setopt(easy_handle, CURLOPT_UPLOAD, 1L); 53 curl_easy_setopt(easy_handle, CURLOPT_READFUNCTION, &read_data); 54 curl_easy_setopt(easy_handle, CURLOPT_READDATA, fp); 55 curl_easy_setopt(easy_handle, CURLOPT_INFILESIZE_LARGE, file_size); 56 57 // 執行上傳操作 58 59 code = curl_easy_perform(easy_handle); 60 if (code == CURLE_OK) 61 { 62 cout << "upload successfully." << endl; 63 } 64 65 // 釋放資源 66 67 fclose(fp); 68 curl_easy_cleanup(easy_handle); 69 curl_global_cleanup(); 70 71 return 0; 72 }
關於密碼
客戶端向服務器發送請求時,許多協議都要求提供用戶名與密碼。libcurl提供了多種方式來設置它們。
一些協議支持在URL中直接指定用戶名和密碼,類似於: protocol://user:password@example.com/path/。libcurl能正確的識別這種URL中的用戶名與密碼並執行 相應的操作。如果你提供的用戶名和密碼中有特殊字符,首先應該對其進行URL編碼。
也可以通過CURLOPT_USERPWD屬性來設置用戶名與密碼。參數是格式如 “user:password ”的字符串:
(下面這幾段文字我理解地模模糊糊)有時候在訪問代理服務器的時候,可能時時要求提供用戶名和密碼進行用戶身份驗證。這種情況下,libcurl提供了另一個屬性CURLOPT_PROXYUSERPWD:
在UNIX平台下,訪問FTP的用戶名和密碼可能會被保存在$HOME/.netrc文件中。libcurl支持直接從這個文件中獲取用戶名與密碼:
在使用SSL時,可能需要提供一個私鑰用於數據安全傳輸,通過CURLOPT_KEYPASSWD來設置私鑰:
HTTP驗證
上一章介紹了如何在libcurl中,對需要身份驗證的URL設置用戶名與密碼。在使用HTTP協議時,客戶端有很多種方式向服務器提供驗證信息。默認的 HTTP驗證方法是”Basic”,它將用戶名與密碼以明文的方式、經Base64編碼后保存在HTTP請求頭中,發往服務器。當然這不太安全。
當前版本的libcurl支持的驗證方法有:basic, Digest, NTLM, Negotiate, GSS-Negotiate and SPNEGO。(譯者感嘆:搞Web這么多年,盡然不知道這些Http的驗證方式,實在慚愧。)可以通過CURLOPT_HTTPAUTH屬性來設置具體 的驗證方式:
向代理服務器發送驗證信息時,可以通過CURLOPT_PROXYAUTH設置驗證方式:
也可以同時設置多種驗證方式(通過按位與), 使用‘CURLAUTH_ANY‘將允許libcurl可以選擇任何它所支持的驗證方式。通過CURLOPT_HTTPAUTH或 CURLOPT_PROXYAUTH屬性設置的多種驗證方式,libcurl會在運行時選擇一種它認為是最好的方式與服務器通信:
HTTP Post
這一章介紹如何使用libcurl以Post方式向HTTP服務器提交數據。
方法一,也是最簡單的方式,就像html中使用