C++ libcurl:关于curl_easy_perform的一些使用注意点

这个是在近日工作中涉及到libcurl的使用时,得出的一些思考。

关于curl_easy_perform的官方文档,见这里

主要背景是这样的:在写完了一个需要用libcurl提出HTTP请求的功能,自测一下假设发生某些异常,会对系统带来什么影响。结果测到以下条件的时候:

  • 第1次调用curl_easy_perform的参数中,CURLOPT_URL是一个无效域名(注意是域名、也就是网址,不是某个IP地址)
  • 在第1个curl_easy_perform发出去后的20秒内,尝试发送第2个curl_easy_perform请求(这个请求是在另外一个线程处理的)

然后意想不到的情况发生了,通过日志跟踪,这次运行结果是:第1个curl在20秒后返回了CURLE_COULDNT_RESOLVE_HOST(6),表示host没有找到,在这个结果返回之后,第2个curl才开始执行。也就是说,后一个curl_easy_perform在前一个发出后、直到第1个curl返回CURLE_COULDNT_RESOLVE_HOST的期间,进入了不必要的等待状态。

文档告诉我们:curl_easy_perform是一个同步返回执行结果的接口,直到执行成功或者失败之前会一直阻塞。后面还有一句话:
You must never call this function simultaneously from two places using the same easy_handle. Let the function return first before invoking it another time. If you want parallel transfers, you must use several curl easy_handles.
人话:不能在两个地方同时针对同一个easy_handle调用这个函数,要先返回一个结果再去调用另一个。如果你需要并行运行的时候,你需要使用多个curl easy_handle。

原来前面提到的情况就是这次运行理应得到的结果,这个运行结果并不是因为你是在线程池里放入N个调用了curl_easy_perform接口的函数就能实现异步http请求的。如果你调用的是一个全局的easy_handle,并且别人也有可能使用这个easy_handle的时候,就会发生后来者被无故阻塞的现象。目前猜测是curl_easy_perform的实现里是加了独占锁的,而且在解析DNS之前就加了,但是我没有证据。


curl_easy_perform在easy.h里的声明是:

CURL_EXTERN CURLcode curl_easy_perform(CURL *curl);

其中CURL的定义,似乎跟编译器有关:

#if defined(BUILDING_LIBCURL) || defined(CURL_STRICTER)
typedef struct Curl_easy CURL;
typedef struct Curl_share CURLSH;
#else
typedef void CURL;
typedef void CURLSH;
#endif

Curl_easy结构体的成员有:

struct Curl_easy {
  /* First a simple identifier to easier detect if a user mix up this easy
     handle with a multi handle. Set this to CURLEASY_MAGIC_NUMBER */
  unsigned int magic;

  /* first, two fields for the linked list of these */
  struct Curl_easy *next;
  struct Curl_easy *prev;

  struct connectdata *conn;
  struct Curl_llist_element connect_queue;
  struct Curl_llist_element conn_queue; /* list per connectdata */

  CURLMstate mstate;  /* the handle's state */
  CURLcode result;   /* previous result */

  struct Curl_message msg; /* A single posted message. */

  /* Array with the plain socket numbers this handle takes care of, in no
     particular order. Note that all sockets are added to the sockhash, where
     the state etc are also kept. This array is mostly used to detect when a
     socket is to be removed from the hash. See singlesocket(). */
  curl_socket_t sockets[MAX_SOCKSPEREASYHANDLE];
  unsigned char actions[MAX_SOCKSPEREASYHANDLE]; /* action for each socket in
                                                    sockets[] */
  int numsocks;

  struct Names dns;
  struct Curl_multi *multi;    /* if non-NULL, points to the multi handle
                                  struct to which this "belongs" when used by
                                  the multi interface */
  struct Curl_multi *multi_easy; /* if non-NULL, points to the multi handle
                                    struct to which this "belongs" when used
                                    by the easy interface */
  struct Curl_share *share;    /* Share, handles global variable mutexing */
#ifdef USE_LIBPSL
  struct PslCache *psl;        /* The associated PSL cache. */
#endif
  struct SingleRequest req;    /* Request-specific data */
  struct UserDefined set;      /* values set by the libcurl user */
  struct CookieInfo *cookies;  /* the cookies, read from files and servers.
                                  NOTE that the 'cookie' field in the
                                  UserDefined struct defines if the "engine"
                                  is to be used or not. */
#ifndef CURL_DISABLE_HSTS
  struct hsts *hsts;
#endif
#ifndef CURL_DISABLE_ALTSVC
  struct altsvcinfo *asi;      /* the alt-svc cache */
#endif
  struct Progress progress;    /* for all the progress meter data */
  struct UrlState state;       /* struct for fields used for state info and
                                  other dynamic purposes */
#ifndef CURL_DISABLE_FTP
  struct WildcardData wildcard; /* wildcard download state info */
#endif
  struct PureInfo info;        /* stats, reports and info data */
  struct curl_tlssessioninfo tsi; /* Information about the TLS session, only
                                     valid after a client has asked for it */
#if defined(CURL_DOES_CONVERSIONS) && defined(HAVE_ICONV)
  iconv_t outbound_cd;         /* for translating to the network encoding */
  iconv_t inbound_cd;          /* for translating from the network encoding */
  iconv_t utf8_cd;             /* for translating to UTF8 */
#endif /* CURL_DOES_CONVERSIONS && HAVE_ICONV */
#ifdef USE_HYPER
  struct hyptransfer hyp;
#endif
};

暂时没有看到哪个是锁,改天再研究
CURLcode是一个枚举值集合,保存了CURL调用的返回值,目前共99个,最常见的CURLE_OK(0)代表正常,其他的都是异常。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 可以通过使用curl_easy_setopt函数来设置输出处理函数,具体的语法为:curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, write_data); 其中 write_data 是输出处理函数的名称。 ### 回答2: 在使用cURL库中的curl_easy_setopt函数时,可以通过设置输出处理函数来处理cURL执行操作的输出。设置输出处理函数的步骤如下: 1. 创建一个用于处理输出的函数。 这个函数的格式必须符合以下原型: size_t handle_output(void *ptr, size_t size, size_t nmemb, void *userdata); 函数的参数依次为ptr,size,nmemb和userdata。其中,ptr是接收到的数据指针,size是每个数据项的大小,nmemb是接收到的数据项数目,userdata是一个用户指定的指针,可用于传递自定义数据。 2. 在代码中定义代表cURL会话的CURL类型的变量,并通过curl_easy_init函数进行初始化。 3. 调用curl_easy_setopt函数,并将设置项设为CURLOPT_WRITEFUNCTION,第二个参数为输出处理函数的指针。 4. 如果需要传递自定义数据给输出处理函数,则还可以使用curl_easy_setopt函数设置CURLOPT_WRITEDATA选项,第二个参数为自定义数据的指针。 5. 执行需要的cURL操作,如发送HTTP请求。 下面是一个简单的示例代码,演示如何设置输出处理函数: ```c #include <stdio.h> #include <curl/curl.h> size_t handle_output(void *ptr, size_t size, size_t nmemb, void *userdata) { // 处理接收到的数据 // ptr是接收到的数据指针,size是每个数据项的大小,nmemb是接收到的数据项数目,userdata是自定义数据指针 // 示例代码中,直接打印接收到的数据到控制台 size_t total_size = size * nmemb; fwrite(ptr, 1, total_size, stdout); return total_size; } int main() { CURL *curl; curl = curl_easy_init(); if (curl) { curl_easy_setopt(curl, CURLOPT_URL, "http://www.example.com"); // 设置请求的URL curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, handle_output); // 设置输出处理函数 // 可选:设置自定义数据指针 // curl_easy_setopt(curl, CURLOPT_WRITEDATA, custom_data_ptr); CURLcode res = curl_easy_perform(curl); // 执行请求操作 if (res != CURLE_OK) { fprintf(stderr, "curl_easy_perform() failed: %s\n", curl_easy_strerror(res)); } curl_easy_cleanup(curl); } return 0; } ``` 以上是设置curl_easy_setopt的输出处理函数的简要步骤和示例代码。根据需要,可以在输出处理函数中自定义处理接收到的数据。 ### 回答3: curl_easy_setopt函数是libcurl库中用于设置选项的函数,可以通过该函数设置curl的各种行为和选项。其中一个常见的选项是设置输出处理函数,即设置curl用于处理响应数据的回调函数。 要设置curl的输出处理函数,需要使用curl_easy_setopt函数,并将选项参数设置为CURLOPT_WRITEFUNCTION,然后将回调函数作为参数传递给该选项。 具体的代码如下所示: ``` // 声明输出处理函数,需要符合curl_write_callback类型的定义 size_t writeCallback(char* ptr, size_t size, size_t nmemb, void* userdata) { // 在这里对收到的数据进行处理 // ptr:收到的数据指针 // size:每个数据元素的大小 // nmemb:数据元素的数量 // userdata:用户自定义数据指针 // 返回值表示实际处理的字节数 } // 初始化curl CURL* curl = curl_easy_init(); if(curl) { // 设置输出处理函数 curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeCallback); // 这里可以继续设置其他选项... // 执行curl请求 CURLcode res = curl_easy_perform(curl); if(res != CURLE_OK) { // 请求失败的处理逻辑... } // 清理curl curl_easy_cleanup(curl); } ``` 在上述代码中,通过curl_easy_setopt函数将选项参数设置为CURLOPT_WRITEFUNCTION,并将回调函数writeCallback作为参数传递。在writeCallback回调函数中,可以对收到的响应数据进行处理。在函数内部,可以通过参数ptr、size和nmemb访问到接收到的数据。需要注意,writeCallback回调函数的返回值需要表示实际处理的字节数。 通过这种方式,我们可以自定义输出处理函数来处理curl接收到的响应数据,实现自己的数据处理逻辑。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值