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