Curl(C++)使用教程

1、Curl简介

2、Easy interface

3、Multi interface


1、Curl简介

    libcurl作为是一个多协议的便于客户端使用的URL传输库,基于C语言,提供C语言的API接口,支持DICT, FILE, FTP, FTPS, Gopher, HTTP, HTTPS, IMAP, IMAPS, LDAP, LDAPS, POP3, POP3S, RTMP, RTSP, SCP, SFTP, SMTP, SMTPS, Telnet and TFTP这些协议,同时支持使用SSL证书的安全文件传输:HTTP POST, HTTP PUT, FTP 上传, 基于HTTP形式的上传、代理、Cookies、用户加密码的认证等多种应用场景。另外,libcurl是一个高移植性的库,能在绝大多数系统上运行,包括Solaris, NetBSD, FreeBSD, OpenBSD, Darwin, HPUX, IRIX, AIX, Tru64, Linux, UnixWare, HURD, Windows, Amiga, OS/2, BeOs, Mac OS X, Ultrix, QNX, OpenVMS, RISC OS, Novell NetWare, DOS等。

  • libcurl官网地址;
  • libcurl的GitHub地址;

    libcurl提供了两种接口,分别是easy interfacemulti interface。easy interface以同步的方式进行数据传输,执行curl函数时会一直阻塞到数据传输完毕后返回,且一次操作只能发送一次请求,如果要同时发送多个请求,必须使用多线程。 而multi interface以一种简单的、非阻塞、异步的方式进行传输,它允许在一个线程中,同时提交多个相同类型的请求。 在使用multi interface之前,你应该掌握easy interface的基本使用。因为multi interface是建立在easy interface基础之上的,它只是简单的将多个easy handler添加到一个multi stack,而后同时传输而已。

2、Easy interface

    在基于libcurl的程序里,主要采用callback function (回调函数)的形式完成传输任务,用户在启动传输前设置好各类参数和回调函数,当满足条件时libcurl将调用用户的回调函数实现特定功能。下面是利用libcurl完成传输任务的流程:

  • 调用 curl_global_init() 初始化 libcurl;
  • 调用 curl_easy_init() 函数得到 easy interface型指针;
  • 调用 curl_easy_setopt() 设置传输选项,根据 curl_easy_setopt() 设置的传输选项,实现回调函数以完成用户特定任务;
  • 调用 curl_easy_perform() 函数完成传输任务;
  • 调用 curl_easy_cleanup() 释放内存。

    在整过过程中设置 curl_easy_setopt() 参数是最关键的,几乎所有的libcurl程序都要使用它。

(1)CURLcode curl_global_init(long flags)

    描述:这个函数只能用一次。(其实在调用curl_global_cleanup() 函数后仍然可再用),如果这个函数在curl_easy_init函数调用时还没调用,它讲由libcurl库自动完成。
    参数:flags

  •  CURL_GLOBAL_ALL                  //初始化所有的可能的调用。
  •  CURL_GLOBAL_SSL                 //初始化支持 安全套接字层。
  •  CURL_GLOBAL_WIN32             //初始化win32套接字库。
  •  CURL_GLOBAL_NOTHING       //没有额外的初始化。

(2)void curl_global_cleanup(void)

    描述:在结束libcurl使用的时候,用来对curl_global_init做的工作清理。类似于close的函数。

(3)char *curl_version( )

    描述: 打印当前libcurl库的版本。

(4)CURL *curl_easy_init( )

    描述:curl_easy_init用来初始化一个CURL的指针(有些像返回FILE类型的指针一样). 相应的在调用结束时要用curl_easy_cleanup 函数清理。一般curl_easy_init意味着一个会话的开始. 它的返回值一般都用在easy系列的函数中.

(5)void curl_easy_cleanup(CURL *handle)

    描述:这个调用用来结束一个会话.与curl_easy_init配合着用. 
    参数: CURL类型的指针.

(6)CURLcode curl_easy_perform(CURL *handle)

    描述:这个函数在初始化CURL类型的指针 以及curl_easy_setopt完成后调用. 就像字面的意思所说perform就像是个舞台.让我们设置的option 运作起来。    
    参数: CURL类型的指针.。
    返回值:返回0意味一切ok,非0代表错误发生。主要错误码说明:

  • CURLE_OK : 任务完成一切都好;
  • CURLE_UNSUPPORTED_PROTOCOL:不支持的协议,由URL的头部指定;
  • CURLE_COULDNT_CONNECT:不能连接到remote 主机或者代理;
  • CURLE_REMOTE_ACCESS_DENIED:访问被拒绝;
  • CURLE_HTTP_RETURNED_ERROR:Http返回错误;
  • CURLE_READ_ERROR:读本地文件错误;

(7)CURLcode curl_easy_getinfo(CURL *curl, CURLINFO info, ... )

     描述:发出http请求后,服务器会返回应答头信息和应答数据,如果仅仅是打印应答头的所有内容,则直接可以通过curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION,  function) 的方式来完成,这里需要获取的是应答头中特定的信息,比如应答码、cookies列表等需要通过这个函数。注意,这个函数必须在执行 curl_easy_perform() 后调用。
     参数:

     info参数就是我们需要获取的内容,下面是一些参数值:

  • CURLINFO_RESPONSE_CODE:获取应答码;
  • CURLINFO_HEADER_SIZE:头大小;
  • CURLINFO_COOKIELIST: cookies列表。

     第三个参数必须是指向 long的指针、指向char *的指针、指向struct curl_slist *的指针或指向double的指针,函数调用返回CURL_OK时,指向的数据将被填充。

(8)CURLcode curl_easy_setopt(CURL *handle, CURLoption option, parameter)

    描述: 这个函数最重要了,几乎所有的curl 程序都要频繁的使用它.它告诉curl库.程序将有如何的行为.。比如要查看一个网页的html代码等.(这个函数有些像ioctl函数)
    参数:
      CURL类型的指针;
      各种CURLoption类型的选项.(都在curl.h库里有定义),下面我们对这个选项进行详细解读;
      parameter 这个参数既可以是个函数的指针,也可以是某个对象的指针,也可以是个long型的变量.它用什么这取决于第二个参数。

    CURLoption的各种参数介绍:

  • CURLOPT_URL:字符串类型,该选项设置要处理的URL地址,该选项是进行curl_easy_perform之前唯一必须要设置的选项。
  • CURLOPT_COOKIE: 字符串类型,设置http头中的cookie信息。
  • CURLOPT_COOKIEFILE:字符串类型,同CURLOPT_COOKIE,不过cookie信息从文件中读取。
  • CURLOPT_POSTFIELDS: 字符串类型,提交http的post操作字符串数据。
  • CURLOPT_NOPROGRESS:布尔值类型,默认为1。设置该值为非零值关闭CRUL传输显示的进度条,为0表示打开。
  • CURLOPT_TIMEOUT:long数值类型,设置函数执行的最长时间,时间单位为s。
  • CURLOPT_CONNECTTIMEOUT:long数值类型,设置连接服务器最长时间,时间单位为s;当置为0时表示无限长。
  • CURLOPT_MAX_RECV_SPEED_LARGE: curl_off_t类型数据,指定下载过程中最大速度,单位bytes/s。
  • CURLOPT_HEADER:默认为 0。指定写回调函数WRITEFUNCTION是否包含报文的头部信息,设置为1表示包含,为0表示不包含。
  • CURLOPT_VERBOSE:默认为0。打开或关系详细模式,参数设置为1可使库显示有关此句柄上操作的大量详细信息。对libcurl和/或协议调试和理解非常有用。详细信息将发送到stderr。
  • CURLOPT_NOSIGNAL:默认为0 。如果设置为1,则libcurl将不使用任何安装信号处理程序的函数或任何导致信号发送到进程的函数。此选项允许多线程unix应用程序仍然设置/使用所有超时选项等,而不会有获取信号的风险。
  • CURLOPT_TCP_NODELAY:默认为1。表示是否关闭TCP的Nagle算法,1表示关闭,0表示打开。
  • CURLOPT_TCP_KEEPALIVE:默认为0。0表示TCP关闭keep-alive,1表示TCP打开keep-alive,也就是支持长连接。
  • CURLOPT_TCP_KEEPIDLE:默认为60s。设置TCP的keep-alive空闲时间的等待,即在发送keep-alive探测之前,在连接空闲时操作系统的等待时间。
  • CURLOPT_TCP_KEEPINTVL:默认为60s。设置TCP的keep-alive的间隔。操作系统在连续两次发送keepalive探测之间的间隔时间。
  • CURLOPT_SSL_CIPHER_LIST:传递一个char *,指向一个零终止的字符串,其中包含用于SSL连接的密码列表。
  • CURLOPT_SSL_VERIFYPEER:默认为1。是否验证对端的SSL证书,1表示验证,0表示不验证。
  • CURLOPT_SSL_VERIFYHOST:默认为2。验证证书的名字和URL中的服务器是否匹配。为2时将验证,为0时将不会验证。
  • CURLOPT_POST:默认为0。设置为1表示发起一次post请求,使用CURLOPT_POSTFIELDS或CURLOPT_COPYPOSTFIELDS选项之一指定要发送的数据,使用CURLOPT_POSTFIELDSIZE或CURLOPT_POSTFIELDSIZE_LARGE来设置数据大小。或者,可以使用CURLOPT_READFUNCTION和CURLOPT_READDATA选项向POST提供数据,但是此时CURLOPT_POSTFIELDS设置NULL。使用回调提供数据时,必须使用分块传输编码进行传输,或者必须使用CURLOPT_POSTFIELDSIZE或CURLOPT_POSTFIELDSIZE_LARGE选项设置数据大小。
  • CURLOPT_HTTPGET:默认为0。设置为1表示发起一次Get请求,将CURLOPT_HTTPGET设置为1时,它会自动将CURLOPT_NOBODY设置为0,将CURLOPT_UPLOAD设置为0。
  •  
  • CURLOPT_HTTPHEADER:curl_slist结构体类型,该选项自定义请求头信息。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");
curl_easy_setopt(easyhandle, CURLOPT_HTTPHEADER, headers); /* pass our list of custom made 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:");
  • CURLOPT_WRITEFUNCTION,CURLOPT_WRITEDATA:

    写回调函数原型为:

size_t write_callback(char *ptr, size_t size, size_t nmemb, void *userdata);

    函数将在libcurl接收到数据后被调用,因此函数多做数据保存的功能,如处理下载文件。对于大多数传输,此回调被多次调用,每次调用都会传递另一块数据。ptr 指向传输的数据,该数据的大小为nmemb ,size参数总是1。CURLOPT_WRITEDATA 用于表明CURLOPT_WRITEFUNCTION函数中的userdata指针的来源。如果你没有通过CURLOPT_WRITEFUNCTION属性给easy handle设置回调函数,libcurl会提供一个默认的回调函数,它只是简单的将接收到的数据打印到标准输出。你也可以通过 CURLOPT_WRITEDATA属性给默认回调函数传递一个已经打开的文件指针,用于将数据输出到文件里。

  • CURLOPT_READFUNCTION,CURLOPT_READDATA

    读回调函数原型是:

size_t read_callback(char *buffer, size_t size, size_t nitems, void *userdata)

    当libcurl需要读取数据传递给远程主机时将调用该函数,在这个函数中,buffer指针指向的数据块需要填充最多 size * nitems 个字节。CURLOPT_READDATA 表明CURLOPT_READFUNCTION函数原型中的userdata指针来源。

  • CURLOPT_HEADERFUNCTION,CURLOPT_HEADERDATA

    写header回调函数原型为:

size_t header_callback(char *buffer, size_t size, size_t nitems, void *userdata);

    libcurl一旦接收到http 头部数据后将调用该函数,只取报文header。CURLOPT_WRITEDATA 传递指针给libcurl,该指针表明CURLOPT_HEADERFUNCTION 函数的userdata指针的来源。

  • CURLOPT_NOPROGRESS,CURLOPT_PROGRESSFUNCTION,CURLOPT_PROGRESSDATA

     进度相关回调函数原型为:

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

   这个函数跟数据传输进度相关。CURLOPT_PROGRESSFUNCTION 指定的函数正常情况下每秒被libcurl调用一次,为了使CURLOPT_PROGRESSFUNCTION被调 用,CURLOPT_NOPROGRESS必须被设置为false,CURLOPT_PROGRESSDATA指定的参数将作为 CURLOPT_PROGRESSFUNCTION指定函数的第一个参数。

   实例:

#include 
#include 

bool getUrl(char *filename)
{
    CURL *curl;
    CURLcode res;
    FILE *fp;
    if ((fp = fopen(filename, "w")) == NULL)  // 返回结果用文件存储
        return false;
    struct curl_slist *headers = NULL;
    headers = curl_slist_append(headers, "Accept: Agent-007");
    curl = curl_easy_init();    // 初始化
    if (curl)
    {       
        curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);   // 改协议头
        curl_easy_setopt(curl, CURLOPT_URL,"http://www.baidu.com");
        curl_easy_setopt(curl, CURLOPT_WRITEDATA, fp);         //将返回的http头输出到fp指向的文件
        curl_easy_setopt(curl, CURLOPT_HEADERDATA, fp);        //将返回的html主体数据输出到fp指向的文件
        res = curl_easy_perform(curl);   // 执行
        if (res != 0) {

            curl_slist_free_all(headers);
            curl_easy_cleanup(curl);
        }
        fclose(fp);
        return true;
    }
}

bool postUrl(char *filename)
{
    CURL *curl;
    CURLcode res;
    FILE *fp;
    if ((fp = fopen(filename, "w")) == NULL)
        return false;
    curl = curl_easy_init();
    if (curl)
    {
        curl_easy_setopt(curl, CURLOPT_COOKIEFILE, "/tmp/cookie.txt");     // 指定cookie文件
        curl_easy_setopt(curl, CURLOPT_POSTFIELDS, "&logintype=uid&u=xieyan&psw=xxx86");    // 指定post内容        
        curl_easy_setopt(curl, CURLOPT_URL, " http://mail.sina.com.cn/cgi-bin/login.cgi ");   // 指定url
        curl_easy_setopt(curl, CURLOPT_WRITEDATA, fp);
        res = curl_easy_perform(curl);
        curl_easy_cleanup(curl);
    }
    fclose(fp);
    return true;
}

int main(void)
{
    getUrl("/tmp/get.html");
    postUrl("/tmp/post.html");
}

3、Multi interface

    libcurl的easy接口是阻塞的,也就是说必须等到上一个curl请求执行完后,下一个curl请求才能继续执行,在一般的应用场合,这种阻塞的访问方式是没有问题的,但是当程序需要进行多次curl并发请求的时候,easy接口就无能为力了,这个时候curl提供的multi接口就派上用场了。相比而言,multi接口的使用会比easy 接口稍微复杂点,毕竟multi接口是依赖easy接口的,首先粗略的讲下其使用流程:

  • curl_multi _init() 初始化一个multi curl对象;
  • 为了同时进行多个curl的并发访问,我们需要初始化多个easy curl对象,使用 curl_easy_setopt() 进行相关设置;
  • 然后调用 curl_multi _add_handle() 把easy curl对象添加到multi curl对象中;
  • 添加完毕后执行curl_multi_perform方法进行并发的访问;
  • 访问结束后 curl_multi_remove_handle() 移除相关easy curl对象;
  • curl_easy_cleanup() 清除easy curl对象;
  • 最后curl_multi_cleanup() 清除multi curl对象。

(1)CURLM *curl_multi_init( ):

    描述:此函数返回一个CURLM句柄,用作所有其它multi-function的输入,有时在文档的某些位置称为multi handle。当操作完成时,此init调用必须对 curl_multi_cleanup() 进行调用。

(2)CURLMcode curl_multi_assign(CURLM *multi_handle, curl_socket_t sockfd,   void *sockptr)

    描述:此函数在给定套接字和应用程序的私有指针之间的多句柄中创建关联。这是为curl_socket_multi_action使用而设计的。设置后,sockptr指针将传递给特定sockfd套接字的所有未来套接字回调。如果libcurl尚未使用给定的sockfd,则此函数将返回错误。

(3)CURLMcode curl_multi_setopt(CURLM * multi_handle, CURLMoption option, param)

    描述:curl_multi_setopt用于告诉libcurl multi handle 如何表现。通过使用curl_multi_setopt的相应选项,可以在使用该多句柄时更改libcurl的行为。使用选项后跟参数param设置所有选项。该参数可以是long,函数指针,对象指针或curl_off_t类型,具体取决于特定选项所期望的内容。

    CURLMoption的参数介绍:

  • CURLMOPT_MAX_HOST_CONNECTIONS:默认值为0。设置主机的最大连接数,设置的数字将用作与单个主机同时打开的最大连接数(一个主机即主机名+端口号)。对于主机的每个新会话,libcurl将打开一个新连接,最多可达CURLMOPT_MAX_HOST_CONNECTIONS设置的限制。达到限制时,会话将处于挂起状态直到连接可用。如果启用了CURLMOPT_PIPELINING,则libcurl将尝试在主机能够进行管道传输时进行管道传输。默认最大值为0,无限制。
  • CURLMOPT_PIPELINING:默认值为CURLPIPE_NOTHING (0)。是否启用HTTP流水线操作CURLPIPE_HTTP1 (1)和多路复用CURLPIPE_MULTIPLEX (2)。对于流水线操作CURLPIPE_HTTP1,这意味着如果添加可以使用现有连接的第二个请求,则第二个请求将在同一连接上使用“管道”传输,而不是并行执行。对于多路复用CURLPIPE_MULTIPLEX (2),这意味着后续请求可以重新使用现有连接,并在其它传输已使用该单个连接的同时发送多路复用的新请求。
  • CURLMOPT_MAX_PIPELINE_LENGTH:一个管道中的最大请求数,默认为5。
  • CURLMOPT_SOCKETFUNCTION,CURLMOPT_SOCKETDATA:

    回调函数原型:

int socket_callback(CURL * easy,         / * easy handle * /
                     curl_socket_t s,     / * socket * /
                     int what,            / *描述套接字* /
                     void * userp,        / *私有回调指针* /
                     void * socketp);     / *私有套接字指针* /

  参数:
    easy:表示与此次更新相关的特定easy handle。因为一个socket和一个easy handle关联在一起;
    s :这个函数调用所关注的特定套接字。如果what参数不被设置为CURL_POLL_REMOVE,那么what参数它包含应用程序应监视此套接字上的哪些活动的有关信息。对此次回调的后续调用可能会更新已监视的套接字的bits位;   
    userp:通过CURLMOPT_SOCKETDATA设置;    
    socketp:通过curl_multi_assign设置,或者可以为NULL.
    what:socket的状态,它可以时一下几个值之一: CURL_POLL_IN、CURL_POLL_OUT、CURL_POLL_INOUT、CURL_POLL_REMOVE。 

    当curl_multi_socket_action被调用时,它通过0次、1次或者多次调用socket_callback来通知应用程序socket状态的更新(也就是把socket的状态更新传给socket_callback,然后由socket_callback来处理这种更新),socket_callback函数获取状态更新,其中包含自上次调用socket_callback以来的更改。如果socket_callback函数指针为空,则不会调用回调函数。

  • CURLMOPT_TIMERFUNCTION,CURLMOPT_TIMERDATA:

      回调函数原型:

int timer_callback(CURLM *multi,    /* multi handle */
                   long timeout_ms, /* timeout in number of ms */
                   void *userp);    /* private callback pointer */

timeout_ms:一个timeout_ms传递给此回调的值,-1意味着你应该删除这个定时器。所有其他值都是有效的到期时间,以毫秒为单位。
           该timer_callback当超时到期时间改变才会被调用。
userp:该指针由CURLMOPT_TIMERDATA参数设置。

    回调函数timer_callback安装一个间隔为timeout_ms的非重复计时器。当计时器触发时,调用curl_multi_socket_action或curl_multi_perform,具体取决于使用是哪个接口。

(4)CURLMcode curl_multi_add_handle(CURLM *multi_handle, CURL *easy_handle)

    描述:为多会话添加一个简单的句柄,调用这个函数时会自动回调 timer_callback() 函数。

(5)CURLMcode curl_multi_remove_handle(CURLM *multi_handle, CURL *easy_handle)

    描述:从多会话中删除一个简单的句柄。

(6)CURLMcode curl_multi_perform(CURLM * multi_handle,int * running_handles)

    描述:从每个简单句柄读取/写入可用数据,它底层使用的是select作为事件监听器。其中running_handles用于保存正在运行的easy_handle数量。

(7)CURLMcode curl_multi_socket_action(CURLM * multi_handle,curl_socket_t sockfd,int ev_bitmask,int * running_handles)

    描述:该函数为应用程序提供了一种方法,不仅可以避免被强制使用select(),还可以提供更高性能的API,这将为使用大量同时连接的应用程序带来显着差异。该函数时用来代替curl_multi_perform的。当应用程序检测到由libcurl处理的套接字上的操作时(比如通过poll、select函数),应调用curl_multi_socket_action并将sockfd参数设置为带有该操作的套接字。ev_bitmask设置一般设置为0,libcurl在内部测试描述符。可以将CURL_SOCKET_TIMEOUT传递给sockfd参数,以启动整个过程或发生超时。返回时,running_handles指向多句柄中正在运行的easy handle的数量。当此数字达到0时,所有传输都完成。当您在特定套接字上调用curl_multi_socket_action并且计数器减1时,它并不一定意味着这个确切的套接字/传输是完成的。使用curl_multi_info_read找出完成的简易句柄。该函数的用法总结如下:

  • 如果传递给 curl_multi_socket_action() 的sockfd参数为 CURL_SOCKET_TIMEOUT,则表示启动multi_socket的整个过程,函数将根据 handle 的数量创建相应数量的socket,当发生超时时函数返回。每次调用 curl_multi_socket_action() 都会自动调用 timer_callback() 函数;
  • 当 curl_multi_socket_action() 创建好一个socket之后,会自动回调 socket_callback() 函数,并在socket_callback()函数中传入新建的socket和该socket的what状态。在 socket_callback() 函数中我们需要将传入的socket保存起来,或者结合事件管理器(poll、select、epoll)一起使用;
  • 当我们在循环中再次调用 curl_multi_socket_action() 函数,并用传入已经创建的socketfd时,如果socketfd可读,那么就会自动调用 write_callback() 函数,从socketfd中读取数据写到本地。如果socketfd可写,那么就会调用 read_callback() 函数,从本地数据中读取上传socketfd。如果socketfd的状态发生了改变,由可读变成可写或者由可写变成可读,那么就会自动调用 socket_callback() 函数;
  • 由于我们不能根据running_handles来判断所有的handle是否运行完成,因此我们在 scoket_callback() 中必须保存所有的socketfd,把socketfd和running_handle两个参数作为判断循环结束的条件;
  • socket_callback() 函数可以结合事件管理器,比如epoll/select/poll等函数,调用sock_callback回调函数时,根据传入的sockfd和what状态,添加到相应的事件管理器中,当事件管理器发现socket状态改变时通过 curl_multi_socket_action() 通知libcurl读写数据,读写数据完成之后,curl_multi_socket_action() 会自动调用 sock_callback() 函数,然后在这个函数中再通知事件管理器,如此反复。

(8)一个实例

     这个实例中使用两个while循环,第一个循环用于创建socket,第二个循环用于发送数据到socket  或者 从socket中读取数据。

#include 
#include 
#include 
#include 
#include 
#include 

using namespace std;

size_t write_callback(char *ptr, size_t size, size_t nmemb, void *userdata)
{
	size = size * nmemb;
	std::cout << "write callback, data size:" << size << endl;
	return size;
}

size_t header_callback(char *buffer, size_t size, size_t nitems, void *userdata)
{
	size = size * nitems;
	std::cout << "header callback, data sieze:" << size << endl;
	return size;
}

int socket_callback(CURL* easy, curl_socket_t s, int action, void* userp, void* socketp)
{
	std::cout << endl << "socket callback" << endl;
	std::cout << "socketp : " << socketp << endl;
	std::cout << "socket fd:" << s << endl;

	struct pollfd* fd = reinterpret_cast(userp);
	fd->fd = s;
	fd->events = 0;
	fd->revents = 0;

	if (action == CURL_POLL_REMOVE)
	{
		std::cout << "action: CURL_POLL_REMOVE" << endl;
		fd->fd = -1;
	}
	else if (action == CURL_POLL_IN || action == CURL_POLL_INOUT)
	{
		std::cout << "action: CURL_POLL_IN | CURL_POLL_INOUT" << endl;
        	fd->events |= POLLIN;
    	}
    	else if (action == CURL_POLL_OUT || action == CURL_POLL_INOUT)
	{
		std::cout << "action: CURL_POLL_OUT | CURL_POLL_INOUT" << endl;
        	fd->events |= POLLOUT;
    	}
	std::cout << endl;
	return 0;
}

int timer_callback(CURLM *multi, long timeout_ms, void *userp)
{
	std::cout << endl << "timer callback" << endl;
	std::cout << "timeout_ms:" << timeout_ms << endl << endl;
	return 0;
}

int main()
{
	CURL* mHandle = curl_easy_init();

	curl_easy_setopt(mHandle, CURLOPT_URL, "www.baidu.com");
	curl_easy_setopt(mHandle, CURLOPT_VERBOSE, 0L);
	curl_easy_setopt(mHandle, CURLOPT_NOSIGNAL, 1);
	curl_easy_setopt(mHandle, CURLOPT_TCP_KEEPALIVE, 1L);
	curl_easy_setopt(mHandle, CURLOPT_TCP_KEEPIDLE, 120L);
	curl_easy_setopt(mHandle, CURLOPT_TCP_KEEPINTVL, 60L);
	curl_easy_setopt(mHandle, CURLOPT_TCP_NODELAY, 1L);
	curl_easy_setopt(mHandle, CURLOPT_HTTPGET, 1);

	FILE *header_fp;
    	if ((header_fp = fopen("./header_file", "w")) == NULL)
	{
		return false;
	}
	curl_easy_setopt(mHandle, CURLOPT_HEADERDATA, header_fp);
	curl_easy_setopt(mHandle, CURLOPT_HEADERFUNCTION, header_callback);

	FILE *body_fp;
    	if ((body_fp = fopen("./body_file", "w")) == NULL)
	{
		return false;
	}
	curl_easy_setopt(mHandle, CURLOPT_WRITEDATA, body_fp);
	curl_easy_setopt(mHandle, CURLOPT_WRITEFUNCTION, write_callback);

	CURLM*  mMultiHandle = curl_multi_init();
	struct pollfd mPollFd;
	int mStillRunning = 0;

	curl_multi_setopt(mMultiHandle, CURLMOPT_TIMERDATA, mPollFd);
	curl_multi_setopt(mMultiHandle, CURLMOPT_TIMERFUNCTION, timer_callback);

	curl_multi_setopt(mMultiHandle, CURLMOPT_SOCKETDATA, &mPollFd);
	curl_multi_setopt(mMultiHandle, CURLMOPT_SOCKETFUNCTION, socket_callback);

	curl_multi_setopt(mMultiHandle, CURLMOPT_PIPELINING , 1);
	curl_multi_setopt(mMultiHandle, CURLMOPT_MAX_HOST_CONNECTIONS, 10);
	curl_multi_setopt(mMultiHandle, CURLMOPT_MAX_PIPELINE_LENGTH, 5);

	curl_multi_add_handle(mMultiHandle, mHandle);    //这个函数调用后会自动调用timer_callback()函数

	std::cout << "begin perform curl" << endl;

	do
	{
            curl_multi_socket_action(mMultiHandle, CURL_SOCKET_TIMEOUT, 0, &mStillRunning);
	    usleep(1000);  // 延迟1ms

        } while (mPollFd.fd == 0 && mStillRunning);           //这个循环中创建需要用到的socket

	std::cout << "create socket successfule!" << endl;	

        do {
            curl_multi_socket_action(mMultiHandle, mPollFd.fd, 0, &mStillRunning);   //通知回调函数
        } while (mPollFd.fd != 0 && mStillRunning);          //这个循环中不断读或者写数据,直到socket被关闭

	fclose(header_fp);
	fclose(body_fp);

	return 0;
}

参考:https://curl.haxx.se/libcurl/c/

你可能感兴趣的:(C/C++)