1. 程式人生 > >libcurl,多執行緒,gzip,共享DNS

libcurl,多執行緒,gzip,共享DNS

http://hi.baidu.com/jjxiaoyan/item/e17b9ec3e31b93d4964452d8

 

libcurl是一個不錯的socket庫,而且又是開源的。如果僅僅是簡單的HTTP請求,那麼只需要幾行程式碼就能輕鬆實現。不過要用libcurl實現高效、高頻率的HTTP請求就需要對libcurl有深入的瞭解才行。如果閱讀英文無障礙的話,那麼libcurl自帶的示例程式和幫助文件就是最好的老師。

一、多執行緒HTTP請求

libcurl提供多執行緒和非同步請求來實現大批量HTTP請求,可參見multithread.c和multi-app.c兩個示例程式。這兩種批量HTTP請求的方式在測試環境下都能正常執行,但使用非同步請求總是會出現問題,於是將目標轉向多執行緒請求。

多執行緒HTTP請求要注意的幾個問題:

1. 千萬不要在多執行緒之間共享同一個CURL物件

在libcurl中,第一步要做的就是使用curl_easy_int函式來初始化一個CURL物件,每個CURL對應一個HTTP連線。於是,在批量請求時為了省去每次進行HTTP連線的時間,會對多個HTTP請求使用同一個CURL物件。這在非多執行緒狀態下是不會出問題的,但在多執行緒下則會出問題。具體原因未知,網上查詢到的資料對此解釋不太詳細。

所以我們需要為每一個執行緒建立一個CURL物件:

void threadfunc( void *p )
{
    CURL *curl;
    
    curl = curl_easy_init();
    ...
    ...
    ...
    curl_easy_cleanup( curl );
}

 



2. 避免多個執行緒中同時呼叫curl_global_init函式

在多執行緒環境下,應在主執行緒中使用curl_global_init和curl_global_cleanup函式。

第一次呼叫 curl_easy_init()時,curl_easy_init 會呼叫 curl_global_init,在單執行緒環境下,這不是問題。但是多執行緒下就不行了,因為curl_global_init不是執行緒安全的。在多個執行緒中呼叫curl_easy_int,然後如果兩個執行緒同時發現curl_global_init還沒有被呼叫,同時呼叫 curl_global_init,悲劇就發生了。這種情況發生的概率很小,但可能性是存在的。

int main()
{
    curl_global_init( CURL_GLOBAL_ALL );

    /* 建立多執行緒程式碼 */
    ...
    ...

    curl_global_cleanup();
    return 0;
}
View Code

 


3. 域名解析的設定

引用:

libcurl 有個很好的特性,它甚至可以控制域名解析的超時。但是在預設情況下,它是使用alarm + siglongjmp 實現的。用alarm在多執行緒下做超時,本身就幾乎不可能。如果只是使用alarm,並不會導致程式崩潰,但是,再加上siglongjmp,就要命了(程式崩潰的很可怕,core中幾乎看不出有用資訊),因為其需要一個sigjmp_buf型的全域性變數,多執行緒修改它。(通常情況下,可以每個執行緒一個 sigjmp_buf 型的變數,這種情況下,多執行緒中使用 siglongjmp 是沒有問題的,但是libcurl只有一個全域性變數,所有的執行緒都會用)。 具體是類似 curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 30L) 的超時設定(發生在域名解析階段),導致alarm的使用,如前所述,這在多執行緒中是有衝突的。解決方式是禁用掉alarm這種超時, curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1L)。 這樣,多執行緒中使用超時就安全了。但是域名解析就沒了超時機制,碰到很慢的域名解析,也很麻煩。文件的建議是 Consider building libcurl with c-ares support to enable asynchronous DNS lookups, which enables nice timeouts for name resolves without signals. c-ares 是非同步的 DNS 解決方案。 參考:http://gcoder.blogbus.com/logs/54871550.html 

4. DNS共享

參考文章:http://blog.csdn.net/colinw/article/details/6534025

由於每個CURL物件都會連線一次伺服器,如果傳送1000次HTTP請求都連線到同一伺服器,libcurl就會返回大量連線錯誤和接收錯誤,為此使用DNS共享是很有必要的。

void set_share_handle(CURL* curl_handle)
{
    static CURLSH* share_handle = NULL;
    if (!share_handle)
    {
        share_handle = curl_share_init();
    curl_share_setopt(share_handle, CURLSHOPT_SHARE, CURL_LOCK_DATA_DNS);
    }
    curl_easy_setopt(curl_handle, CURLOPT_SHARE, share_handle);
    curl_easy_setopt(curl_handle, CURLOPT_DNS_CACHE_TIMEOUT, 60 * 5);
}

void threadfunc( void *p )
{
    CURL *curl;
    
    set_share_handle( curl );
    curl = curl_easy_init();
    ...
    ...
    ...
    curl_easy_cleanup( curl );
}

 



二、接收gzip資料及解壓縮gzip

假如一個網頁有180KB大小,使用gzip演算法壓縮後可能就只有60KB大小。目前絕大部分網站都支援gzip,這樣使用者向網站請求獲取的資料是gzip格式的,下載會使用者電腦後再由瀏覽器對gzip資料進行解壓,這樣可以大大提高網站的瀏覽速度。

要讓libcurl接受gzip編碼很簡單,只需要加入一行程式碼:

curl_easy_setopt(curl, CURLOPT_ENCODING, "gzip");

關鍵的問題是如何解壓縮gzip資料,這需要用到zlib庫。下面是從網上找的一個解壓gzip資料的函式:

/* HTTP gzip decompress */
/* 引數1.壓縮資料 2.資料長度 3.解壓資料 4.解壓後長度 */
int CHttp::httpgzdecompress(Byte *zdata, uLong nzdata, Byte *data, uLong *ndata)
{
    int err = 0;
    z_stream d_stream = {0}; /* decompression stream */
    static char dummy_head[2] = 
    {
        0x8 + 0x7 * 0x10,
        (((0x8 + 0x7 * 0x10) * 0x100 + 30) / 31 * 31) & 0xFF,
    };
    d_stream.zalloc = (alloc_func)0;
    d_stream.zfree = (free_func)0;
    d_stream.opaque = (voidpf)0;
    d_stream.next_in  = zdata;
    d_stream.avail_in = 0;
    d_stream.next_out = data;
    if(inflateInit2(&d_stream, 47) != Z_OK) return -1;
    while (d_stream.total_out < *ndata && d_stream.total_in < nzdata) 
    {
        d_stream.avail_in = d_stream.avail_out = 1; /* force small buffers */
        if((err = inflate(&d_stream, Z_NO_FLUSH)) == Z_STREAM_END) break;
        if(err != Z_OK )
        {
            if(err == Z_DATA_ERROR)
            {
                d_stream.next_in = (Bytef*) dummy_head;
                d_stream.avail_in = sizeof(dummy_head);
                if((err = inflate(&d_stream, Z_NO_FLUSH)) != Z_OK) 
                {
                    return -1;
                }
            }
            else return -1;
        }
    }
    if(inflateEnd(&d_stream) != Z_OK) return -1;
    *ndata = d_stream.total_out;
    return 0;
}

使用示例:

//解壓縮buffer中的資料
int ndesize = 1024000;//此處長度需要足夠大以容納解壓縮後資料
char *szdebuffer = new char[ndesize];
memset( szdebuffer, 0, ndesize ); 
int err; //錯誤變數的定義
/* 執行httpgzdecompress後,會在ndesize中儲存解壓後的資料長度 */
err = httpgzdecompress( ( Byte* ) szbuffer.c_str(), ( uLong ) szbuffer.size(), ( Byte* ) szdebuffer, ( uLong* ) &ndesize );

if ( err == Z_OK )
{
    /* 成功解壓 */
}

 

參考文章:

安裝zlib
http://www.linuxidc.com/Linux/2012-06/61982p2.htm

gzip的壓縮與解壓縮
http://www.cppblog.com/woaidongmao/archive/2011/06/05/148089.html