使用libcurl进行异步并发访问与文件上传

来源:互联网 发布:小世界网络模型 编辑:程序博客网 时间:2024/06/02 03:55

转载自:http://blog.csdn.net/zxgfa/article/details/8302059

 curl是一款利用URL语法进行文件传输的工具,它支持多种协议,包括FTP, FTPS, HTTP, HTTPS, GOPHER, TELNET等,我们既可以在命令行上使用它,也可以利用 libcurl进行相关编程。相信大部分同学都应该使用过libcurl的easy 接口,easy接口的使用非常的简单,curl_easy_init用来初始化一个easy curl对象,curl_easy_setopt对easy curl对象进行相关设置,最后curl_easy_perform执行curl请求,返回相应结果。easy接口是阻塞的,也就是说必须等到上一个curl请求执行完后,下一个curl请求才能继续执行,在一般的应用场合,这种阻塞的访问方式是没有问题的,但是当程序需要进行多次curl并发请求的时候,easy接口就无能为力了,这个时候curl提供的multi接口就派上用场了,网上关于libcurl的multi接口的使用资料比较少(百度出来的大部分都是php multi curl的资料),curl官网上貌似也只有相关函数的说明,有实际demo才能让我们更快速的上手使用,所以下面结合实际例子来讲讲multi curl接口的使用方法。

    相比而言,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对象。

    上面的介绍只是给大家一个大概的印象,实际使用中还有很多细节需要注意,好了,代码才能说明一切,下面的例子使用multi curl方式进行多次http并发访问,并输出访问结果。

[cpp] view plaincopy
  1. #include <string>  
  2. #include <iostream>  
  3.   
  4. #include <curl/curl.h>  
  5. #include <sys/time.h>  
  6. #include <unistd.h>  
  7.   
  8. using namespace std;  
  9.   
  10. size_t curl_writer(void *buffer, size_t size, size_t count, void * stream)  
  11. {  
  12.     std::string * pStream = static_cast<std::string *>(stream);  
  13.     (*pStream).append((char *)buffer, size * count);  
  14.   
  15.     return size * count;  
  16. };  
  17.   
  18. /** 
  19.  * 生成一个easy curl对象,进行一些简单的设置操作 
  20.  */  
  21. CURL * curl_easy_handler(const std::string & sUrl,  
  22.                          const std::string & sProxy,  
  23.                          std::string & sRsp,  
  24.                          unsigned int uiTimeout)  
  25. {  
  26.     CURL * curl = curl_easy_init();  
  27.   
  28.     curl_easy_setopt(curl, CURLOPT_URL, sUrl.c_str());  
  29.     curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1);  
  30.   
  31.     if (uiTimeout > 0)  
  32.     {  
  33.         curl_easy_setopt(curl, CURLOPT_TIMEOUT_MS, uiTimeout);  
  34.     }  
  35.     if (!sProxy.empty())  
  36.     {  
  37.         curl_easy_setopt(curl, CURLOPT_PROXY, sProxy.c_str());  
  38.     }  
  39.   
  40.     // write function //  
  41.     curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_writer);  
  42.     curl_easy_setopt(curl, CURLOPT_WRITEDATA, &sRsp);  
  43.   
  44.     return curl;  
  45. }  
  46.   
  47. /** 
  48.  * 使用select函数监听multi curl文件描述符的状态 
  49.  * 监听成功返回0,监听失败返回-1 
  50.  */  
  51. int curl_multi_select(CURLM * curl_m)  
  52. {  
  53.     int ret = 0;  
  54.   
  55.     struct timeval timeout_tv;  
  56.     fd_set  fd_read;  
  57.     fd_set  fd_write;  
  58.     fd_set  fd_except;  
  59.     int     max_fd = -1;  
  60.   
  61.     // 注意这里一定要清空fdset,curl_multi_fdset不会执行fdset的清空操作  //  
  62.     FD_ZERO(&fd_read);  
  63.     FD_ZERO(&fd_write);  
  64.     FD_ZERO(&fd_except);  
  65.   
  66.     // 设置select超时时间  //  
  67.     timeout_tv.tv_sec = 1;  
  68.     timeout_tv.tv_usec = 0;  
  69.   
  70.     // 获取multi curl需要监听的文件描述符集合 fd_set //  
  71.     curl_multi_fdset(curl_m, &fd_read, &fd_write, &fd_except, &max_fd);  
  72.   
  73.     /** 
  74.      * When max_fd returns with -1, 
  75.      * you need to wait a while and then proceed and call curl_multi_perform anyway. 
  76.      * How long to wait? I would suggest 100 milliseconds at least, 
  77.      * but you may want to test it out in your own particular conditions to find a suitable value. 
  78.      */  
  79.     if (-1 == max_fd)  
  80.     {  
  81.         return -1;  
  82.     }  
  83.   
  84.     /** 
  85.      * 执行监听,当文件描述符状态发生改变的时候返回 
  86.      * 返回0,程序调用curl_multi_perform通知curl执行相应操作 
  87.      * 返回-1,表示select错误 
  88.      * 注意:即使select超时也需要返回0,具体可以去官网看文档说明 
  89.      */  
  90.     int ret_code = ::select(max_fd + 1, &fd_read, &fd_write, &fd_except, &timeout_tv);  
  91.     switch(ret_code)  
  92.     {  
  93.     case -1:  
  94.         /* select error */  
  95.         ret = -1;  
  96.         break;  
  97.     case 0:  
  98.         /* select timeout */  
  99.     default:  
  100.         /* one or more of curl's file descriptors say there's data to read or write*/  
  101.         ret = 0;  
  102.         break;  
  103.     }  
  104.   
  105.     return ret;  
  106. }  
  107.   
  108. #define MULTI_CURL_NUM 3  
  109.   
  110. // 这里设置你需要访问的url //  
  111. std::string     URL     = "http://website.com";  
  112. // 这里设置代理ip和端口  //  
  113. std::string     PROXY   = "ip:port";  
  114. // 这里设置超时时间  //  
  115. unsigned int    TIMEOUT = 2000; /* ms */  
  116.   
  117. /** 
  118.  * multi curl使用demo 
  119.  */  
  120. int curl_multi_demo(int num)  
  121. {  
  122.     // 初始化一个multi curl 对象 //  
  123.     CURLM * curl_m = curl_multi_init();  
  124.   
  125.     std::string     RspArray[num];  
  126.     CURL *          CurlArray[num];  
  127.   
  128.     // 设置easy curl对象并添加到multi curl对象中  //  
  129.     for (int idx = 0; idx < num; ++idx)  
  130.     {  
  131.         CurlArray[idx] = NULL;  
  132.         CurlArray[idx] = curl_easy_handler(URL, PROXY, RspArray[idx], TIMEOUT);  
  133.         if (CurlArray[idx] == NULL)  
  134.         {  
  135.             return -1;  
  136.         }  
  137.         curl_multi_add_handle(curl_m, CurlArray[idx]);  
  138.     }  
  139.   
  140.     /* 
  141.      * 调用curl_multi_perform函数执行curl请求 
  142.      * url_multi_perform返回CURLM_CALL_MULTI_PERFORM时,表示需要继续调用该函数直到返回值不是CURLM_CALL_MULTI_PERFORM为止 
  143.      * running_handles变量返回正在处理的easy curl数量,running_handles为0表示当前没有正在执行的curl请求 
  144.      */  
  145.     int running_handles;  
  146.     while (CURLM_CALL_MULTI_PERFORM == curl_multi_perform(curl_m, &running_handles))  
  147.     {  
  148.         cout << running_handles << endl;  
  149.     }  
  150.   
  151.     /** 
  152.      * 为了避免循环调用curl_multi_perform产生的cpu持续占用的问题,采用select来监听文件描述符 
  153.      */  
  154.     while (running_handles)  
  155.     {  
  156.         if (-1 == curl_multi_select(curl_m))  
  157.         {  
  158.             cerr << "select error" << endl;  
  159.             break;  
  160.         } else {  
  161.             // select监听到事件,调用curl_multi_perform通知curl执行相应的操作 //  
  162.             while (CURLM_CALL_MULTI_PERFORM == curl_multi_perform(curl_m, &running_handles))  
  163.             {  
  164.                 cout << "select: " << running_handles << endl;  
  165.             }  
  166.         }  
  167.         cout << "select: " << running_handles << endl;  
  168.     }  
  169.   
  170.     // 输出执行结果 //  
  171.     int         msgs_left;  
  172.     CURLMsg *   msg;  
  173.     while((msg = curl_multi_info_read(curl_m, &msgs_left)))  
  174.     {  
  175.         if (CURLMSG_DONE == msg->msg)  
  176.         {  
  177.             int idx;  
  178.             for (idx = 0; idx < num; ++idx)  
  179.             {  
  180.                 if (msg->easy_handle == CurlArray[idx]) break;  
  181.             }  
  182.   
  183.             if (idx == num)  
  184.             {  
  185.                 cerr << "curl not found" << endl;  
  186.             } else  
  187.             {  
  188.                 cout << "curl [" << idx << "] completed with status: "  
  189.                         << msg->data.result << endl;  
  190.                 cout << "rsp: " << RspArray[idx] << endl;  
  191.             }  
  192.         }  
  193.     }  
  194.   
  195.     // 这里要注意cleanup的顺序 //  
  196.     for (int idx = 0; idx < num; ++idx)  
  197.     {  
  198.         curl_multi_remove_handle(curl_m, CurlArray[idx]);  
  199.     }  
  200.   
  201.     for (int idx = 0; idx < num; ++idx)  
  202.     {  
  203.         curl_easy_cleanup(CurlArray[idx]);  
  204.     }  
  205.   
  206.     curl_multi_cleanup(curl_m);  
  207.   
  208.     return 0;  
  209. }  
  210.   
  211. /** 
  212.  * easy curl使用demo 
  213.  */  
  214. int curl_easy_demo(int num)  
  215. {  
  216.     std::string     RspArray[num];  
  217.   
  218.     for (int idx = 0; idx < num; ++idx)  
  219.     {  
  220.         CURL * curl = curl_easy_handler(URL, PROXY, RspArray[idx], TIMEOUT);  
  221.         CURLcode code = curl_easy_perform(curl);  
  222.         cout << "curl [" << idx << "] completed with status: "  
  223.                 << code << endl;  
  224.         cout << "rsp: " << RspArray[idx] << endl;  
  225.   
  226.         // clear handle //  
  227.         curl_easy_cleanup(curl);  
  228.     }  
  229.   
  230.     return 0;  
  231. }  
  232.   
  233. #define USE_MULTI_CURL  
  234.   
  235. struct timeval begin_tv, end_tv;  
  236.   
  237. int main(int argc, char * argv[])  
  238. {  
  239.     if (argc < 2)  
  240.     {  
  241.         return -1;  
  242.     }  
  243.     int num = atoi(argv[1]);  
  244.   
  245.     // 获取开始时间 //  
  246.     gettimeofday(&begin_tv, NULL);  
  247. #ifdef USE_MULTI_CURL  
  248.     // 使用multi接口进行访问 //  
  249.     curl_multi_demo(num);  
  250. #else  
  251.     // 使用easy接口进行访问 //  
  252.     curl_easy_demo(num);  
  253. #endif  
  254.     // 获取结束时间  //  
  255.     struct timeval end_tv;  
  256.     gettimeofday(&end_tv, NULL);  
  257.   
  258.     // 计算执行延时并输出,用于比较  //  
  259.     int eclapsed = (end_tv.tv_sec - begin_tv.tv_sec) * 1000 +  
  260.                    (end_tv.tv_usec - begin_tv.tv_usec) / 1000;  
  261.   
  262.     cout << "eclapsed time:" << eclapsed << "ms" << endl;  
  263.   
  264.     return 0;  
  265. }  


    上面的代码在关键位置都做了详细的注释,相信应该不难看懂。


  上篇博文讲到了如何使用multicurl来进行http并发访问,今天继续有关curl的主题,来八一八如何使用curl来上传文件,在介绍具体方法之前了解下目前http文件上传的基本实现。

    rfc1867描述了如何使用http协议来上传客户端文件,目前基本上所有的浏览器和web服务器都支持http文件上传,它的使用也十分的简单,具体的来说就是在页面上创建一个form表单,表单的enctype属性为multipart/form-data,action为接收上传文件的cgi url,请求方式为post,在表单中添加type属性为file的input,file input里面选择需要上传的文件,选择好后点击submit,服务器端收到multipart post请求后,会根据相关协议解析请求,然后保存上传的文件内容,Multipart表单示例:

[html] view plaincopy
  1. <form enctype="multipart/form-data" action="http://host:port/UploadFile" method=post>  
  2.     Upload :<br>  
  3.     <input name="userfile" type="file"><br>  
  4.     text field :<input type="text" name="text" value="text"><br>  
  5.     <input type="submit" value="提交"><input type=reset>  
  6. </form>  


    好了,现在来讲一讲curl的文件上传,对于curl来讲,其实它要完成的任务就是构建一个multipart/formdata HTTP POST请求。类似于往multipart form表单中添加type为file或者text的input item一样,curl也需要我们构造表单中的input item,curl_formadd函数可以帮助我们完成这个任务,它即可以添加普通的name-value section,也可以添加file upload section,下面举几个具体例子:

1、添加name/content section

[cpp] view plaincopy
  1. curl_formadd(&post, &last, CURLFORM_COPYNAME, "name",   CURLFORM_COPYCONTENTS, "content", CURLFORM_END);  

2、添加name/content/contenttype section

[cpp] view plaincopy
  1. curl_formadd(&post, &last, CURLFORM_COPYNAME, "name",   CURLFORM_COPYCONTENTS, "content",   CURLFORM_CONTENTTYPE, "type", CURLFORM_END);  

3、添加 file/filename section

[cpp] view plaincopy
  1. curl_formadd(&post, &last, CURLFORM_COPYNAME, "pic",   CURLFORM_FILE, "demo.jpg", CURLFORM_FILENAME, "upload.pic", CURLFORM_END);     

4、添加file/contenttype section

[cpp] view plaincopy
  1. curl_formadd(&post, &last, CURLFORM_COPYNAME, "pic",   CURLFORM_FILE, "demo.jpg", CURLFORM_FILENAME, "upload.pic",  CURLFORM_CONTENTTYPE, "image/jpeg", CURLFORM_END);  


     上面的post 和 last都是指向curl_httppost对象的指针, post指向的就是一个由所有section组成的链表的开端,last是该链表的尾指针。当我们添加完所有的form section之后,使用curl_easy_setopt(curl, CURLOPT_HTTPPOST,post)函数设置curl的http post,最后就是调用curl_easy_perform执行请求。需要注意的是,当使用libcurl的POST方式时,如果POST数据的大小大于1024个字节,libcurl不会直接发送POST请求,而是会分为两步执行请求:
    1、发送一个请求,该请求头部包含一个Expect: 100-continue的字段,用来询问server是否愿意接受数据
    2、当接收到从server返回的100-continue的应答后,它才会真正的发起POST请求,将数据发送给server。
    对于文件上传来说,文件大小往往会超过1024个字节,所以如果你确认你的服务器不会拒绝你的文件上传请求的话,可以禁止curl的Expect请求头,具体方法可以去看看我的另外一篇文章《libcurl的使用问题“Expect100-continue” 》。

    最后附上curl官网上提供的文件上传例子:

[cpp] view plaincopy
  1. /* This is an example application source code using the multi interface 
  2.  * to do a multipart formpost without "blocking". */  
  3. #include <stdio.h>  
  4. #include <string.h>  
  5. #include <sys/time.h>  
  6.   
  7. #include <curl/curl.h>  
  8.   
  9. int main(void)  
  10. {  
  11.   CURL *curl;  
  12.   
  13.   CURLM *multi_handle;  
  14.   int still_running;  
  15.   
  16.   struct curl_httppost *formpost=NULL;  
  17.   struct curl_httppost *lastptr=NULL;  
  18.   struct curl_slist *headerlist=NULL;  
  19.   static const char buf[] = "Expect:";  
  20.   
  21.   /* Fill in the file upload field. This makes libcurl load data from 
  22.      the given file name when curl_easy_perform() is called. */  
  23.   curl_formadd(&formpost,  
  24.                &lastptr,  
  25.                CURLFORM_COPYNAME, "sendfile",  
  26.                CURLFORM_FILE, "postit2.c",  
  27.                CURLFORM_END);  
  28.   
  29.   /* Fill in the filename field */  
  30.   curl_formadd(&formpost,  
  31.                &lastptr,  
  32.                CURLFORM_COPYNAME, "filename",  
  33.                CURLFORM_COPYCONTENTS, "postit2.c",  
  34.                CURLFORM_END);  
  35.   
  36.   /* Fill in the submit field too, even if this is rarely needed */  
  37.   curl_formadd(&formpost,  
  38.                &lastptr,  
  39.                CURLFORM_COPYNAME, "submit",  
  40.                CURLFORM_COPYCONTENTS, "send",  
  41.                CURLFORM_END);  
  42.   
  43.   curl = curl_easy_init();  
  44.   multi_handle = curl_multi_init();  
  45.   
  46.   /* initalize custom header list (stating that Expect: 100-continue is not 
  47.      wanted */  
  48.   headerlist = curl_slist_append(headerlist, buf);  
  49.   if(curl && multi_handle) {  
  50.   
  51.     /* what URL that receives this POST */  
  52.     curl_easy_setopt(curl, CURLOPT_URL, "http://www.example.com/upload.cgi");  
  53.     curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);  
  54.   
  55.     curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headerlist);  
  56.     curl_easy_setopt(curl, CURLOPT_HTTPPOST, formpost);  
  57.   
  58.     curl_multi_add_handle(multi_handle, curl);  
  59.   
  60.     curl_multi_perform(multi_handle, &still_running);  
  61.   
  62.     do {  
  63.       struct timeval timeout;  
  64.       int rc; /* select() return code */  
  65.   
  66.       fd_set fdread;  
  67.       fd_set fdwrite;  
  68.       fd_set fdexcep;  
  69.       int maxfd = -1;  
  70.   
  71.       long curl_timeo = -1;  
  72.   
  73.       FD_ZERO(&fdread);  
  74.       FD_ZERO(&fdwrite);  
  75.       FD_ZERO(&fdexcep);  
  76.   
  77.       /* set a suitable timeout to play around with */  
  78.       timeout.tv_sec = 1;  
  79.       timeout.tv_usec = 0;  
  80.   
  81.       curl_multi_timeout(multi_handle, &curl_timeo);  
  82.       if(curl_timeo >= 0) {  
  83.         timeout.tv_sec = curl_timeo / 1000;  
  84.         if(timeout.tv_sec > 1)  
  85.           timeout.tv_sec = 1;  
  86.         else  
  87.           timeout.tv_usec = (curl_timeo % 1000) * 1000;  
  88.       }  
  89.   
  90.       /* get file descriptors from the transfers */  
  91.       curl_multi_fdset(multi_handle, &fdread, &fdwrite, &fdexcep, &maxfd);  
  92.   
  93.       /* In a real-world program you OF COURSE check the return code of the 
  94.          function calls.  On success, the value of maxfd is guaranteed to be 
  95.          greater or equal than -1.  We call select(maxfd + 1, ...), specially in 
  96.          case of (maxfd == -1), we call select(0, ...), which is basically equal 
  97.          to sleep. */  
  98.   
  99.       rc = select(maxfd+1, &fdread, &fdwrite, &fdexcep, &timeout);  
  100.   
  101.       switch(rc) {  
  102.       case -1:  
  103.         /* select error */  
  104.         break;  
  105.       case 0:  
  106.       default:  
  107.         /* timeout or readable/writable sockets */  
  108.         printf("perform!\n");  
  109.         curl_multi_perform(multi_handle, &still_running);  
  110.         printf("running: %d!\n", still_running);  
  111.         break;  
  112.       }  
  113.     } while(still_running);  
  114.   
  115.     curl_multi_cleanup(multi_handle);  
  116.   
  117.     /* always cleanup */  
  118.     curl_easy_cleanup(curl);  
  119.   
  120.     /* then cleanup the formpost chain */  
  121.     curl_formfree(formpost);  
  122.   
  123.     /* free slist */  
  124.     curl_slist_free_all (headerlist);  
  125.   }  
  126.   return 0;  
  127. }  


1 0
原创粉丝点击