libcurl 学习笔记
libcurl在开发当中还是比较常用的,最近做一个新feature有用到,在这里我把我学到的一些东西分享一下,也许其中有错误,欢迎指正。
libcurl提供了一套名为“easy interface”的接口供使用。一个libcurl应用程序的生命周期应该是这样的:
- 调用init接口做初始化。
- 调用setopt接口设置一些发送请求的参数,当然,当response回来时调用的回调函数也是在这里。
- 调用perform接口发起请求。这个接口是阻塞的,当请求完成或者失败的情况下,这个调用才会返回。
- 当perform请求完成后,使用getinfo接口来获得关于这个请求的一些信息。
- 当所有的事情都完成之后,如果没有后续的事情要做,则调用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
。