libcurl教程


最近一段時間無事,等待入職。以前把libcurl的easy Interface 的那些函數用法手冊翻譯了,這次也把 libcurl-tutorial翻譯一下,多有不足,請多指教。

原文是網址是 :http://curl.haxx.se/libcurl/c/libcurl-tutorial.html

之前也有人翻譯過,我參考了很多,在這里表示感謝,鏈接是:

http://blog.csdn.net/jgood/article/details/4787670

我有些是逐句翻譯的,有些是省略了,只翻譯出自己認為重要的地方。還有些地方我也有困惑,不知道如何翻譯是好。有些我認為用的不多的主題,比如代理,簡略了。也許翻譯久了有些枯燥了,漸漸后面的多的不如剛開始的好。有上面諸多缺點,希望我翻譯的不要誤導到大家。能夠大家一個參考,我就感到足矣。

 

 

 

名稱

libcurl 的編程教程

目標

本文檔介紹使用libcurl編程的一般原則和一些基本方法。本文主要是介紹 c 語言的調用接口,同時也可能很好的適用於其他類 c 語言的接口。

跨平台的可移植代碼

libcurl庫背后的開發人員投入了相當大的努力確保libcurl可以在很多不同的系統和環境里工作。

全局的准備

程序必須初始化一些libcurl的全局函數。這意味着不管你准備使用libcurl多少次,你都應該,且只初始化一次。當你的程序開始的時候,使用

curl_global_init()

這個函數需要一個參數來告訴它如何來初始化。使用CURL_GLOBAL_ALL ,它將用通常是比較好的默認參數來初始化所有已知的內部子模塊。還有兩個可選值:

CURL_GLOBAL_WIN32

這個參數只被用在windows 操作系統上。它讓libcurl初始化win32套接字的的東西。沒有正確的初始化,你的程序將不能正確的使用套接字。你應該只為每個程序做一次這樣的操作,如果你的程序的其他庫這樣了,你就不要再讓libcurl這樣做。

CURL_GLOBAL_SSL

這個參數會使libcurl具有SSL的能力。你應該只為每個程序做一次這樣的操作,如果你的程序的其他庫這樣了,你就不要再讓libcurl這樣做。

libcurl有個默認的保護機制,檢測如果curl_global_init沒有在curl_easy_perform之前被調用,那么libcurl會猜測該使用的初始化模式來執行程序。請注意,依靠默認的保護機制來這么做一點都不好。

當程序不再使用libcurl,請調用curl_global_cleanup函數來對應初始化函數所做的工作,它會做逆向的工作來清理curl_global_init所初始化的資源。

請避免重復的調用curl_global_init curl_global_cleanup。他們每個僅被調用一次。

libcurl所支持的功能

確定libcurl所提供功能的最佳辦法是在運行的時候而不是在編譯的時候。通過調用curl_version_info函數,然后查看返回信息的結構體,你知道當前的libcurl版本所支持的功能了。

使用libcurl的簡單接口

首先介紹libcurl的簡單接口(easy interface),這些接口都有curl_easy的前綴。 libcurl的最近版本還提供了復雜接口(multi interface)。更多關系該接口的信息將另作討論,為了更好的理解復雜接口,你仍然需要先了解簡單接口。

為了使用簡單接口,你先需要創建一個簡單接口的句柄。每一個簡單接口的數據通信都需要這個句柄。一般來說,你需要為每個准備傳輸數據的線程使用一個句柄。但你絕不能在多線程里共享相同的句柄。

獲取簡單句柄

easyhandle = curl_easy_init();

這個函數返回一個簡單接口句柄。接下來的操作力,可以在這個`easyhandle設置各種選項。在隨后的一個或者多次數據傳輸中,句柄只是一個邏輯實體。 通過 curl_easy_setopt設置句柄的屬性和選項,它們控制隨后的數據傳輸。這些屬性和選項的設置一個保存在句柄里直到它再次被設置為其他值。多次網絡請求使用相同的句柄,它們的句柄選擇和屬性也是相同的。

很多用於設置libcurl屬性都是字符串,一個指向一段以0結尾數據。當你用字符串設置 curl_easy_setopt(libcurl會復制這個字符串的一個副本,所以你在設置后不用再保存那個字符串的內存。

在句柄上最經常設置的屬性是URL。你可以這樣設置它

curl_easy_setopt(handle, CURLOPT_URL, "http://domain.com/");

假如你希望得到指定URL上的遠程主機的數據資源到本地。如果你想自己處理得到的數據而不是直接顯示在標准輸出里,你可以寫一個符合下面原型的函數

size_t write_data(void *buffer, size_t size, size_t nmemb, void *userp);

你可以用類似下面這樣的代碼來控制libcurl將得到的數據傳遞到你寫的函數里

curl_easy_setopt(easyhandle, CURLOPT_WRITEFUNCTION, write_data);

你還可以控制你的回調函數第四個參數得到的數據,用這樣的函數原型

curl_easy_setopt(easyhandle, CURLOPT_WRITEDATA, &internal_struct);

使用這種原型,你可以很容易在你程序和被libcurl調用函數之間傳遞本地數據。使用 CURLOPT_WRITEDATA,libcurl不會處理你所傳遞的數據。

假如你沒用使用CURLOPT_WRITEFUNCTION設置回調函數,libcurl會有默認的處理。它只是把接收到的數據輸出到標准輸出里。你可以使用傳遞一個FILE *的打開文件參數,設置默認處理函數CURLOPT_WRITEDATA ,它把得到的數據存放在一個文件里。

這里有一個與平台有關的缺陷。有時候libcurl不能操作一個唄程序所打開的文件。如果你用CURLOPT_WRITEDATA給默認的回調函數傳遞一個打開的文件,程序可能崩潰。(CURLOPT_WRITEDATA 原來的名稱為CURLOPT_FILE,它們是同樣的工作機理)

如果你以 win32 dll的方式使用libcurl,如果你設置了,CURLOPT_WRITEDATA ,也必須用CURLOPT_WRITEFUNCTION ,否則你會遇到程序崩潰。 還有其他很多的選項可以設置,我們放在后面再詳細討論,接着往下看

success = curl_easy_perform(easyhandle);

curl_easy_perform函數將會連接遠程的站點,發送必要的命令和接受傳輸的數據。當它收到了數據,它就會調用我們先前設置的回調函數。這個函數可能一次得到一個字節或者幾千個字節。libcurl會盡可能多的,盡可能快的傳回數據。我們回調函數返回它所得到數據的大小。如果返回的數據大小與傳遞給它數據大小不一致,libcurl將會終止操作,並返回一個錯誤代碼。

當數據傳輸完成,curl_easy_perform返回一個代碼來告訴你是否成功。如果你僅返回一個代碼還不夠,你可以使用CURLOPT_ERRORBUFFER ,libcurl緩存許多可讀的錯誤信息

如果你還想傳輸其他數據,已有的句柄可以多次使用。記住,鼓勵你使用現有的句柄來傳輸其他數據,libcurl會嘗試已經先前已經建立好的連接。

對於某些協議,下載文件可能涉及到很多復雜的協議用來記錄信息,設置傳輸模式,更改當前的目錄並最終傳遞數據。傳遞一個文件的URLlibcurl會為你掌管所有細節,把文件從一台機器移動到兩一台機器。

多線程問題

最基本的原則是絕對不要同時在多個線程之間共享一個libcurl的句柄。確保任何時候一個句柄只是在一個線程里使用。你可以在多個線程之間傳遞句柄,但是你不能使用。

libcurl是線程安全的,除了以下兩種情況:信號量和SSL/TLS句柄。信號量用於超時失效名字解析(在域名解析的時候)

如果你通過多線程方式來訪問HTTPS 或者 FTPS 網址,你可以使用底層的SSL庫,多線程庫。這些庫可能有它們獨有的要求,你要多加注意詳細參考

        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 它宣稱是線程安全,沒有任何特殊的要求。 PolarSSL 未知。yassl 未知。axTLS 未知。

當你使用多線程的時候,你應當為所有的句柄設置CURLOPT_NOSIGNAL 選項。所有的時候都可以工作正常除了DNS查詢超時的時候。 同樣,CURLOPT_DNS_USE_GLOBAL_CACHE 也不是線程安全的。

libcurl實際無法工作

總是有各種原因導致網絡傳輸失敗。你可能設置錯誤的libcurl選項,誤解了libcurl某些選項的實際作用,或者遠程服務器返回libcurl一個非標准的應答。

當發生錯誤的時候,這里有一個黃金法:設置CURLOPT_VERBOSE 選項為1。這將導致libcurl顯示出所有發送的實體協議的細節,或者還有一些內部的信息和一些收到協議的數據(尤其是 FTP)。如果你使用HTTP ,CURLOPT_HEADER設為1,請求頭/響應頭也會被輸出,這些頭信息將出現在消息的內容中。

如果CURLOPT_VERBOSE 還不夠,你設置CURLOPT_DEBUGFUNCTION來調試你的數據。

上傳數據到遠程站點

libcurl盡量保持與協議無關性,就是上傳文件到遠程的FTP跟用PUT方式上傳數據到HTTP服務器和非常類似的。 我們寫一個程序,很可能想libcurl按照我們的要求上傳數據。我需要設置如下函數原型的讀數據的回調函數

size_t function(char *bufptr, size_t size, size_t nitems, void *userp);

bufptr 參數指向一段准備上傳數據的緩沖區,nitem是這段緩沖區的大小,userp是一個用戶自定義指針,libcurl不對該指針作任何操作,它只是簡單的傳遞該指針。可以使用該指針在應用程序與libcurl之間傳遞信息。

        curl_easy_setopt(easyhandle, CURLOPT_READFUNCTION, read_function);
        curl_easy_setopt(easyhandle, CURLOPT_READDATA, &filedata);
        Tell libcurl that we want to upload:
        curl_easy_setopt(easyhandle, CURLOPT_UPLOAD, 1L);

有幾個協議將不能正常工作當上傳的時候沒有告訴上傳文件的大小。所以設置上傳文件的大小請使用CURLOPT_INFILESIZE_LARGE

         /* in this example, file_size must be an curl_off_t variable */ 
         curl_easy_setopt(easyhandle, CURLOPT_INFILESIZE_LARGE, file_size);

當你調用curl_easy_perform()的時候,libcurl會執行所有的必要動作,當開始上傳的時候,它會調用我的回調函數。程序會盡可能多,盡可能快的的上傳數據。回調函數返回寫入緩沖區的數據的大小。返回0的時候就表示上傳結束了。

密碼

許多協議要求用戶名和密碼才能下載或者上傳你所選擇的數據。libcurl提供了指定的幾種方法。

許多協議都支持用戶名和密碼包含在指定的URL里。libcurl會檢測並相應的使用它們。可以按照這樣的格式寫

protocol://user:password@example.com/path/

如果你的用戶名和密碼需要一些奇特的字符,你應該使用URLd編碼,像%XX,其中XX是兩位十六進制的數字。

libcurl同事也提供了一個設置各種類型密碼的選項。設置CURLOPT_USERPWD 選項如下

curl_easy_setopt(easyhandle, CURLOPT_USERPWD, "myname:thesecret");

在某些情況下可能會多次用到用戶名和密碼,可以使用代碼來驗證身份。libcurl提供一個CURLOPT_PROXYUSERPWD選項來實現這種功能,CURLOPT_USERPWD 選項很類似

curl_easy_setopt(easyhandle, CURLOPT_PROXYUSERPWD, "myname:thesecret");

HTTP 認證

先前的章節我們顯示了如何為需要驗證的URL設置用戶和密碼。當我們使用HTTP協議的時候,客戶端有許多不同的向服務器發送身份驗證。最基本的HTTP認證為Basic認證,它發送base64編碼的明文用戶和密碼,這不安全。 如今,libcrul支持使用Basic, Digest, NTLM, Negotiate, GSS-Negotiate and SPNEGO等方式的認證,你可以設置CURLOPT_HTTPAUTH 告訴libcurl使用那種認證。

curl_easy_setopt(easyhandle, CURLOPT_HTTPAUTH, CURLAUTH_DIGEST);

當你想代理服務器認證的時候,你可以使用

        CURLOPT_PROXYAUTH:

        curl_easy_setopt(easyhandle, CURLOPT_PROXYAUTH, CURLAUTH_NTLM);

你可以組合多種認證方式,libcurl會以合理的方式處理 curl_easy_setopt(easyhandle, CURLOPT_HTTPAUTH, CURLAUTH_DIGEST|CURLAUTH_BASIC);

為了方便起見,你還可以使用CURLAUTH_ANY,它允許libcurl使用任何想要的方法。

HTTP POSTing

HTML最簡單的也是最常見的POST是使用<form>這個標簽來實現的。我們提供了一個指向數據段 指針,然后告訴licurl把它發送到遠程站點:

        char *data="name=daniel&project=curl";
        curl_easy_setopt(easyhandle, CURLOPT_POSTFIELDS, data);
        curl_easy_setopt(easyhandle, CURLOPT_URL, "http://posthere.com/");

        curl_easy_perform(easyhandle); /* post away! */

是不是很簡單。當你設置了CURLOPT_POSTFIELDS選項,它會自動切換句柄來POST接下的請求。

如果你想發送二進制的數據,需要設置一下數據頭,用來知名數據的類型和數據的大小。如下

        struct curl_slist *headers=NULL;
        headers = curl_slist_append(headers, "Content-Type: text/xml");

        /* post binary data */ 
        curl_easy_setopt(easyhandle, CURLOPT_POSTFIELDS, binaryptr);

        /* set the size of the postfields data */ 
        curl_easy_setopt(easyhandle, CURLOPT_POSTFIELDSIZE, 23L);

        /* pass our list of custom made headers */ 
        curl_easy_setopt(easyhandle, CURLOPT_HTTPHEADER, headers);

        curl_easy_perform(easyhandle); /* post away! */

        curl_slist_free_all(headers); /* free the header list */

還有一種是multi-part 表單提交,這被認為是一種更好的方法提交二進制的數據。libcrul同樣也支持這種方法。下面是例子:

        struct curl_httppost *post=NULL;
        struct curl_httppost *last=NULL; 
        curl_formadd(&post, &last,   CURLFORM_COPYNAME, "name",   CURLFORM_COPYCONTENTS, "daniel", CURLFORM_END);
        curl_formadd(&post, &last,   CURLFORM_COPYNAME, "project",   CURLFORM_COPYCONTENTS, "curl", CURLFORM_END);
        curl_formadd(&post, &last,   CURLFORM_COPYNAME, "logotype-image",   CURLFORM_FILECONTENT, "curl.png",
        CURLFORM_END);

        /* Set the form info */ 
        curl_easy_setopt(easyhandle, CURLOPT_HTTPPOST, post);

        curl_easy_perform(easyhandle); /* post away! */

        /* free the post data again */ 
        curl_formfree(post);

libcurl還允許你在每個定義的部分設置頭。下面就是例子

        struct curl_slist *headers=NULL; 
        headers = curl_slist_append(headers, "Content-Type: text/xml");

        curl_formadd(&post, &last,   CURLFORM_COPYNAME, "logotype-image",   CURLFORM_FILECONTENT, "curl.xml",
        CURLFORM_CONTENTHEADER, headers,   CURLFORM_END);

        curl_easy_perform(easyhandle); /* post away! */

         curl_formfree(post); 
         /* free post */ 
         curl_slist_free_all(headers); 
         /* free custom header list */

所有對簡單接口句柄都是"粘的",這些屬性會一直保持不變除非你在調用 curl_easy_perform之前改過。通過將CURLOPT_HTTPGET設為1可以使簡單接口句柄回到最原始的狀態:

curl_easy_setopt(easyhandle, CURLOPT_HTTPGET, 1L);

只設置CURLOPT_POSTFIELDS 的只為""或者NULL是不會停止libcurlPOST請求的,這只會做提交空數據的POST

顯示進度

通過將CURLOPT_NOPROCESS設置為0開啟進度支持。該選項默認值為1。對大多數應用程序,我們需要提供一個進度顯示回調。libcurl會不定期的將當前傳輸的進度通過回調函數告訴你的程序。

        //回調函數原型
        int progress_callback(void *clientp,   double dltotal,  
        double dlnow,   double ultotal,   double ulnow);

        /*通過CURLOPT_PROGRESSFUNCTION注冊該回調函數。
        參數clientp是一個用戶自定義指針,應用程序通過CURLOPT_PROCESSDATA
        屬性將該自定義指定傳遞給libcurllibcurl對該參數不作任何處理,
        只是簡單將其傳遞給回調函數。*/

libcurlc++

只有一件需要特別注意的是回調函數不能使類的非靜態成員函數。如

        class AClass 
        {  
                static size_t write_data(void *ptr, size_t size, size_t nmemb,  
                void *ourpointer)  
                {  
                        /* do what you want with the data */  
                } 
        }

代理

略過,有興趣請參考原文。

持久化的好處

當需要發送多次請求時,應該重復使用easy handle

每次執行完curl_easy_performlicurl會繼續保持與服務器的連接。接下來的請求可以使用這個連接而不必創建新的連接(如果目標主機是同一個的話)。這樣可以減少網絡開銷。 即使連接被釋放了,libcurl也會緩存這些連接的會話信息,這樣下次再連接到目標主機上時,就可以使用這些信息,從而減少重新連接所需的時間。

FTP連接可能會被保存較長的時間。因為客戶端要與FTP服務器進行頻繁的命令交互。對於有訪問人數上限的FTP服務器,保持一個長連接,可以使你不需要排除等待,就直接可以與FTP服務器通信。

libcurl會緩存DNS的解析結果。

在今后的libcurl版本中,還會添加一些特性來提高數據通信的效率。 每個簡單接口句柄都會保存最近使用的幾個連接,以備重用。默認是5個。可以通過CURLOPT_MAXCONNECTS屬性來設置保存連接的數量。

如果你不想重用連接,將CURLOPT_FRESH_CONNECT屬性設置為1。這樣每次提交請求時,libcurl都會先關閉以前創建的連接,然后重新創建一個新的連接。也可以將CURLOPT_FORBID_REUSE設置為1,這樣每次執行完請求,連接就會馬上關閉。

libcurl使用HTTP消息頭

當使用libcurl發送http請求時,它會自動添加一些http頭。我們可以通過CURLOPT_HTTPHEADER屬性手動替換、添加或刪除相應的HTTP消息頭。

例外:以POST的方式向HTTP服務器提交請求時,libcurl會設置該消息頭為"100-continue",它要求服務器在正式處理該請求之前,返回一個"OK"消息。如果POST的數據很小,libcurl可能不會設置該消息頭。

自定義的操作

當前越來越多的協議都構建在HTTP協議之上(如:soap),這主要歸功於HTTP的可靠性,以及被廣泛使用的代理支持(可以穿透大部分防火牆)。 這些協議的使用方式與傳統HTTP可能有很大的不同。對此,libcurl作了很好的支持。

cookies

cookie是一個鍵值對的集合,HTTP服務器發給客戶端的cookie,客戶端提交請求的時候,也會將cookie發送到服務器。服務器可以根據cookie來跟蹤用戶的會話信息。cookie有過期時間,超時后cookie就會失效。cookie有域名和路徑限制,cookie只能發給指定域名和路徑的HTTP服務器。

libcurl中,可以通過CURLOPT_COOKIE屬性來設置發往服務器的cookie

curl_easy_setopt(easyhandle, CURLOPT_COOKIE, "name1=var1; name2=var2;");

在實在的應用場景中,你可能需要保存服務器發送給你的cookie,並在接下來的請求中,把這些cookie一並發往服務器。所以,可以把上次從服務器收到的所有響應頭信息保存到文本文件中,當下次需要向服務器發送請求時,通過CURLOPT_COOKIEFILE屬性告訴libcurl從該文件中讀取cookie信息。 設置CURLOPT_COOKIEFILE屬性意味着激活libcurlcookie parser。在cookie parser被激活之前,libcurl忽略所以之前接收到的cookie信息。cookie parser被激活之后,cookie信息將被保存內存中,在接下來的請求中,libcurl會自動將這些cookie信息添加到消息頭里,你的應用程序不需要做任何事件。大多數情況下,這已經足夠了。需要注意的是,通過CURLOPT_COOKIEFILE屬性來激活cookie parser,給CURLOPT_COOKIEFILE屬性設置的一個保存cookie信息的文本文件路徑,可能並不需要在磁盤上物理存在。 如果你需要使用NetScape或者FireFox瀏覽器的cookie文件,你只要用這些瀏覽器的cookie文件的路徑來初始化CURLOPT_COOKIEFILE屬性,libcurl會自動分析cookie文件,並在接下來的請求過程中使用這些cookie信息。 libcurl甚至能夠把接收到的cookie信息保存成能被Netscape/Mozilla的瀏覽器所識別的cookie文件。通過把這些稱為cookie-jar。通過設置CURLOPT_COOKIEJAR選項,在調用curl_easy_cleanup釋放easy handle的時候,所有的這些cookie信息都會保存到cookie-jar文件中。這就使得cookie信息能在不同的easy handle甚至在瀏覽器之間實現共享。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM