1 简介
存在这样的应用场景,客户端需要从web服务器上面下载一些文件。要实现这个功能有以下几种选择:
1)利用SOCKET,自己实现HTTP中的Get请求。
2)利用现有的库实现。
虽然自己完成一个发送Get请求的模块并不困难,但是考虑到通过url获取文件的模块应该是软件当中的一项基础设施,一定存在可复用的模块。所以还是尽量用现成的东西去做,避免重复造轮子。
这时,找到了libcurl库。它有如下优点:开源、接口简单(C类型的接口)、轻巧。
下面内容的组织结构如下:第2节给一个实例关于如何利用libcurl库实现文件的下载。第3、4节是Libcurl接口的分类、结合一个例子介绍multi接口使用、注意事项。第5节对easy接口和multi接口做了总结和比较。
2 使用Libcurl下载文件的一个例子
/*下载url的文件*/
void UseLibcurl(const char* strUrl)
{
CURL *curl_handle;
const char *bodyfilename = "body.png"; //文件路径
FILE *bodyfile;
/* 全局初始化*/
curl_global_init(CURL_GLOBAL_ALL);
/* easy 接口初始化*/
curl_handle = curl_easy_init();
/* 设置URL */
curl_easy_setopt(curl_handle, CURLOPT_URL, strUrl);
/* 设置写数据函数*/
curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, write_data);
/* 打开文件*/
bodyfile = fopen(bodyfilename,"wb");
if (bodyfile == NULL) {
curl_easy_cleanup(curl_handle);
return;
}
/* 设置报文的主体写到文件当中而不是标准输出 */
curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, bodyfile);
/* 执行请求 */
res = curl_easy_perform(curl_handle);
/* 关闭文件 */
fclose(bodyfile);
/* 清理easy句柄*/
curl_easy_cleanup(curl_handle);
/* 全局清理 */
curl_global_cleanup();
}
通过简单的接口调用,可以实现文件的下载。同时Libcurl封装了文件的读写部分,可以写到标准输出或者交给用户的回调函数当中。
3 Libcurl接口的分类
Libcurl中接口分为4种类型:全局环境接口(globalinterface)、easy类型接口(easy interface)、multiple类型接口(multiple interface)、shared类型接口(shared interface)。
Globalinterface: libcurl的全局环境变量必须在程序初运行中建立和维持。初始化时必须调用curl_global_init,结束时必须调用curl_global_cleanup接口。值得注意的是,global接口不保证线程安全。必须在其他线程没有建立时调用。在C++中可以在一个静态变量的构造函数当中使用curl_global_init,析构函数当中使用curl_global_cleanup。
Easy interface:easy接口是同步的。当调用curl_easy_perform时,就会执行文件的传输。当网络传输完毕,函数结束。
Multiinterface:mutli接口则是异步接口。使用者可以在数据流传输的过程做一些处理,可以使用一个线程进行多个文件同时下载。The multi interface allows you to select()。
Shared interface:用于创建共享资源,被多个线程共用。通过使用curl_share_setopt设置共享的数据类型,目前支持的类型是DNS和COOKIE数据。共享数据由用户保证资源的互斥。必须用curl_share_setopt提供互斥的回调函数来保护资源。
4 一个关于Multi interface的例子
int main(int argc, char **argv)
{
CURL *http_handle;
CURL *http_handle2;
CURLM *multi_handle;
int running_hanlde; /* keep number of running handles */
http_handle = curl_easy_init();
http_handle2 = curl_easy_init();
/* 任务1的url */
curl_easy_setopt(http_handle, CURLOPT_URL, "http://www.haxx.se/");
/* 任务2的url */
curl_easy_setopt(http_handle2, CURLOPT_URL, "http://localhost/");
/* 多任务句柄 */
multi_handle = curl_multi_init();
/* 添加任务 */
curl_multi_add_handle(multi_handle, http_handle);
curl_multi_add_handle(multi_handle, http_handle2);
/* 开始执行请求 */
while(CURLM_CALL_MULTI_PERFORM ==
curl_multi_perform(multi_handle, &running_hanlde));
while(running_hanlde) {
struct timeval timeout;
int rc; /* select() return code */
fd_set fdread;
fd_set fdwrite;
fd_set fdexcep;
int maxfd;
FD_ZERO(&fdread);
FD_ZERO(&fdwrite);
FD_ZERO(&fdexcep);
/* 设置超时时间和重连次数 */
timeout.tv_sec = 1;
timeout.tv_usec = 0;
/* 获取文件描述信息*/
curl_multi_fdset(multi_handle, &fdread, &fdwrite, &fdexcep, &maxfd);
rc = select(maxfd+1, &fdread, &fdwrite, &fdexcep, &timeout);
switch(rc) {
case SOCKET_ERROR:
/* select 错误*/
break;
case 0:
default:
/* 超时或socket可读写 */
while(CURLM_CALL_MULTI_PERFORM ==
curl_multi_perform(multi_handle, &running_hanlde));
break;
}
}
curl_multi_cleanup(multi_handle);
curl_easy_cleanup(http_handle);
curl_easy_cleanup(http_handle2);
return 0;
}
这个例子使用multi接口执行两个url请求。
curl_multi_fdset返回内部socket的信息。接下来调用select,一旦拥有可读或可写的socket,就应该立即调用curl_multi_perform。只要返回值是CURLM_CALL_MULTI_PERFORM,需要一直调用curl_multi_perform接口。
总结
Easy接口的编程模型
Easy接口都是单线程模型,调用简单,适用于单个url请求的场景。
Multi接口的编程模型
Multi接口则是多线程模型。当有多个任务需要同时交替进行时,则需要使用这种方式。如果使用多个线程来分别执行easy请求,每个请求是单独完成的。可以根据实际需求选择使用方式。