libcurl

来源:互联网 发布:长春大学教务处软件 编辑:程序博客网 时间:2024/04/30 01:04
翻译文献:libcurl官方文档libcurl库的使用部分。

libcurl 基础

         命令行curl工具的核心(引擎)是libcurl, 现在他也是成千上万个用来传输互联网数据的工具,服务或者是应用的核心(引擎)。

面向传输(Transfer oriented)


        用户不需要成为网络协议方面的专家或者对网络网络或协议有很深的了解就可以方便的使用libcurl, 这是把libcurl设计为面向传输(面向传输封装的底层的协议 by Monk)的目的。 你可以设定一些传输的细节和特定的信息, 然后用libcurl来帮助你实现传输。

       也就是说,网络和协议是有许多缺陷(pitfalls)和特殊情形(special cases)的领域(areas), 所有你对这些缺陷和特殊情形了解的越多, 你就能更好的理解libcurl的选项和工作方式。 更不必说, 这些知识是无价的, 当你在debug或者当程序没有按照你设想的哪样运行。

        libcurl的简单应用场景差不多都是那么几行代码,但是绝大多数的应用是需要更多代码来完成的。

(Simple by default, more on demand)

        libcurl默认只做一些简单基础的传输, 但是如果你想要更多高级的特性(advanced features),可以通过设置正确的选项来实现。例如, 默认的libcurl不支持http cookies,但是你可以设置运行http cookies。

       这使libcurl的行为(behaviors)易于控制(to guess and depend on), 也使得维护老版本的行为或加入新特性更容易。只有应用要求的特性才会得到相应的行为(Only applications that actually ask for and use the new features will get that behavior)。

简单/默认的句柄(Easy handle)

         你需要学习的libcurl基础

          首先, 创建用来传输的简单句柄:

            CURL* easy_handle = curl_easy_init();

          然后,可以设置各种选项来控制上行传输(upcoming transfer)。例如,设置URL

            /* Set URL to operate on */
            res = curl_easy_setopt(easy_handle, CURLOPT_URL, "http://example.com/");

          创建简单句柄和设置选项不会使实际的传输发生,甚至不会发生任何事情,当传输真正开始时, libcurl才会存储你设置的选项(Creating the easy handle and setting options on it doesn't make any transfer happen, and usually don't even make much more happen other than libcurl storing your wish to be used later when the transfer actually occurs)。多数的语法检查和输入合法性可能被延迟, 因为 curl_easy_setopt  不会解析(complain), 但是这并不意味着输入就是正确有效的,稍后你可能会捕获到错误。

           所有这些选项都是一体的(sticky)。设置会一直留在句柄里,直到你去改变他,或者调用 curl_easy_reset() 。

            当你设置完所有想要的选项后, 你可以开始真正的传输了(fire off the actual transfer)。

           实际传输阶段(perform the transfer phase)可以用的方式和函数调用来完成, 这取决于你的应用想要什么样的行为和libcurl怎么程序架构更好的融合。这会在稍后的章节做进一步的阐述。

           在传输完成后,你可以判断传输是否成功并且提取libcurl在传输期间收集的各种静态信息(stats and various information)。

           当传输正在进行时, libcurl会调用你指定的函数--回调函数--来发送数据, 读取数据,以及其他很多事件。             

重用(Reuse)

简单句柄被设计(meant and designed to)为可重用。当你用简单句柄来处理单个传输,你可以了马上用他来处理另一个传输。这样做有很多的好处。

驱动传输(“Driver” transfers)

           libcurl提供三种不同的传输方式。使用哪种方式完全取决于你自己和你的需求。

                        1. 简单的接口让你用同步的方式来实现单个传输。libcurl会完成整个传输过程然后返回不管传输是成功或是失败。

                        2. ‘multi’ 接口是用来当你同时要实现多个传输或者你想要一个非阻塞的传输机制时使用的。

                        3. ‘multi_socket’接口是是普通‘multi’接口一个特例(slight variation),但是他是基于事件的。当应用有成千上万的并发量时,‘multi_socket’是被推荐的API。

Driving with the easy interface

          选用 'easy'这个名字仅仅是因为使用libcurl确实一个很简单, 并且限制也很少。例如,他可以使用一个函数来完成一次传输:

           res = curl_easy_perform(easy_handle);

          如果服务端很慢,或者传输数据比较大,或者有网络超时,这个函数调用可能会用很长的时间才能完成。当然, 你可以设置超时时间,在特定的条件下,这同样会花费大量的时间。

          在使用简单接口时,如果你想要你的应用程序在libcurl传输的过程时做一些其他的事情,你想要用到多线程。如果你用简单接口和多线程来实现并发传输 ,那就要在各个线程中分别实现传输(怎么看都觉得这句话是多余的,附上原文:If you want to do multiple simultaneous transfers when using the easy interface, you need to perform each of the transfers in its own thread)。

Driving with the multi interface

        见名知意,‘multi’ 就是来实现多路复用的,在同一个线程完成所有的操作(The name ‘multi’ is for multi , as in multiple parallel transfers, all done in the same single thread.)。‘multi’ API is 非阻塞的,但是同样可以用于单个传输。

        ‘multi’传输和简单句柄一样是在“easy” CURL*句柄中设置的。但是使用‘multi’接口, 你需要CURLM* 句柄来驱动。‘multi’句柄可以处理一个或多个简单句柄:

CURLM* multi_handle = curl_multi_init();

         ‘multi’句柄也可以通过 curl_multi_setopt() 设置具体选项,但是更为简单的方式是什么也不用设置。

           为了驱动‘multi’接口传输, 你首先要把所用的单个简单句柄都添加都‘multi’句柄中。你可以在任何时刻从‘multi’句柄中添加或删除他们。从中删除一个简单句柄同时也删除了他们之间的联系,这导致相应的传输也会立刻被终止。

           添加一个简单句柄到‘multi’句柄是非常简单的:

           curl_multi_add_handle(multi_handle, easy_handle);

          从‘multi’中删除简单句柄也是同样的简单:

            cul_multi_remove_handle(multi_handle, easy_handle);

           Having added the easy handles repersenting the transfers you want to perform , you write the transfer loop. With the multi interface, you di the looping so you can ask libcurl for a set of file descriptors and a timeout value and do the select() call yourself, or you can use the slightly simplified version which dies that for us, with curl_multi_wait.  The simplest loop would basicallu be this:(note that a real application would check return codes)

            int transfers_running;
            do{
                  curl_muitl_wait(multi_handle, NULL, 0, 1000, NULL);
                  curl_multi_perform(multi_handle, &transfers_running);
            }while(transfers_running);

            curl_multi_wait的第四个参数被设置为1000,是一个超时时间,以毫秒为单位。在函数返回前,这是他等待任何事件发生的最长时间。如果不想等待那么长的时间,可以再次调用curl_multi_perform,但是这样做会丢失精度。

             作为替代,我们可以用select()自己实现,我们从libcurl中提取文件描述符和超时时间(在实际应用中需要需要检查返回值):  

              int  transfers_running;
              do{
                    fd_set fdread;
                    fd_set fdwrite;
                    fd_set fdexcep;
                    int maxfd = -1;
                    long timeout;
   
                    /* extract timeout value */
                    curl_multi_timeout(multi_handle, &timeout);
                    if(timeout < 0){
                        timeout = 1000;
                    }
                    
                    /* convert to struct usable by select */
                    timeout.tv_sec = timeout / 1000;
                    timeout.tv_usec = (timeout % 1000);
    
                    FD_ZERO(&fdread);
                    FD_ZERO(&fdwrite);
                    FD_ZERO(&fdexcep);

                    /* get file descriptors from the transfers */
                    mc = curl_multi_fdset(multi_handle, &fdread, &fdwrite, &fdexcep, &maxfd);
                    
                    if(maxfd == -1){
                          SHORT_SLEEP;
                    }else{
                          select(maxfd + 1, &fdread, &fdwrite, &fdexcep, &timeout);
                    }

                    /* timeout or readable/writeabke sockets */
                    curl_multi_perform(multi_handle, &transfers_running);
              }while(transfers_running);

Driving with the "multi_socket" interface
          "multi_socket" 是普通"multi"接口的一个特殊版本(extra spicy version),也是专门为事件驱动应用程序设计的。
          "multi_socket" 支持并发传输 - 所有的操作都在单个线程中完成(all done in the same single thread),并且支持在单个应用中实现成百上千个传输。
 
        Many easy handles
           和普通"multi"接口一样,"multi_socket"也是通过curl_multi_add_handle()把简单句柄加入到事件集合中。
           可以在任何时候从事件集合中添加和删除句柄。删除句柄和添加句柄一样的简单curl_multi_remove_handle()。但是,还是建议在传输完成(不管是成功或是失败)之后,再再从事件集合中删除句柄。
         
        Multi_socket callbacks
            正如上面所解释的,事件驱动机制依赖应用中运用了sockets的libcurl以及libcurl在等待哪些sockets是否可读或者可写。
            也需要告诉libcurl等待什么时间失效,因为libcurl不能自己设置超时时间。所以libcurl需要通知应用来更新超时时间。
       
        Socket_callback
             libcurl给出所等待sockets事件的回调函数声明,但是需要应用来实现它。   

                   int socket_callback(CURL* easy,
                                       curl_socket_t s,
                                       int what,
                                       void* userp,
                                       void* socketp)
                   {

                           /* told about the socket 's' */
                    }

                    /* set the callback in the multi handle */
                    curl_multi_setopt(multi_handle, CURLMOPT_SOCKETFUNCTION, socket_callback);
             这样,libcurl就设置和删除应用程序监控的sockets。应用程序通知潜在的基于事件驱动的底层系统等待事件的发生。如果多个sockets在被监控,则这个回调函数可能会被多次调用,当sockets状态发生改变的时候,回调也会被执行。

                    int running_handles;
                    ret = curl_multi_socket_action(multi_handle,
                                                   sockfd, /* the socket with activity */
                                                   ev_bitmask, /* the specific activity */
                                                   &running_handles);
           timer_callback:
                    int timer_callback(multi_handle,   /* multi handle */
                                       timeout_ms,      /* milliseconds to wait */
                                       userp)          /* private callback pointer */
                     {
                          /* new value to wait for is ... */
                      }
                       
                      /* set the callback in the multi handles */
                      curl_multi_sockopt(multi_handle, CURLMOPT_TIMERFUNCTION, timer_cb);

                      curl_multi_socket_action(multi, CURL_SOCKET_TIMEOUT, 0, running);
           How to start everything
                    当完成了事件的添加和设置,就可以开始传输了。
                    /* all easy handles and callbacks are setup */
                    curl_multi_socket_action(multi, CURL_SOCKET_TIMEOUT, 0, &running);
                    
                    /* now the callbacks should have been called and we have sockets to wait for  and possibly a timeout, too. Make the event system do it magic */
                    event_base_dispatch(event_base);  // libevent2 has this API
                    
                    /* at this point we have exited the loop */  
           When is it done?
                   curl_multi_socket_action() 会返回正在运行的事件(running handles)的计数器。当计数器变为零时,就表示没有传输在进行。
                   每次计数器发生变化时,更可以通过调用curl_multi_info_read()来获得特定传输返回的信息。  

Connection resue

          -------------按照 Monk 的理解-----------------------
          就是当一个事件完成时,libcurl不会释放事件所占有的资源,而是留给随后的事件使用,这就可以避免重新创建新的句柄。这样性能就会得到提升。                  

Callbacks
    Wirte data:
        ---prototype
        size_t write_callback(char* ptr, size_t size, size_t nmemb, void* userdata);
        curl_easy_setopt(handle, CURLOPT_WRITEDATA, custom_pointer/*pointes to private data*/);
        curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, write_cb);
            ptr : 指向需要传输的数据
            size 和 nmemb 的乘积为数据的长度
            通常最大的传输长度在curl.h头文件中定义为16KB。如果设置了CURLOPT_HEADRE,这数据头信息也会被传给回调函数,最大长度为CURL_MAX_HTTP_HEADER(100KB)      
 
      Read data:
          ---prototype
          size_t read_callback(char* buffer, size_t size, size_t nitems, void* stream);
          curl_easy_setopt(handle, CURLOPT_READDATA, custom_pointer/*points to private data*/);
          curl_easy_setopt(handle, CURLOPT_READFUNCTION, read_cb);
                 buffer大小为 size 和 nitems 的乘积

      Progress callback
          -------------按照Monk的理解----------------
           就是在事件执行过程中,通过回调获得事件的状态信息。

          ----prototype
          int xfer_callback(void* clientp,  /* points to the private data */
                            curl_off_t dltotal, /* the number of bytes libcurl expects to download in this transfer */
                            curl_off_t dlnow, /* the number of bytes downloaded so far */
                            curl_off_t ultotal,/* the total number of bytes libcurl expects to upload in this transfer */
                            curl_off_t ulnow); /* the number of bytes uploaded so far */
          curl_easy_setopt(handle, CURLOPT_XFERINFOFUNCTION, xfer_callback);
          curl_easy_setopt(handle, CURLOPT_XFERINFODATA, custom_pointer);

       Header data
           ---prototype
           size_t header_callback(char* ptr, size_t size, size_t nmemb, void* userdata);
           curl_easy_setopt(handle, CURLOPT_HEADERFUNCTION, header_cb);
           curl_easy_setopt(handle, CURLOPT_HEADERDATA, custon_pointer);

       Debug data
          ---prototype
          int debug_callback(CURL* handle,
                             curl_infotype type,
                             char* data,
                             size_t size,
                             void* userdata);
          curl_easy_setopt(handle, CURLOPT_DEBUGFUNCTION, debug_cb);
          curl_easy_setopt(handle,CURLOPT_DEBUGDATA,custom_pointer);
       
       Sockopt
          ---prototype
          int sockopt_callback(void* clientp,
                   curl_socket_t curlfd,
                               curlsocktype purpose);
          curl_easy_setopt(handle, CURLOPT_SOCKOPTFUNCTION, sockopt_cb);
          curl_easy_setopt(handle,CURLOPT_SOCKOPTDATA, custom_pointer);
Cleanup
     Multi API
         每用 multi API 完成一次简单传输,你应该用curl_multi_info_read()来确定是哪个事件已经完成,并从curl_multi_remove_handle()从事件集中删除。
         当从事件集中把最后一个简单事件都删除后,应该调用curl_multi_cleanup(multi_handle) 来释放资源。
 
     Easy handle
         当简单事件完成时,如果不希望其他的事件再使用这个事件所占的资源,则可以调用curl_easy_cleanup(easy_handle)来释放资源。

Sample

/********* Sample code generated by the curl command-line tool **********
 * All curl_easy_setopt() options are documented at:
 * https://curl.haxx.se/libcurl/c/curl_easy_setopt.html
 ************************************************************************/
#include <curl/curl.h>

int main(int argc, char *argv[])
{
  CURLcode ret;
  CURL *hnd;

  hnd = curl_easy_init();
  curl_easy_setopt(hnd, CURLOPT_URL, "http://example.com");
  curl_easy_setopt(hnd, CURLOPT_NOPROGRESS, 1L);
  curl_easy_setopt(hnd, CURLOPT_USERAGENT, "curl/7.45.0");
  curl_easy_setopt(hnd, CURLOPT_MAXREDIRS, 50L);
  curl_easy_setopt(hnd, CURLOPT_SSH_KNOWNHOSTS, "/home/daniel/.ssh/known_hosts");
  curl_easy_setopt(hnd, CURLOPT_TCP_KEEPALIVE, 1L);

  /* Here is a list of options the curl code used that cannot get generated
     as source easily. You may select to either not use them or implement
     them yourself.

  CURLOPT_WRITEDATA set to a objectpointer
  CURLOPT_WRITEFUNCTION set to a functionpointer
  CURLOPT_READDATA set to a objectpointer
  CURLOPT_READFUNCTION set to a functionpointer
  CURLOPT_SEEKDATA set to a objectpointer
  CURLOPT_SEEKFUNCTION set to a functionpointer
  CURLOPT_ERRORBUFFER set to a objectpointer
  CURLOPT_STDERR set to a objectpointer
  CURLOPT_HEADERFUNCTION set to a functionpointer
  CURLOPT_HEADERDATA set to a objectpointer

  */

  ret = curl_easy_perform(hnd);

  curl_easy_cleanup(hnd);
  hnd = NULL;

  return (int)ret;
}
/**** End of sample code ****/        

CURLcode return code
      method 1:
         const char* str = curl_easy_strerror(error);
         printf("libcurl said %s\n", str);
      method 2:
         curl error[CURL_ERROR_SIZE];
         CURLcode ret = curl_easy_setopt(handle, CURLOPT_ERRORBUFFER, error);

     我的神 .......

     才翻译这么一点点!!!

     有啥错误,还望给个看客姥爷们批评指正,第一次翻译这东西,难免有不周之处,哎, 又误了多少子弟!

-------- Created at : 2016-11-23 18:00

-------- Author   : Monk

-------- Modified  : 2016-11-24 17:00

0 0
原创粉丝点击