linux c libcurl的簡單使用

curl是Linux下一個很是著名的下載庫,經過這個庫,能夠很簡單的實現文件的下載等操做。
看一個簡單的例子: html

#include <curl/curl.h>
#include <stdio.h>
#include <string.h>

CURL *curl;
CURLcode res;

size_t write_data(void *ptr, size_t size, size_t nmemb, void *stream)
{
    if (strlen((char *)stream) + strlen((char *)ptr) > 999999) return 0;
    strcat(stream, (char *)ptr);
    return size*nmemb;
}

char *down_file(char *filename)
{
    static char str[10000000];
    strcpy(str,"");
    //return 「」;

    curl_easy_setopt(curl, CURLOPT_URL, filename); //設置下載地址

    curl_easy_setopt(curl, CURLOPT_TIMEOUT, 3);//設置超時時間

    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_data);//設置寫數據的函數

    curl_easy_setopt(curl, CURLOPT_WRITEDATA, str);//設置寫數據的變量

    res = curl_easy_perform(curl);//執行下載

    str[9999999] = '\0';
    if(CURLE_OK != res) return NULL;//判斷是否下載成功

    return str;
}

int main()
{
    char url[200];
    curl = curl_easy_init();//對curl進行初始化

    char *result;
    while(fgets(url, 200, stdin)){
        result = down_file(url);
        if (result) puts(result);
        else puts("Get Error!");
        printf("\nPlease Input a url:");

    }
    curl_easy_cleanup(curl);//釋放curl資源


    return 0;
}

下面是轉載的curl詳細使用: node

curl->libcurl的手冊能夠查看 api

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的功能和接口信息,能夠在相關的主頁上查閱。 cookie

編譯源碼

有不少種不一樣的方式來編譯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();

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版本信息的截圖:
pic1

使用easy interface

首先介紹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. size_t process_data(void *buffer, size_t size, size_t nmemb, void *user_p)
  13. {
  14.     FILE *fp = (FILE *)user_p;
  15.     size_t return_size = fwrite(buffer, size, nmemb, fp);
  16.     cout << (char *)buffer << endl;
  17.     return return_size;
  18. }

  19. int main(int argc, char **argv)
  20. {
  21.     // 初始化libcurl

  22.     CURLcode return_code;
  23.     return_code = curl_global_init(CURL_GLOBAL_WIN32);
  24.     if (CURLE_OK != return_code)
  25.     {
  26.         cerr << "init libcurl failed." << endl;
  27.         return -1;
  28.     }

  29.     // 獲取easy handle

  30.     CURL *easy_handle = curl_easy_init();
  31.     if (NULL == easy_handle)
  32.     {
  33.         cerr << "get a easy handle failed." << endl;
  34.                   curl_global_cleanup();
  35.         return -1;
  36.     }

  37.     FILE *fp = fopen("data.html", "ab+");    //

  38.     // 設置easy handle屬性

  39.     curl_easy_setopt(easy_handle, CURLOPT_URL, http://blog.csdn.net/JGood);

  40.     curl_easy_setopt(easy_handle, CURLOPT_WRITEFUNCTION, &process_data);
  41.     curl_easy_setopt(easy_handle, CURLOPT_WRITEDATA, fp);

  42.     // 執行數據請求

  43.     curl_easy_perform(easy_handle);    

  44.     // 釋放資源


  45.     fclose(fp);
  46.     curl_easy_cleanup(easy_handle);
  47.     curl_global_cleanup();

  48.     return 0;
  49. }
多線程問題

首先一個基本原則就是:絕對不該該在線程之間共享同一個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經過回調函數讀取要上傳的數據。(若是要從遠程服務器下載數據,能夠經過回調來保存接收到的數據。)回調函數的原型以下:

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

bufptr指針表示緩衝區,用於保存要上傳的數據,size * nitems是緩衝區數據的長度,userp是一個用戶自定義指針,libcurl不對該指針做任何操做,它只是簡單的傳遞該指針。可使用該指針在應用程序與libcurl之間傳遞信息。

4. 註冊回調函數,設置自定義指針。語法以下:

// 註冊回調函數  curl_easy_setopt(easy_handle, CURLOPT_READFUNCTION, read_function); // 設置自定義指針  curl_easy_setopt(easy_handle, CURLOPT_READDATA, &filedata);

5. 告訴libcurl,執行的是上傳操做。

curl_easy_setopt(easy_handle, CURLOPT_UPLOAD, 1L);

有些協議在沒有預先知道上傳文件大小的狀況下,可能沒法正確判斷上傳是否結束,因此最好預先使用CURLOPT_INFILESIZE_LARGE屬性:告訴它要上傳文件的大小:

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

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. int main(int argc, char **argv)
  9. {
  10.     // 初始化libcurl

  11.     CURLcode code;
  12.     code = curl_global_init(CURL_GLOBAL_WIN32);
  13.     if (code != CURLE_OK)
  14.     {
  15.         cerr << "init libcurl failed." << endl;
  16.         return -1;
  17.     }

  18.     FILE *fp = fopen("a.html", "rb");
  19.     if (NULL == fp)
  20.     {
  21.         cout << "can't open file." << endl;
  22.         curl_global_cleanup();
  23.         return -1;
  24.     }

  25.     // 獲取文件大小

  26.     fseek(fp, 0, 2);
  27.     int file_size = ftell(fp);
  28.     rewind(fp);

  29.     // 獲取easy handle


  30.     CURL *easy_handle = NULL;
  31.     easy_handle = curl_easy_init();
  32.     if (NULL == easy_handle)
  33.     {
  34.         cerr << "get a easy handle failed." << endl;
  35.         fclose(fp);
  36.         curl_global_cleanup();
  37.         return -1;
  38.     }

  39.     // 設置eash handle屬性

  40.     curl_easy_setopt(easy_handle, CURLOPT_URL, ftp://127.0.0.1/upload.html);

  41.     curl_easy_setopt(easy_handle, CURLOPT_UPLOAD, 1L);
  42.     curl_easy_setopt(easy_handle, CURLOPT_READFUNCTION, &read_data);
  43.     curl_easy_setopt(easy_handle, CURLOPT_READDATA, fp);
  44.     curl_easy_setopt(easy_handle, CURLOPT_INFILESIZE_LARGE, file_size);

  45.     // 執行上傳操做

  46.     code = curl_easy_perform(easy_handle);
  47.     if (code == CURLE_OK)
  48.     {
  49.         cout << "upload successfully." << endl;
  50.     }

  51.     // 釋放資源

  52.     fclose(fp);
  53.     curl_easy_cleanup(easy_handle);
  54.     curl_global_cleanup();

  55.     return 0;
  56. }
關於密碼

客戶端向服務器發送請求時,許多協議都要求提供用戶名與密碼。libcurl提供了多種方式來設置它們。

一些協議支持在URL中直接指定用戶名和密碼,相似於: protocol://user:password@example.com/path/。libcurl能正確的識別這種URL中的用戶名與密碼並執行 相應的操做。若是你提供的用戶名和密碼中有特殊字符,首先應該對其進行URL編碼。

也能夠經過CURLOPT_USERPWD屬性來設置用戶名與密碼。參數是格式如 「user:password 」的字符串:

curl_easy_setopt(easy_handle, CURLOPT_USERPWD, "user_name:password");

(下面這幾段文字我理解地模模糊糊)有時候在訪問代理服務器的時候,可能時時要求提供用戶名和密碼進行用戶身份驗證。這種狀況下,libcurl提供了另外一個屬性CURLOPT_PROXYUSERPWD:

curl_easy_setopt(easy_handle, CURLOPT_PROXYUSERPWD, "user_name:password");

在UNIX平臺下,訪問FTP的用戶名和密碼可能會被保存在$HOME/.netrc文件中。libcurl支持直接從這個文件中獲取用戶名與密碼:

curl_easy_setopt(easy_handle, CURLOPT_NETRC, 1L);

在使用SSL時,可能須要提供一個私鑰用於數據安全傳輸,經過CURLOPT_KEYPASSWD來設置私鑰:

curl_easy_setopt(easy_handle, CURLOPT_KEYPASSWD, "keypassword");
HTTP驗證

上一章介紹瞭如何在libcurl中,對須要身份驗證的URL設置用戶名與密碼。在使用HTTP協議時,客戶端有不少種方式向服務器提供驗證信息。默認的 HTTP驗證方法是」Basic」,它將用戶名與密碼以明文的方式、經Base64編碼後保存在HTTP請求頭中,發往服務器。固然這不太安全。

當前版本的libcurl支持的驗證方法有:basic, Digest, NTLM, Negotiate, GSS-Negotiate and SPNEGO。(譯者感嘆:搞Web這麼多年,盡然不知道這些Http的驗證方式,實在慚愧。)能夠經過CURLOPT_HTTPAUTH屬性來設置具體 的驗證方式:

curl_easy_setopt(easy_handle, CURLOPT_HTTPAUTH, CURLAUTH_DIGEST);

向代理服務器發送驗證信息時,能夠經過CURLOPT_PROXYAUTH設置驗證方式:

curl_easy_setopt(easy_handle, CURLOPT_PROXYAUTH, CURLAUTH_NTLM);

也能夠同時設置多種驗證方式(經過按位與), 使用‘CURLAUTH_ANY‘將容許libcurl能夠選擇任何它所支持的驗證方式。經過CURLOPT_HTTPAUTH或 CURLOPT_PROXYAUTH屬性設置的多種驗證方式,libcurl會在運行時選擇一種它認爲是最好的方式與服務器通訊:

curl_easy_setopt(easy_handle, CURLOPT_HTTPAUTH, CURLAUTH_DIGEST|CURLAUTH_BASIC); //  curl_easy_setopt(easy_handle, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
HTTP Post

這一章介紹如何使用libcurl以Post方式向HTTP服務器提交數據。

方法一,也是最簡單的方式,就像html中使用

標籤提交數據同樣,只需向libcurl提供一個包含數據的字符串便可。下面是筆者學習過程當中的一個demo程序:
  1. int main(int argc, char **argv)
  2. {
  3.     code = curl_global_init(CURL_GLOBAL_WIN32);
  4.     CURL *easy_handle = curl_easy_init();

  5.     curl_easy_setopt(easy_handle, CURLOPT_URL, http://localhost:2210/Default.aspx);

  6.     // 單個域post

  7.     curl_easy_setopt(easy_handle, CURLOPT_POSTFIELDS, "name=jgood&address=hangzhou");
  8.     code = curl_easy_perform(easy_handle);

  9.     curl_easy_cleanup(easy_handle);
  10.     curl_global_cleanup();

  11.     return 0;
  12. }

在asp.net Web服務器上跟蹤調試,獲得客戶程序提交上來的數據,下面是截圖:pic1

上面的代碼夠簡單吧~_~ 有時候,咱們須要提交一些二進制數據到HTTP服務器,使用方法一就不行了,由於方法一中實際提交的是一個字符串,字符串遇到\0就表示結束了。因此在上 傳二進制數據的時候,必須明確的告訴libcurl要提交的數據的長度。在上傳二進制數據的時候,還應該設置提交的Content-Type頭信息。下面 的示例代碼:

  1. int main(int argc, char **argv)
  2. {
  3.     curl_global_init(CURL_GLOBAL_WIN32);
  4.     CURL *easy_handle = curl_easy_init();

  5.     // 上傳二進制數據

  6.     char data[] = { 1, 0, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0 };
  7.     curl_slist *http_headers = NULL;
  8.     http_headers = curl_slist_append(http_headers, "Content-Type: text/xml");

  9.     curl_easy_setopt(easy_handle, CURLOPT_HTTPHEADER, http_headers);
  10.     curl_easy_setopt(easy_handle, CURLOPT_URL, http://localhost:2210/Default.aspx);

  11.     curl_easy_setopt(easy_handle, CURLOPT_POSTFIELDS, data);
  12.     curl_easy_setopt(easy_handle, CURLOPT_POSTFIELDSIZE, sizeof(data));

  13.     curl_easy_perform(easy_handle);

  14.     curl_slist_free_all(http_headers);
  15.     curl_easy_cleanup(easy_handle);
  16.     curl_global_cleanup();

  17.     return 0;
  18. }

在asp.net Web服務器上跟蹤調試,獲得客戶程序提交上來的二進制數據,下面是截圖:pic1

上面介紹的兩種方式,能夠完成大部分的HTTP POST操做。但上面的兩種方式都不支持multi-part formposts。Multi-part formposts被認爲是提交二進制數據(或大量數據)的更好方法,能夠在RFC1867, RFC2388中找到他們的定義。何爲Multi-part?其實,就我理解,就是在Post提交的時候,有不一樣的數據單元,每一個單元有本身的名稱與內 容,內容能夠是文本的,也能夠是二進制的。同時,每一個數據單元均可以有本身的消息頭,MIME類型,這些數據單元組成一個鏈表,提交到HTTP服務器。 libcurl提供了方便的api用於支持multi-part formposts。使用curl_formadd函數,能夠添加不一樣的數據數據單元,而後提交到服務器。下面是一個multi-part formposts的例子(更詳細的使用,請參考:http://curl.haxx.se/libcurl/c/curl_formadd.html ):

  1. int main()
  2. {
  3. curl_global_init(CURL_GLOBAL_WIN32);
  4. CURL *easy_handle = curl_easy_init();

  5.     // 使用multi-parts form post

  6.     curl_easy_setopt(easy_handle, CURLOPT_URL, http://localhost:2210/Default.aspx);

  7.     curl_httppost *post = NULL;
  8.     curl_httppost *last = NULL;    

  9.     // 文本數據

  10.     curl_formadd(&post, &last, CURLFORM_COPYNAME, "name", CURLFORM_COPYCONTENTS, "JGood", CURLFORM_END);
  11.     curl_formadd(&post, &last, CURLFORM_COPYNAME, "address", CURLFORM_COPYCONTENTS, "HangZhou", CURLFORM_END);

  12.     // 文本文件中的數據

  13.     curl_formadd(&post, &last, CURLFORM_COPYNAME, "file", CURLFORM_FILECONTENT, "ReadMe.txt", CURLFORM_END);
  14.     curl_easy_setopt(easy_handle, CURLOPT_HTTPPOST, post);
  15.     curl_easy_perform(easy_handle);

  16.     curl_formfree(post);
  17.     curl_easy_cleanup(easy_handle);
  18.     curl_global_cleanup();

  19.     return 0;
  20. }

最後要說明的是,全部在easy handle上設置的屬性都是」sticky」的,什麼意思?就是說在easy handle上設置的屬性都將被保存,即便執行完curl_easy_perform以後,這些屬性值仍然存在。經過將CURLOPT_HTTPGET設 爲1可使easy handle回到最原始的狀態:

curl_easy_setopt(easy_handle, CURLOPT_HTTPGET, 1L);
顯示進度

libcurl支持通訊過程當中的進度控制。經過將CURLOPT_NOPROCESS設置爲0開啓進度支持。該選項默認值爲1。對大多數應用程序,咱們需 要提供一個進度顯示回調。libcurl會不按期的將當前傳輸的進度經過回調函數告訴你的程序。回調函數的原型以下:

int progress_callback(void *clientp, double dltotal, double dlnow, double ultotal, double ulnow);

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

在C++中使用libcurl

在C++中使用libcurl跟在C語言中沒有任何區別,只有一個地方要注意:回調函數不能是類的非靜態成員函數。例如:

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

什麼是代理?Merrian-Webster的解釋是:一個經過驗證的用戶扮演另外一個用戶。今天,代理已經被普遍的使用。許多公司提供網絡代理服務器,容許員工的網絡客戶端訪問、下載文件。代理服務器處理這些用戶的請求。

libcurl支持SOCKS和HTTP代理。使用代理,libcurl會把用戶輸入的URL提交給代理服務器,而不是直接根據URL去訪問遠程資源。

當前版本的libcurl並不支持SOCKS代理的全部功能。

對於HTTP代理來講,即便請求的URL不是一個合法的HTTP URL(比方你提供了一個ftp的url),它仍然會先被提交到HTTP代理。

代理選項

CURLOPT_PROXY屬性用於設置libcurl使用的代理服務器地址:

curl_easy_setopt(easy_handle, CURLOPT_PROXY, "proxy-host.com:8080");

能夠把主機名與端口號分開設置:

curl_easy_setopt(easy_handle, CURLOPT_PROXY, "proxy-host.com");  curl_easy_setopt(easy_handle, CURLOPT_PROXYPORT, "8080"); // 端口號是用字符串仍是整數??

有些代理服務器要求用戶經過驗證以後才容許接受其請求,此時應該先提供驗證信息:

curl_easy_setopt(easy_handle, CURLOPT_PROXYUSERPWD, "user:password");

還要告訴libcurl使用的代理類型(若是沒有提供,libcurl會認爲是HTTP代理):

curl_easy_setopt(easy_handle, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS4);
環境變量

對於有些協議,libcurl會自動檢測並使用一些環境變量,並根據這些環境變量來肯定要使用的代理服務器。這些環境變量的名稱格式通常是」 [protocol]_proxy」(注意小寫)。例如輸入一個HTTP的URL,那麼名稱爲」http_proxy」的環境變量就會被檢測是否存在,如 果存在,libcurl會使用該環境變量指定的代理。相同的規則也適用於FTP。

這些環境變量的值的格式必須是這樣的:」[protocol://][user:password@]machine[:port]「。libcurl會忽略掉[protocol://],若是沒有提供端口號,libcurl使用該協議的默認端口。

有兩個比較特殊的環境變量:’all_proxy’與’no_proxy’。若是一個URL所對應的協議,它的環境變量沒有設置,那麼 ‘all_proxy’指定的代理將被使用。’no_proxy’則指定了一個不該被使用的代理主機的列表。例如:no_proxy的值是 ’192.168.1.10′,即便存在http_proxy,它的值也是’192.168.1.10′,’192.168.1.10′也不會被做爲代 理。no_proxy=」*」表示不容許使用任何代理。

顯式地將CURLOPT_PROXY屬性設置爲空,能夠禁止libcurl檢查並使用環境變量來使用代理。

SSL和代理

SSL爲點到點通訊提供安全保障。它包含一些強壯的加密措施和其餘安全檢測,這使得上面講到的代理方式不適用於SSL。除非代理服務器提供專用通道,對進 出該代理服務器的數據不做任何檢測或禁止。經過HTTP代理服務器打開SSL鏈接,意味着代理服務器要直接鏈接到目標主機的指定端口。由於代理服務器對在 專用通道上傳輸的數據的類型毫無所知,因此它每每會使某些機制失效,如緩存機制。許多組織只容許在443端口上建立這種類型的數據通道。

代理通道(Tunneling Through Proxy)

正如上面講到的,要使SSL工做必須在代理服務器建立專用數據通道,一般專用通道只被限制應用於HTTPS。經過HTTP代理在應用程序與目標之間建立一 個專用數據通道,應該預防在該專有通道上執行非HTTP的操做,如進行FTP上傳或執行FTP命令。代理服務器管理員應該禁止非法的操做。

經過CURLOPT_HTTPPROXYTUNNEL屬性來告訴libcurl使用代理通道:

curl_easy_setopt(easy_handle, CURLOPT_HTTPPROXYTUNNEL, 1L);

有時候你想經過代理通道執行日常的HTTP操做,而實際上卻可能使你不通過代理服務器而直接與遠程主機進行交互。libcurl不會代替這種新引入的行爲。

自動配置代理

許多瀏覽器支持自動配置代理,例如NetScape。libcurl並不支持這些。

持久化的好處(Persistence Is The Way to Happiness)

當須要發送屢次請求時,應該重複使用easy handle。

每次執行完curl_easy_perform,licurl會繼續保持與服務器的鏈接。接下來的請求可使用這個鏈接而沒必要建立新的鏈接(若是目標主機是同一個的話)。這樣能夠減小網絡開銷。
即便鏈接被釋放了,libcurl也會緩存這些鏈接的會話信息,這樣下次再鏈接到目標主機上時,就可使用這些信息,從而減小從新鏈接所需的時間。

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

libcurl會緩存DNS的解析結果。

在從此的libcurl版本中,還會添加一些特性來提升數據通訊的效率。
每一個easy handle都會保存最近使用的幾個鏈接,以備重用。默認是5個。能夠經過CURLOPT_MAXCONNECTS屬性來設置保存鏈接的數量。

若是你不想重用鏈接,將CURLOPT_FRESH_CONNECT屬性設置爲1。這樣每次提交請求時,libcurl都會先關閉之前建立的鏈接,而後重 新建立一個新的鏈接。也能夠將CURLOPT_FORBID_REUSE設置爲1,這樣每次執行完請求,鏈接就會立刻關閉。

libcurl使用的HTTP消息頭

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

Host

http1.1(大部分http1.0)版本都要求客戶端請求提供這個信息頭。

Pragma

「no-cache」。表示不要緩衝數據。

Accept

「*/*」。表示容許接收任何類型的數據。

Expect

以POST的方式向HTTP服務器提交請求時,libcurl會設置該消息頭爲」100-continue」,它要求服務器在正式處理該請求以前,返回一個」OK」消息。若是POST的數據很小,libcurl可能不會設置該消息頭。

自定義選項

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

自定義請求方式(CustomRequest)

HTTP支持GET, HEAD或者POST提交請求。能夠設置CURLOPT_CUSTOMREQUEST來設置自定義的請求方式,libcurl默認以GET方式提交請求:

curl_easy_setopt(easy_handle, CURLOPT_CUSTOMREQUEST, "MYOWNREQUEST");
修改消息頭

HTTP協議提供了消息頭,請求消息頭用於告訴服務器如何處理請求;響應消息頭則告訴瀏覽器如何處理接收到的數據。在libcurl中,你能夠自由的添加這些消息頭:

struct curl_slist *headers=NULL; /* init to NULL is important */ headers = curl_slist_append(headers, "Hey-server-hey: how are you?"); headers = curl_slist_append(headers, "X-silly-content: yes"); /* pass our list of custom made headers */  curl_easy_setopt(easyhandle, CURLOPT_HTTPHEADER, headers); curl_easy_perform(easyhandle); /* transfer http */ curl_slist_free_all(headers); /* free the header list */

對於已經存在的消息頭,能夠從新設置它的值:

headers = curl_slist_append(headers, "Accept: Agent-007"); headers = curl_slist_append(headers, "Host: munged.host.line");
刪除消息頭

對於一個已經存在的消息頭,設置它的內容爲空,libcurl在發送請求時就不會同時提交該消息頭:

headers = curl_slist_append(headers, "Accept:");
強制分塊傳輸(Enforcing chunked transfer-encoding)

(這段文字理解可能有誤碼)以非GET的方式提交HTTP請求時,若是設置了自定義的消息頭」Transfer- Encoding:chunked」,libcurl會分塊提交數據,即便要上傳的數據量已經知道。在上傳數據大小未知的狀況下,libcurl自動採用 分塊上傳數據。(譯者注:非GET方式提交請求,提交的數據量每每比較大。)

HTTP版本

每一次http請求,都包含一個表示當前使用http版本的消息頭。libcurl默認使用HTTP 1.1。能夠經過CURLOPT_HTTP_VERSION屬性來設置具體的版本號:

curl_easy_setopt(easy_handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
FTP自定義命令

並非因此的協議都像HTTP那樣,經過消息頭來告訴服務器如何處理請求。對於FTP,你就要使用另外的方式來處理。

發送自定義的命令到ftp服務器,意味着你發送的命令必須是能被ftp服務器理解的命令(FTP協議中定義的命令,參考rfc959)。

下面是一個簡單的例子,在文件傳輸操做操做以前刪除指定文件:

headers = curl_slist_append(headers, "DELE file-to-remove"); /* pass the list of custom commands to the handle */  curl_easy_setopt(easyhandle, CURLOPT_QUOTE, headers); // curl_easy_setopt(easyhandle, CURLOPT_POSTQUOTE, headers); // 在數據傳輸以後操行刪除操做curl_easy_perform(easyhandle); /* transfer ftp data! */ curl_slist_free_all(headers); /* free the header list */

FTP服務器執行命令的順序,同這些命令被添加到列表中順序是一致的。發往服務器的命令列表中,只要有一個命令執行失敗,ftp服務器就會返回一個錯誤代碼,此時libcurl將直接返回CURLE_QUOTE_ERROR,再也不執行剩餘的FTP命令。

將CURLOPT_HEADER設置爲1,libcurl獲取目標文件的信息,並以HTTP消息頭的樣式來輸出消息頭。

FTP自定義CUSTOMREQUEST

使用CURLOPT_CUSTOMREQUEST屬性,能夠向FTP服務器發送命令。」NLST」是ftp默認的列出文件列表的命令。 下面的代碼用於列出FTP服務器上的文件列表:

  1. int main(int argc, char **argv)
  2. {
  3.     curl_global_init(CURL_GLOBAL_WIN32);
  4.     CURL *easy_handle = curl_easy_init();
  5.     curl_easy_setopt(easy_handle, CURLOPT_URL, "ftp://127.0.0.1/");
  6.     curl_easy_setopt(easy_handle, CURLOPT_CUSTOMREQUEST, "NLST");
  7.     curl_easy_perform(easy_handle);

  8.     curl_easy_cleanup(easy_handle);
  9.     curl_global_cleanup();

  10.     return 0;
  11. }
Cookies Without Chocolate Chips

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

cookie以消息頭」Set-Cookie」的形式從HTTP服務器發送到客戶端;客戶端發以消息頭」Cookie」的形式將Cookie提交到 HTTP服務器。爲了對這些東西有個直觀的概念,下圖是FireFox中,使用Firebug跟蹤到的cookie消息頭:
pic1

在libcurl中,能夠經過CURLOPT_COOKIE屬性來設置發往服務器的cookie:

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

下面的例子演示瞭如何使用libcurl發送cookie信息給HTTP服務器,代碼很是的簡單:

  1. int main(int argc, char **argv)
  2. {
  3.     curl_global_init(CURL_GLOBAL_WIN32);
  4.     CURL *easy_handle = curl_easy_init();

  5.     curl_easy_setopt(easy_handle, CURLOPT_URL, http://localhost:2210/Default.aspx);

  6.     curl_easy_setopt(easy_handle, CURLOPT_COOKIE, "name=JGood; address=HangZhou");

  7.     curl_easy_perform(easy_handle);

  8.     curl_easy_cleanup(easy_handle);
  9.     curl_global_cleanup();

  10.     return 0;
  11. }

下圖是在ASP.NET Web服務器上調試時跟蹤到的Cookie數據:pic1

在實在的應用場景中,你可能須要保存服務器發送給你的cookie,並在接下來的請求中,把這些cookie一併發往服務器。因此,能夠把上次從服務器收 到的全部響應頭信息保存到文本文件中,當下次須要向服務器發送請求時,經過CURLOPT_COOKIEFILE屬性告訴libcurl從該文件中讀取 cookie信息。
設置CURLOPT_COOKIEFILE屬性意味着激活libcurl的cookie 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甚至在瀏覽器之間實現共享。

FTP Peculiarities We Need

在使用FTP協議進行數據傳輸的時候,須要建立兩個鏈接。第一個鏈接用於傳輸控制命令,另外一個鏈接用於傳輸數據。(關於FTP的通訊過程,請參考這篇文章:http://www.wangjia.net/bo-blog/post/698/)。 FTP通訊須要建立兩個鏈接這個事實每每被不少人忽略。根據第二個鏈接的發起方是誰,能夠分爲主動模式與被動模式。libcurl對此都提供了支持。 libcurl默認使用被動模式,由於被動模式能夠方便的穿透防火牆,NAT等問題。在被動模式下,libcurl要求ftp服務器打開一個新的端口監 聽,而後libcurl鏈接該端口用於數據傳輸。若是使用主動模式,程序必須告訴FTP服務器你監聽的IP與端口,經過設置 CURLOPT_FTPPORT屬性來完成。

Headers Equal Fun

(這段文字我理解的很模糊,請讀者參考原文)有些協議提供獨立於正常數據的 消息頭、meta-data。正常的數據流裏一般不包括 信息頭和元數據。能夠將CURLOPT_HEADER設置爲1,使信息頭、元數據也能出如今數據流中。libcurl的強大之處在於,它可以從數據流中解 析出消息頭,….

Post Transfer Information

[ curl_easy_getinfo ]

安全考慮

請參考原文,此處略。

使用multi interface同時進行多項傳輸

上面介紹的easy interface以同步的方式進行數據傳輸,curl_easy_perform會一直阻塞到數據傳輸完畢後返回,且一次操做只能發送一次請求,若是要同時發送多個請求,必須使用多線程。
而multi interface以一種簡單的、非阻塞的方式進行傳輸,它容許在一個線程中,同時提交多個相同類型的請求。 在使用multi interface以前,你應該掌握easy interface的基本使用。由於multi interface是創建在easy interface基礎之上的,它只是簡單的將多個easy handler添加到一個multi stack,然後同時傳輸而已。
使用multi interface很簡單,首先使用curl_multi_init()函數建立一個multi handler,而後使用curl_easy_init()建立一個或多個easy handler,並按照上面幾章介紹的接口正常的設置相關的屬性,而後經過curl_multi_add_handler將這些easy handler添加到multi handler,最後調用curl_multi_perform進行數據傳輸。
curl_multi_perform是異步的、非阻塞的函數。若是它返回CURLM_CALL_MULTI_PERFORM,表示數據通訊正在進行。

經過select()來操做multi interface將會使工做變得簡單(譯者注:其實每一個easy handler在低層就是一個socket,經過select()來管理這些socket,在有數據可讀/可寫/異常的時候,通知應用程序)。在調用 select()函數以前,應該使用curl_multi_fdset來初始化fd_set變量。

select()函數返回時,說明受管理的低層socket能夠操做相應的操做(接收數據或發送數據,或者鏈接已經斷開),此時應該立刻調用 curl_multi_perform,libcurl將會執行相應操做。使用select()時,應該設置一個較短的超時時間。在調用select() 以前,形成不要忘記經過curl_multi_fdset來初始化fd_set,由於每次操做,fd_set中的文件描述符可能都不同。

若是你想停止multi stack中某一個easy handle的數據通訊,能夠調用curl_multi_remove_handle函數將其從multi stack中取出。千萬另忘記釋放掉easy handle(經過curl_easy_cleanup()函數)。

當multi stack中的一個eash handle完成數據傳輸的時候,同時運行的傳輸任務數量就會減小一個。當數量降到0的時候,說明全部的數據傳輸已經完成。

curl_multi_info_read用於獲取當前已經完成的傳輸任務信息,它返回每個easy handle的CURLcode狀態碼。能夠根據這個狀態碼來判斷每一個easy handle傳輸是否成功。

下面的例子,演示瞭如何使用multi interface進行網頁抓取:

 

  1. int main(int argc, char **argv)
  2. {
  3.     // 初始化

  4.     curl_global_init(CURL_GLOBAL_WIN32);
  5.     CURLM *multi_handle = NULL;
  6.     CURL *easy_handle1 = NULL;
  7.     CURL *easy_handle2 = NULL;

  8.     extern size_t save_sina_page(void *buffer, size_t size, size_t count, void *user_p);
  9.     extern size_t save_sohu_page(void *buffer, size_t size, size_t count, void *user_p);
  10.     FILE *fp_sina = fopen("sina.html", "ab+");
  11.     FILE *fp_sohu = fopen("sohu.html", "ab+");

  12.     multi_handle = curl_multi_init();

  13.     // 設置easy handle

  14.     easy_handle1 = curl_easy_init();
  15.     curl_easy_setopt(easy_handle1, CURLOPT_URL, "http://www.sina.com.cn");
  16.     curl_easy_setopt(easy_handle1, CURLOPT_WRITEFUNCTION, &save_sina_page);
  17.     curl_easy_setopt(easy_handle1, CURLOPT_WRITEDATA, fp_sina);

  18.     easy_handle2 = curl_easy_init();
  19.     curl_easy_setopt(easy_handle2, CURLOPT_URL, "http://www.sohu.com");
  20.     curl_easy_setopt(easy_handle2, CURLOPT_WRITEFUNCTION, &save_sohu_page);
  21.     curl_easy_setopt(easy_handle2, CURLOPT_WRITEDATA, fp_sohu);

  22.     // 添加到multi stack

  23.     curl_multi_add_handle(multi_handle, easy_handle1);
  24.     curl_multi_add_handle(multi_handle, easy_handle2);

  25.     //

  26.     int running_handle_count;
  27.     while (CURLM_CALL_MULTI_PERFORM == curl_multi_perform(multi_handle, &running_handle_count))
  28.     {
  29.         cout << running_handle_count << endl;
  30.     }

  31.     while (running_handle_count)
  32.     {
  33.         timeval tv;
  34.         tv.tv_sec = 1;
  35.         tv.tv_usec = 0;

  36.         int max_fd;
  37.         fd_set fd_read;
  38.         fd_set fd_write;
  39.         fd_set fd_except;

  40.         FD_ZERO(&fd_read);
  41.         FD_ZERO(&fd_write);
  42.         FD_ZERO(&fd_except);

  43.         curl_multi_fdset(multi_handle, &fd_read, &fd_write, &fd_except, &max_fd);
  44.         int return_code = select(max_fd + 1, &fd_read, &fd_write, &fd_except, &tv);
  45.         if (SOCKET_ERROR == return_code)
  46.         {
  47.             cerr << "select error." << endl;
  48.             break;
  49.         }
  50.         else
  51.         {
  52.             while (CURLM_CALL_MULTI_PERFORM == curl_multi_perform(multi_handle, &running_handle_count))
  53.             {
  54.                 cout << running_handle_count << endl;
  55.             }
  56.         }
  57.     }

  58.     // 釋放資源

  59.     fclose(fp_sina);
  60.     fclose(fp_sohu);
  61.     curl_easy_cleanup(easy_handle1);
  62.     curl_easy_cleanup(easy_handle2);
  63.     curl_multi_cleanup(multi_handle);
  64.     curl_global_cleanup();

  65.     return 0;
  66. }

  67. size_t save_sina_page(void *buffer, size_t size, size_t count, void *user_p)
  68. {
  69.     return fwrite(buffer, size, count, (FILE *)user_p);
  70. }

  71. size_t save_sohu_page(void *buffer, size_t size, size_t count, void *user_p)
  72. {
  73.     return fwrite(buffer, size, count, (FILE *)user_p);
  74. }
相關文章
相關標籤/搜索