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;
}

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

时间: 2024-10-26 14:53:14

libcurl,多线程,gzip,共享DNS的相关文章

java 多线程 session共享

问题描述 java 多线程 session共享 在struts2中,启动新的线程 调用ServletActionContext.getRequest() 报空指针错误 解决方案 这个共享的缓存数据是否进程共享的,是不是只能某个线程私有的地址了 解决方案二: Java 多线程 变量共享java多线程共享全局变量java多线程的共享变量访问控制实例 解决方案三: 由于是并发的,当开启线程A(有session)后,再开启线程B,你得确定线程B开启的时候,此时A线程中已经初始化了session及其中想获

java基础多线程之共享数据

java基础巩固笔记5-多线程之共享数据 线程范围内共享数据 ThreadLocal类 多线程访问共享数据 几种方式 本文主要总结线程共享数据的相关知识,主要包括两方面:一是某个线程内如何共享数据,保证各个线程的数据不交叉:一是多个线程间如何共享数据,保证数据的一致性. 线程范围内共享数据 自己实现的话,是定义一个Map,线程为键,数据为值,表中的每一项即是为每个线程准备的数据,这样在一个线程中数据是一致的. 例子 package com.iot.thread; import java.util

【CURL (LIBCURL) 开发 之一】COCOS2DX之LIBCURL(CURL_EASY)的编程教程(帮助手册)!

本站文章均为 李华明Himi 原创,转载务必在明显处注明:  转载自[黑米GameDev街区] 原文链接: http://www.himigame.com/curl-libcurl/878.html      注意:如果你的服务器是Java的,那么要注意数据之间的大端小端的处理:否则无法正常获取正确的数据! 本篇介绍使用libcurl编程的一般原则和一些基本方法.本文主要是介绍 c 语言的调用接口,同时也可能很好的适用于其他类 c 语言的接口. 跨平台的可移植代码 libcurl库背后的开发人员

libcurl教程

原文地址:http://curl.haxx.se/libcurl/c/libcurl-tutorial.html     译者:JGood(http://blog.csdn.net/JGood )     译者注:这是一篇介绍如何使用libcurl的入门教程.文档不是逐字逐句按原文翻译,而是根据笔者对libcurl的理解,参考原文写成.文中用到的一些例子,可能不是出自原文,而是笔者在学习过程中,写的一些示例程序(笔者使用的libcurl版本是:7.19.6).出现在这里主要是为了更好的说明lib

PHP 高级编程之多线程(四)-多线程与ZeroMQ

PHP 高级编程之多线程 http://netkiller.github.io/journal/thread.php.html Mr. Neo Chen (netkiller), 陈景峰(BG7NYT)     中国广东省深圳市龙华新区民治街道溪山美地 518131 +86 13113668890 +86 755 29812080<netkiller@msn.com> 版权 2011, 2012, 2013, 2014 http://netkiller.github.io 版权声明 转载请与作

新手学JAVA(十)-多线程----线程的创建和启动

Java使用Thread类代表线程,所有的线程对象都必须是Thread类或者其子类的实例.每个下次你哼的作用是完成一定的任务,实际上就是执行一段程序流(一段顺序执行的代码).Java使用线程执行体来代表这段程序流     在Java线程的创建有三种方式      通过继承Thread类创建线程类 步骤如下 定义Thread类的子类,并重写该类的run()方法,该run()方法的方法体就代表了线程需要完成的任务.因此把run()方法称为线程执行体. 创建Thread子类的实例,即创建了线程的对象.

解决Java多线程并发的计数器问题

问题描述 解决Java多线程并发的计数器问题 3C public class Counter { public static int count = 0; public synchronized static void inc() { count++; } public static void main(String[] args) { //同时启动1000个线程,去进行i++计算,看看实际结果 for (int i = 0; i < 1000; i++) { new Thread(new Ru

谈消息总线客户端的多线程实现

最近在实现一个基于RabbitMQ的消息总线.因为它提供了Client(客户端),这里就牵扯到凡是技术组件的client都无法回避的并发问题.本文借实现消息总线的client谈谈在实现过程中的想法以及最终的处理方式,当然这些都不仅仅适用于消息总线的client,其他通用组件的client也同样适用. 并发问题的分类 其实上面所提到的并发问题,从大的层面上可以划分为两类问题: 自身固有的并发问题:这个存在的前提条件是client自身内部使用了多线程技术,并且本身就存在线程安全的缺陷. 被动调用的并

使用FMDB多线程访问数据库,及database is locked的问题

每日更新关注:http://weibo.com/hanjunqiang  新浪微博 今天终于解决了多线程同时访问数据库时,报数据库锁定的问题,错误信息是: Unknown error finalizing or resetting statement (5: database is locked) 最后通过FMDatabaseQueue解决了这个问题,本文总结一下: FMDatabase不能多线程使用同一个实例 多线程访问数据库,不能使用同一个FMDatabase的实例,否则会发生异常.如果线程