博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
libcurl,多线程,gzip,共享DNS
阅读量:5964 次
发布时间:2019-06-19

本文共 4648 字,大约阅读时间需要 15 分钟。

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

转载于:https://www.cnblogs.com/super999/p/9920977.html

你可能感兴趣的文章
C++ Error: error LNK2019: unresolved external symbol
查看>>
Bitmap 和Drawable 的区别
查看>>
Java操作mongoDB2.6的常见API使用方法
查看>>
信息熵(Entropy)究竟是用来衡量什么的?
查看>>
如何给服务器设置邮件警报。
查看>>
iOS 数据库操作(使用FMDB)
查看>>
CEF js调用C#封装类含注释
查看>>
麦克劳林
查看>>
AOP概念
查看>>
jquery插件
查看>>
python time
查看>>
C#使用Advanced CSharp Messenger
查看>>
SharePoint Online 创建门户网站系列之首页布局
查看>>
SVN高速新手教程
查看>>
FSMC(STM32)
查看>>
ueditor样式过滤问题
查看>>
2015第36周日每天进步1%
查看>>
系统性能测试及调优--转载
查看>>
横向滚动条并且隐藏竖向滚动条
查看>>
五大最主流浏览器综合性能测试
查看>>