libcurl 学习笔记

libcurl在开发当中还是比较常用的,最近做一个新feature有用到,在这里我把我学到的一些东西分享一下,也许其中有错误,欢迎指正。

libcurl提供了一套名为“easy interface”的接口供使用。一个libcurl应用程序的生命周期应该是这样的:

  1. 调用init接口做初始化。
  2. 调用setopt接口设置一些发送请求的参数,当然,当response回来时调用的回调函数也是在这里。
  3. 调用perform接口发起请求。这个接口是阻塞的,当请求完成或者失败的情况下,这个调用才会返回。
  4. 当perform请求完成后,使用getinfo接口来获得关于这个请求的一些信息。
  5. 当所有的事情都完成之后,如果没有后续的事情要做,则调用cleanup接口来做最好的关闭清理工作。

APIS

libcurl 常用的API有下面这些:

  • curl_global_init()
  • curl_global_cleanup()
  • curl_easy_init()
  • curl_easy_cleanup()
  • curl_easy_setopt()
  • curl_easy_perform()
  • curl_easy_getinfo()

1 curl_easy_init()

#include <curl/curl.h>

 CURL * curl_easy_init();

调用这个函数是使用libcurl发起http请求的第一步,在调用成功后,这个函数会返回一个“curl easy handle”,在往后的程序中,调用其他的curl easy接口都需要这个handle作为输入。在程序结束需要关闭的时候必须要调用curl_easy_cleanup()

在程序中curl_easy_init()curl_easy_cleanup()总是成对出现。

如果在之前的程序中,你并没有调用curl_global_init(),那么curl_easy_init()会在内部调用它。然而,由于curl_global_init()不是线程安全的,如果这种场景发生在多个线程同时调用curl_easy_init(),程序可能会发生资源泄露问题---由于curl_global_init()curl_global_cleanup()没有成对出现导致的。

因此,libcurl官方建议在合适的地方显式调用curl_global_init()curl_global_cleanup(),不要依靠curl_easy_init()来触发curl_global_init()

2 curl_easy_cleanup()

#include <curl/curl.h>

void curl_easy_cleanup(CURL * handle );

这个函数是和curl_easy_init()成对使用的,用来关闭一个“easy”会话(session),在调用时,需要传入需要关闭的“handle”即之前curl_easy_init()的返回值。

调用这个函数会关闭掉对应“handle”所打开的所有连接(除非这个连接被多个“handle”使用)。

因为不断地init和cleanup会对性能有一些影响。所以,有一个建议是:使用同一个“handle”来处理多个连接。

对于一些协议,如FTP,POP3以及IMAP,由于这些协议的实现细节---在关闭的过程中会有“req->resp->req->resp”这样的流程。在这些场景中,调用curl_easy_cleanup()会导致之前设置的一些回调函数被调用。

3 curl_easy_setopt()

#include <curl/curl.h>

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

这个接口是用来定制发送细节的,我们大部分逻辑需要通过这个接口传给“curl easy handle”在下一步的curl_easy_perform()中进行传输。

3.1 定制请求行 --- CURLOPT_URL

#include <curl/curl.h>

CURLcode curl_easy_setopt(CURL *handle, CURLOPT_URL, char *URL);

该选项CURLOPT_URL用来设置请求行。该选项是唯一必须设置的选项。

其中URL需遵循以下格式:

[scheme://]host[:port]/path

libcurl 不会对传入的url进行验证,因此即使传入的是一个很离谱的字符串,接口的返回值也一样会是CURLE_OK

scheme

如果传入的URL没有定义scheme,那么libcurl会根据host进行猜测,如果host的域名符合DICT, FTP, IMAP, LDAP, POP3 或者 SMTP,那么对应的scheme会被使用,否则的话,会用http。默认的scheme可以用CURLOPT_DEFAULT_PROTOCOL进行定义。

如果scheme中设置的协议不是libcurl所支持的,那么在后续的调用curl_easy_perform会返回CURLE_UNSUPPORTED_PROTOCOL

host

host部分包含需要访问的服务地址,可以是hostname也可以是IP,对于一些类型的协议,可以用以下格式指定用户名和密码:

scheme:\\user:password;options@host:port\path

port

port部分为可选配置,如果不设置的话,libcurl会根据协议的类型来选择端口,如http默认使用80端口。

3.2 定制HTTP头部 --- CURLOPT_HTTPHEADER

以发送http请求为例,如果我们想在在发送的请求中带上一些http头,那么我们就需要调用:

CURLcode curl_easy_setopt(CURL *handle, CURLOPT_HTTPHEADER, struct curl_slist *headers);

使用CURLOPT_HTTPHEADER的option,把定制的http头放在struct curl_slist *headers中。

关于struct curl_slist *headers,curl提供了一组API进行操作:

  • curl_slist_append()
  • curl_slist_free_all()
#include <curl/curl.h>

struct curl_slist *curl_slist_append(struct curl_slist * list, const char * string );

该接口用来向curl_slist添加新的项目。比如说,我想添加一个自定义的头“x-hello-world”,对应的代码应该是如下:

struct curl_slist* slist = NULL;
 slist = curl_slist_append(slist, "x-hello-world: 1");

这样,最终发出来的报文中就会包含x-hello-world:1头,如果在这一步中设置了一个默认的http头,那么这个定制的头会覆盖默认的http头。

如果在这步设置了一个不完整的http头(这个头默认是存在的),如:Accept:(缺少数据),那么这个头会被在报文中被去掉。

如果想加一个没有内容的http头,需要使用这样的格式:MyHeader;

这些添加的头不能以\r\n(CRLF)结尾,因为在添加的过程中,函数会自动地给每个头加上\r\n

请求行需要用3.1中介绍的CURLOPT_URL进行设置。

另外一个接口:

#include <curl/curl.h>

void curl_slist_free_all(struct curl_slist * list);

这个接口用来释放一个curl_slist中所有的节点。
需要注意的是,在整个请求的发送过程中,该链表的内存会被libcurl直接使用,所有只有在传输全部完成的情况下,才可以调用curl_slist_free_all()释放该链表的内存。

4 curl_easy_perform

#include <curl/curl.h>

CURLcode curl_easy_perform(CURL * easy_handle );

在初始化(curl_easy_init)和设置必要的参数(curl_easy_setopt)之后,开发者就可以用curl_easy_perform发送请求了。
该接口会用阻塞的方式发送,只有在发送成功或者失败的情况下才会返回。
开发者可以用同一个easy handle发送多次请求。在这种情况下,libcurl会复用同一个连接,以这样的方式,可以节省CPU和内存的使用。
在同一个easy handle上同时调用curl_easy_perform不是线程安全的。如果想同时发送多个请求需要使用多个easy handle
libcurl另有一种非阻塞的发送方式,参见curl_multi_perform