curl是Linux下一个很是著名的下载库,经过这个库,能够很简单的实现文件的下载等操做。
看一个简单的例子:html
#include <curl/curl.h>
#include <stdio.h>
#include <string.h>node
CURL *curl;
CURLcode res;api
size_t write_data(void *ptr, size_t size, size_t nmemb, void *stream)
{
if (strlen((char *)stream) + strlen((char *)ptr) > 999999) return 0;
strcat(stream, (char *)ptr);
return size*nmemb;
}数组
char *down_file(char *filename)
{
static char str[10000000];
strcpy(str, “”);
//return “<a href=/”http://gtk.awaysoft.com//”>”;
curl_easy_setopt(curl, CURLOPT_URL, filename); //设置下载地址
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 3);//设置超时时间
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_data);//设置写数据的函数
curl_easy_setopt(curl, CURLOPT_WRITEDATA, str);//设置写数据的变量
res = curl_easy_perform(curl);//执行下载
str[9999999] = ‘/0′;
if(CURLE_OK != res) return NULL;//判断是否下载成功
return str;
}浏览器
int main()
{
char url[200];
curl = curl_easy_init();//对curl进行初始化
char *result;
while(fgets(url, 200, stdin)){
result = down_file(url);
if (result) puts(result);
else puts(“Get Error!”);
printf(“/nPlease Input a url:”);缓存
}
curl_easy_cleanup(curl);//释放curl资源安全
return 0;
}服务器
下面是转载的curl详细使用:cookie
curl->libcurl的手册能够查看网络
http://curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTWRITEDATA
译者:JGood(http://blog.csdn.net/JGood )
译者注:这是一篇介绍如何使用libcurl的入门教程。文档不是逐字逐句按原文翻译,而是根据笔者对libcurl的理解,参考原文写成。文中用到的一 些例子,可能不是出自原文,而是笔者在学习过程当中,写的一些示例程序(笔者使用的libcurl版本是:7.19.6)。出如今这里主要是为了更好的说明 libcurl的某些api函数的使用。许多例子都参考libcurl提供的example代码。原文example中的提供的示例程序彻底使用C语言, 而这里笔者提供的例子使用C++语言。由于能力有限,对于libcurl的某些理解和使用可能有误,欢迎批评指正。
本文档介绍了在应用程序开发过程当中,如何正确使用libcurl的基本方式和指导原则。文档使用C语言来调用libcurl的接口,固然也适用于其余与C语言接近的语言。
文档主要针对使用libcurl来进行开发的人员。文档所掼的应用程序泛指你写的源代码,这些代码使用了libcurl进行数据传输。
更多关于libcurl的功能和接口信息,能够在相关的主页上查阅。
有不少种不一样的方式来编译C语言代码。这里使用UNIX平台下的编译方式。即便你使用的是其余的操做系统,你仍然能够经过阅读本文档来获取许多有用的信息。
编译
你的编译器必须知道libcurl头文件的位置。因此在编译的时候,你要设置头文件的包含路径。可使用curl-config工具来获取这方面的信息:
$ curl-config –cflags
连接
编译完源码(这时的源代码不是指libcurl的源代码,你是你本身写的程序代码)以后,你还必须把目标文件连接成单个可执行文件。你要连接 libcurl库,以及libcurl所依赖的其余库,例如OpenSLL库。固然可能还须要一些其余的操做系统库。最后你还要设置一些编译选项,固然可 以使用curl-config工具简化操做:
$curl-config –libs
是否使用SSL
定制编译libcurl。与其余库不一样的是,libcurl能够定制编译,根据实际须要是否支持某些特性,如是否支持SSL传输,像HTTPS和 FTPS。若是决定须要支持SSL,必须在编译时正确的设置。可使用’curl-config’来判断libcurl库是否支持SSL:
$ curl-config –feature
autoconf宏
当你编写配置脚原本检测libcurl及其相应设置时,你可使用预约义宏。文档docs/libcurl/libcurl.m4告诉你如何使用这些宏。
libcurl的开发人员花费很大的努力,使libcurl尽量在大多数平台上正常运行。
应用程序在使用libcurl以前,必须先初始化libcurl。libcurl只需初始化一次。可使用如下语句进行初始化:
curl_global_init();
curl_global_init()接收一个参数,告诉libcurl如何初始化。参数CURL_GLOBAL_ALL 会使libcurl初始化全部的子模块和一些默认的选项,一般这是一个比较好的默认参数值。还有两个可选值:
CURL_GLOBAL_WIN32
只能应用于Windows平台。它告诉libcurl初始化winsock库。若是winsock库没有正确地初始化,应用程序就不能使用socket。在应用程序中,只要初始化一次便可。
CURL_GLOBAL_SSL
若是libcurl在编译时被设定支持SSL,那么该参数用于初始化相应的SSL库。一样,在应用程序中,只要初始化一次便可。
libcurl有默认的保护机制,若是在调用curl_easy_perform时它检测到尚未经过curl_global_init进行初始 化,libcurl会根据当前的运行时环境,自动调用全局初始化函数。但必须清楚的是,让系统自已初始化不是一个好的选择。
当应用程序再也不使用libcurl的时候,应该调用curl_global_cleanup来释放相关的资源。
在程序中,应当避免屡次调用curl_global_init和curl_global_cleanup。它们只能被调用一次。
在运行时根据libcurl支持的特性来进行开发,一般比编译时更好。能够经过调用curl_version_info函数返回的结构体来获取运行时的具 体信息,从而肯定当前环境下libcurl支持的一些特性。下面是笔者在visual studio2008中调用相关函数获取libcurl版本信息的截图:
首先介绍libcurl中被称为easy interface的api函数,全部这些函数都是有相同的前缀:curl_easy 。
当前版本的libcurl也提供了multi interface,关于这些接口的详细使用,在下面的章节中会有介绍。在使用multi interface以前,你首先应该理解如何使用easy interface。
要使用easy interface,首先必须建立一个easy handle,easy handle用于执行每次操做。基本上,每一个线程都应该有本身的easy handle用于数据通讯(若是须要的话)。千万不要在多线程之间共享同一个easy handle。下面的函数用于获取一个easy handle :
CURL *easy_handle = curl_easy_init();
在easy handle上能够设置属性和操做(action)。easy handle就像一个逻辑链接,用于接下来要进行的数据传输。
使用curl_easy_setopt函数能够设 置easy handle的属性和操做,这些属性和操做控制libcurl如何与远程主机进行数据通讯。一旦在easy handle中设置了相应的属性和操做,它们将一直做用该easy handle。也就是说,重复使用easy hanle向远程主机发出请求,先前设置的属性仍然生效。
easy handle的许多属性使用字符串(以/0结尾的字节数组)来设置。经过curl_easy_setopt函数设置字符串属性时,libcurl内部会自动拷贝这些字符串,因此在设置完相关属性以后,字符串能够直接被释放掉(若是须要的话)。
easy handle最基本、最经常使用的属性是URL。你应当经过CURLOPT_URL属性提供适当的URL:
curl_easy_setopt(easy_handle, CURLOPT_URL, “http://blog.csdn.net/JGood “);
假设你要获取URL所表示的远程主机上的资源。你须要写一段程序用来完成数据传输,你可能但愿直接保存接收到的数据而不是简单的在输出窗口中打印它们。因此,你必须首先写一个回调函数用来保存接收到的数据。回调函数的原型以下:
size_t write_data(void *buffer, size_t size, size_t nmemb, void *userp);
可使用下面的语句来注册回调函数,回调函数将会在接收到数据的时候被调用:
curl_easy_setopt(easy_handle, CURLOPT_WRITEFUNCTION, write_data);
能够给回调函数提供一个自定义参数,libcurl不处理该参数,只是简单的传递:
curl_easy_setopt(easy_handle, CURLOPT_WRITEDATA, &internal_struct);
若是你没有经过CURLOPT_WRITEFUNCTION属性给easy handle设置回调函数,libcurl会提供一个默认的回调函数,它只是简单的将接收到的数据打印到标准输出。你也能够经过 CURLOPT_WRITEDATA属性给默认回调函数传递一个已经打开的文件指针,用于将数据输出到文件里。
下面是一些平台相关的注意点。在一些平台上,libcurl不能直接操做由应用程序打开的文件。因此,若是使用默认的回调函数,同时经过 CURLOPT_WRITEDATA属性给easy handle传递一个文件指针,应用程序可能会执行失败。若是你但愿本身的程序能跑在任何系统上,你必须避免出现这种状况。
若是以win32动态链接库的形式来使用libcurl,在设置CURLOPT_WRITEDATA属性时,你必须同时 使用CURLOPT_WRITEFUNCTION来注册回调函数。不然程序会执行失败(笔者尝试只传递一个打开的文件指针而不显式设置回调函数,程序并无崩溃。多是我使用的方式不正确。)。
固然,libcurl还支持许多其余的属性,在接下来的篇幅里,你将会逐步地接触到它们。调用下面的函数,将执行真正的数据通讯:
success = curl_easy_perform(easy_handle);
curl_easy_perfrom将链接到远程主机,执行必要的命令,并接收数据。当接收到数据时,先前设置的回调函数将被调用。libcurl可能一 次只接收到1字节的数据,也可能接收到好几K的数据,libcurl会尽量多、及时的将数据传递给回调函数。回调函数返回接收的数据长度。若是回调函数 返回的数据长度与传递给它的长度不一致(即返回长度 != size * nmemb),libcurl将会终止操做,并返回一个错误代码。
当数据传递结束的时候,curl_easy_perform将返回一个代码表示操做成功或失败。若是须要获取更多有关通讯细节的信息,你能够设置CURLOPT_ERRORBUFFER属性,让libcurl缓存许多可读的错误信息。
easy handle在完成一次数据通讯以后能够被重用。这里很是建议你重用一个已经存在的easy handle。若是在完成数据传输以后,你建立另外一个easy handle来执行其余的数据通讯,libcurl在内部会尝试着重用上一次建立的链接。
对于有些协议,下载文件可能包括许多复杂的子过程:日志记录、设置传输模式、选择当前文件夹,最后下载文件数据。使用libcurl,你不须要关心这一切,你只需简单地提供一个URL,libcurl会给你作剩余全部的工做。
下面的这个例子演示了如何获取网页源码,将其保存到本地文件,并同时将获取的源码输出到控制台上。
/** * @brief libcurl接收到数据时的回调函数 * * 将接收到的数据保存到本地文件中,同时显示在控制台上。 * * @param [in] buffer 接收到的数据所在缓冲区 * @param [in] size 数据长度 * @param [in] nmemb 数据片数量 * @param [in/out] 用户自定义指针 * @return 获取的数据长度 */
size_t process_data(void *buffer, size_t size, size_t nmemb, void *user_p)
{
FILE *fp = (FILE *)user_p;
size_t return_size = fwrite(buffer, size, nmemb, fp);
cout << (char *)buffer << endl;
return return_size;
}
int main(int argc, char **argv)
{
// 初始化libcurl
CURLcode return_code;
return_code = curl_global_init(CURL_GLOBAL_WIN32);
if (CURLE_OK != return_code)
{
cerr << "init libcurl failed." << endl; return -1;
}
// 获取easy handle
CURL *easy_handle = curl_easy_init();
if (NULL == easy_handle)
{
cerr << "get a easy handle failed." << endl;
curl_global_cleanup();
return -1;
}
FILE *fp = fopen("data.html", "ab+");
// // 设置easy handle属性
curl_easy_setopt(easy_handle, CURLOPT_URL, http://blog.csdn.net/JGood);
curl_easy_setopt(easy_handle, CURLOPT_WRITEFUNCTION, &process_data);
curl_easy_setopt(easy_handle, CURLOPT_WRITEDATA, fp);
// 执行数据请求 curl_easy_perform(easy_handle);
// 释放资源 fclose(fp);
curl_easy_cleanup(easy_handle);
curl_global_cleanup();
return 0;
}
首先一个基本原则就是:绝对不该该在线程之间共享同一个libcurl handle,无论是easy handle仍是multi handle(将在下文中介绍)。一个线程每次只能使用一个handle。
libcurl是线程安全的,但有两点例外:信号(signals)和SSL/TLS handler。 信号用于超时失效名字解析(timing out name resolves)。libcurl依赖其余的库来支持SSL/STL,因此用多线程的方式访问HTTPS或FTPS的URL时,应该知足这些库对多线程 操做的一些要求。详细能够参考:
OpenSSL: http://www.openssl.org/docs/crypto/threads.html#DESCRIPTION
GnuTLS: http://www.gnu.org/software/gnutls/manual/html_node/Multi_002dthreaded-applications.html
NSS: 宣称是多线程安全的。
传输失败老是有缘由的。你可能错误的设置了一些libcurl的属性或者没有正确的理解某些属性的含义,或者是远程主机返回一些没法被正确解析的内容。
这里有一个黄金法则来处理这些问题:将CURLOPT_VERBOSE属性设置为1,libcurl会输出通讯过程当中的一些细节。若是使用的是http协议,请求头/响应头也会被输出。将CURLOPT_HEADER设为1,这些头信息将出如今消息的内容中。
固然不能否认的是,libcurl还存在bug。当你在使用libcurl的过程当中发现bug时,但愿可以提交给咱们,好让咱们可以修复这些bug。你在 提交bug时,请同时提供详细的信息:经过CURLOPT_VERBOSE属性跟踪到的协议信息、libcurl版本、libcurl的客户代码、操做系 统名称、版本、编译器名称、版本等等。
若是你对相关的协议了解越多,在使用libcurl时,就越不容易犯错。
libcurl提供协议无关的方式进行数据传输。因此上传一个文件到FTP服务器,跟向HTTP服务器提交一个PUT请求的操做方式是相似的:
1. 建立easy handle或者重用先前建立的easy handle。
2. 设置CURLOPT_URL属性。
3. 编写回调函数。在执行上传的时候,libcurl经过回调函数读取要上传的数据。(若是要从远程服务器下载数据,能够经过回调来保存接收到的数据。)回调函数的原型以下:
size_t function(char *bufptr, size_t size, size_t nitems, void *userp);
bufptr指针表示缓冲区,用于保存要上传的数据,size * nitems是缓冲区数据的长度,userp是一个用户自定义指针,libcurl不对该指针做任何操做,它只是简单的传递该指针。可使用该指针在应用程序与libcurl之间传递信息。
4. 注册回调函数,设置自定义指针。语法以下:
// 注册回调函数 curl_easy_setopt(easy_handle, CURLOPT_READFUNCTION, read_function); // 设置自定义指针 curl_easy_setopt(easy_handle, CURLOPT_READDATA, &filedata);
5. 告诉libcurl,执行的是上传操做。
curl_easy_setopt(easy_handle, CURLOPT_UPLOAD, 1L);
有些协议在没有预先知道上传文件大小的状况下,可能没法正确判断上传是否结束,因此最好预先使用CURLOPT_INFILESIZE_LARGE属性:告诉它要上传文件的大小:
/* in this example, file_size must be an curl_off_t variable */ curl_easy_setopt(easy_handle, CURLOPT_INFILESIZE_LARGE, file_size);
6. 调用curl_easy_perform。
接下来,libcurl将会完成剩下的全部工做。在上传文件过程当中,libcurl会不断调用先前设置的回调函数,用于将要上传的数据读入到缓冲区,并执行上传。
下面的例子演示如何将文件上传到FTP服务器。笔者使用的是IIS自带的FTP服务,同时在FTP上设置了可写权限。
/** * @brief 读取数据的回调。 */ size_t read_data(void *buffer, size_t size, size_t nmemb, void *user_p) { return fread(buffer, size, nmemb, (FILE *)user_p); } int main(int argc, char **argv) { // 初始化libcurl CURLcode code; code = curl_global_init(CURL_GLOBAL_WIN32); if (code != CURLE_OK) { cerr << "init libcurl failed." << endl; return -1; } FILE *fp = fopen("a.html", "rb"); if (NULL == fp) { cout << "can't open file." << endl; curl_global_cleanup(); return -1; } // 获取文件大小 fseek(fp, 0, 2); int file_size = ftell(fp); rewind(fp); // 获取easy handle CURL *easy_handle = NULL; easy_handle = curl_easy_init(); if (NULL == easy_handle) { cerr << "get a easy handle failed." << endl; fclose(fp); curl_global_cleanup(); return -1; } // 设置eash handle属性curl_easy_setopt(easy_handle, CURLOPT_URL, ftp://127.0.0.1/upload.html);curl_easy_setopt(easy_handle, CURLOPT_UPLOAD, 1L); curl_easy_setopt(easy_handle, CURLOPT_READFUNCTION, &read_data); curl_easy_setopt(easy_handle, CURLOPT_READDATA, fp);curl_easy_setopt(easy_handle, CURLOPT_INFILESIZE_LARGE, file_size); // 执行上传操做 code = curl_easy_perform(easy_handle); if (code == CURLE_OK) { cout << "upload successfully." << endl; } // 释放资源 fclose(fp); curl_easy_cleanup(easy_handle); curl_global_cleanup(); return 0; }
客户端向服务器发送请求时,许多协议都要求提供用户名与密码。libcurl提供了多种方式来设置它们。
一些协议支持在URL中直接指定用户名和密码,相似于: protocol://user:password@example.com/path/。libcurl能正确的识别这种URL中的用户名与密码并执行 相应的操做。若是你提供的用户名和密码中有特殊字符,首先应该对其进行URL编码。
也能够经过CURLOPT_USERPWD属性来设置用户名与密码。参数是格式如 “user:password ”的字符串:
curl_easy_setopt(easy_handle, CURLOPT_USERPWD, "user_name:password");
(下面这几段文字我理解地模模糊糊)有时候在访问代理服务器的时候,可能时时要求提供用户名和密码进行用户身份验证。这种状况下,libcurl提供了另外一个属性CURLOPT_PROXYUSERPWD:
curl_easy_setopt(easy_handle, CURLOPT_PROXYUSERPWD, "user_name:password");
在UNIX平台下,访问FTP的用户名和密码可能会被保存在$HOME/.netrc文件中。libcurl支持直接从这个文件中获取用户名与密码:
curl_easy_setopt(easy_handle, CURLOPT_NETRC, 1L);
在使用SSL时,可能须要提供一个私钥用于数据安全传输,经过CURLOPT_KEYPASSWD来设置私钥:
curl_easy_setopt(easy_handle, CURLOPT_KEYPASSWD, "keypassword");
上一章介绍了如何在libcurl中,对须要身份验证的URL设置用户名与密码。在使用HTTP协议时,客户端有不少种方式向服务器提供验证信息。默认的 HTTP验证方法是”Basic”,它将用户名与密码以明文的方式、经Base64编码后保存在HTTP请求头中,发往服务器。固然这不太安全。
当前版本的libcurl支持的验证方法有:basic, Digest, NTLM, Negotiate, GSS-Negotiate and SPNEGO。(译者感叹:搞Web这么多年,尽然不知道这些Http的验证方式,实在惭愧。)能够经过CURLOPT_HTTPAUTH属性来设置具体 的验证方式:
curl_easy_setopt(easy_handle, CURLOPT_HTTPAUTH, CURLAUTH_DIGEST);
向代理服务器发送验证信息时,能够经过CURLOPT_PROXYAUTH设置验证方式:
curl_easy_setopt(easy_handle, CURLOPT_PROXYAUTH, CURLAUTH_NTLM);
也能够同时设置多种验证方式(经过按位与), 使用‘CURLAUTH_ANY‘将容许libcurl能够选择任何它所支持的验证方式。经过CURLOPT_HTTPAUTH或 CURLOPT_PROXYAUTH属性设置的多种验证方式,libcurl会在运行时选择一种它认为是最好的方式与服务器通讯:
curl_easy_setopt(easy_handle, CURLOPT_HTTPAUTH, CURLAUTH_DIGEST|CURLAUTH_BASIC); //curl_easy_setopt(easy_handle, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
这一章介绍如何使用libcurl以Post方式向HTTP服务器提交数据。
方法一,也是最简单的方式,就像html中使用<form>标签提交数据同样,只需向libcurl提供一个包含数据的字符串便可。下面是笔者学习过程当中的一个demo程序:
int main(int argc, char **argv) { code = curl_global_init(CURL_GLOBAL_WIN32); CURL *easy_handle = curl_easy_init(); curl_easy_setopt(easy_handle, CURLOPT_URL,http://localhost:2210/Default.aspx); // 单个域post curl_easy_setopt(easy_handle, CURLOPT_POSTFIELDS, "name=jgood&address=hangzhou"); code = curl_easy_perform(easy_handle); curl_easy_cleanup(easy_handle); curl_global_cleanup(); return 0; }
在asp.net Web服务器上跟踪调试,获得客户程序提交上来的数据,下面是截图:
上面的代码够简单吧~_~ 有时候,咱们须要提交一些二进制数据到HTTP服务器,使用方法一就不行了,由于方法一中实际提交的是一个字符串,字符串遇到/0就表示结束了。因此在上 传二进制数据的时候,必须明确的告诉libcurl要提交的数据的长度。在上传二进制数据的时候,还应该设置提交的Content-Type头信息。下面 的示例代码:
int main(int argc, char **argv) { curl_global_init(CURL_GLOBAL_WIN32); CURL *easy_handle = curl_easy_init(); // 上传二进制数据 char data[] = { 1, 0, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0 }; curl_slist *http_headers = NULL; http_headers = curl_slist_append(http_headers, "Content-Type: text/xml"); curl_easy_setopt(easy_handle, CURLOPT_HTTPHEADER, http_headers);curl_easy_setopt(easy_handle, CURLOPT_URL, http://localhost:2210/Default.aspx);curl_easy_setopt(easy_handle, CURLOPT_POSTFIELDS, data); curl_easy_setopt(easy_handle, CURLOPT_POSTFIELDSIZE, sizeof(data)); curl_easy_perform(easy_handle); curl_slist_free_all(http_headers); curl_easy_cleanup(easy_handle); curl_global_cleanup(); return 0; }
在asp.net Web服务器上跟踪调试,获得客户程序提交上来的二进制数据,下面是截图:
上面介绍的两种方式,能够完成大部分的HTTP POST操做。但上面的两种方式都不支持multi-part formposts。Multi-part formposts被认为是提交二进制数据(或大量数据)的更好方法,能够在RFC1867, RFC2388中找到他们的定义。何为Multi-part?其实,就我理解,就是在Post提交的时候,有不一样的数据单元,每一个单元有本身的名称与内 容,内容能够是文本的,也能够是二进制的。同时,每一个数据单元均可以有本身的消息头,MIME类型,这些数据单元组成一个链表,提交到HTTP服务器。 libcurl提供了方便的api用于支持multi-part formposts。使用curl_formadd函数,能够添加不一样的数据数据单元,而后提交到服务器。下面是一个multi-part formposts的例子(更详细的使用,请参考:http://curl.haxx.se/libcurl/c/curl_formadd.html ):
int main() {
curl_global_init(CURL_GLOBAL_WIN32);
CURL *easy_handle = curl_easy_init();
// 使用multi-parts form post curl_easy_setopt(easy_handle, CURLOPT_URL,http://localhost:2210/Default.aspx); curl_httppost *post = NULL; curl_httppost *last = NULL; // 文本数据 curl_formadd(&post, &last, CURLFORM_COPYNAME, "name", CURLFORM_COPYCONTENTS, "JGood", CURLFORM_END); curl_formadd(&post, &last, CURLFORM_COPYNAME, "address", CURLFORM_COPYCONTENTS, "HangZhou", CURLFORM_END); // 文本文件中的数据 curl_formadd(&post, &last, CURLFORM_COPYNAME, "file", CURLFORM_FILECONTENT, "ReadMe.txt", CURLFORM_END); curl_easy_setopt(easy_handle, CURLOPT_HTTPPOST, post); curl_easy_perform(easy_handle); curl_formfree(post); curl_easy_cleanup(easy_handle); curl_global_cleanup(); return 0; }
最后要说明的是,全部在easy handle上设置的属性都是”sticky”的,什么意思?就是说在easy handle上设置的属性都将被保存,即便执行完curl_easy_perform以后,这些属性值仍然存在。经过将CURLOPT_HTTPGET设 为1可使easy handle回到最原始的状态:
curl_easy_setopt(easy_handle, CURLOPT_HTTPGET, 1L);
libcurl支持通讯过程当中的进度控制。经过将CURLOPT_NOPROCESS设置为0开启进度支持。该选项默认值为1。对大多数应用程序,咱们需 要提供一个进度显示回调。libcurl会不按期的将当前传输的进度经过回调函数告诉你的程序。回调函数的原型以下:
int progress_callback(void *clientp, double dltotal, double dlnow, double ultotal, double ulnow);
经过CURLOPT_PROGRESSFUNCTION注册该回调函数。参数clientp是一个用户自定义指针,应用程序经过 CURLOPT_PROCESSDATA属性将该自定义指定传递给libcurl。libcurl对该参数不做任何处理,只是简单将其传递给回调函数。
在C++中使用libcurl跟在C语言中没有任何区别,只有一个地方要注意:回调函数不能是类的非静态成员函数。例如:
class AClass { static size_t write_data(void *ptr, size_t size, size_t nmemb, void *ourpointer) { /* do what you want with the data */ } }
什么是代理?Merrian-Webster的解释是:一个经过验证的用户扮演另外一个用户。今天,代理已经被普遍的使用。许多公司提供网络代理服务器,容许员工的网络客户端访问、下载文件。代理服务器处理这些用户的请求。
libcurl支持SOCKS和HTTP代理。使用代理,libcurl会把用户输入的URL提交给代理服务器,而不是直接根据URL去访问远程资源。
当前版本的libcurl并不支持SOCKS代理的全部功能。
对于HTTP代理来讲,即便请求的URL不是一个合法的HTTP URL(比方你提供了一个ftp的url),它仍然会先被提交到HTTP代理。
代理选项
CURLOPT_PROXY属性用于设置libcurl使用的代理服务器地址:
curl_easy_setopt(easy_handle, CURLOPT_PROXY, "proxy-host.com:8080");
能够把主机名与端口号分开设置:
curl_easy_setopt(easy_handle, CURLOPT_PROXY, "proxy-host.com"); curl_easy_setopt(easy_handle, CURLOPT_PROXYPORT, "8080"); // 端口号是用字符串仍是整数??
有些代理服务器要求用户经过验证以后才容许接受其请求,此时应该先提供验证信息:
curl_easy_setopt(easy_handle, CURLOPT_PROXYUSERPWD, "user:password");
还要告诉libcurl使用的代理类型(若是没有提供,libcurl会认为是HTTP代理):
curl_easy_setopt(easy_handle, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS4);
环境变量
对于有些协议,libcurl会自动检测并使用一些环境变量,并根据这些环境变量来肯定要使用的代理服务器。这些环境变量的名称格式通常是” [protocol]_proxy”(注意小写)。例如输入一个HTTP的URL,那么名称为”http_proxy”的环境变量就会被检测是否存在,如 果存在,libcurl会使用该环境变量指定的代理。相同的规则也适用于FTP。
这些环境变量的值的格式必须是这样的:”[protocol://][user:password@]machine[:port]“。libcurl会忽略掉[protocol://],若是没有提供端口号,libcurl使用该协议的默认端口。
有两个比较特殊的环境变量:’all_proxy’与’no_proxy’。若是一个URL所对应的协议,它的环境变量没有设置,那么 ‘all_proxy’指定的代理将被使用。’no_proxy’则指定了一个不该被使用的代理主机的列表。例如:no_proxy的值是 ’192.168.1.10′,即便存在http_proxy,它的值也是’192.168.1.10′,’192.168.1.10′也不会被做为代 理。no_proxy=”*”表示不容许使用任何代理。
显式地将CURLOPT_PROXY属性设置为空,能够禁止libcurl检查并使用环境变量来使用代理。
SSL和代理
SSL为点到点通讯提供安全保障。它包含一些强壮的加密措施和其余安全检测,这使得上面讲到的代理方式不适用于SSL。除非代理服务器提供专用通道,对进 出该代理服务器的数据不做任何检测或禁止。经过HTTP代理服务器打开SSL链接,意味着代理服务器要直接链接到目标主机的指定端口。由于代理服务器对在 专用通道上传输的数据的类型毫无所知,因此它每每会使某些机制失效,如缓存机制。许多组织只容许在443端口上建立这种类型的数据通道。
代理通道(Tunneling Through Proxy)
正如上面讲到的,要使SSL工做必须在代理服务器建立专用数据通道,一般专用通道只被限制应用于HTTPS。经过HTTP代理在应用程序与目标之间建立一 个专用数据通道,应该预防在该专有通道上执行非HTTP的操做,如进行FTP上传或执行FTP命令。代理服务器管理员应该禁止非法的操做。
经过CURLOPT_HTTPPROXYTUNNEL属性来告诉libcurl使用代理通道:
curl_easy_setopt(easy_handle, CURLOPT_HTTPPROXYTUNNEL, 1L);
有时候你想经过代理通道执行日常的HTTP操做,而实际上却可能使你不通过代理服务器而直接与远程主机进行交互。libcurl不会代替这种新引入的行为。
自动配置代理
许多浏览器支持自动配置代理,例如NetScape。libcurl并不支持这些。
当须要发送屡次请求时,应该重复使用easy handle。
每次执行完curl_easy_perform,licurl会继续保持与服务器的链接。接下来的请求可使用这个链接而没必要建立新的链接(若是目标主机是同一个的话)。这样能够减小网络开销。
即便链接被释放了,libcurl也会缓存这些链接的会话信息,这样下次再链接到目标主机上时,就可使用这些信息,从而减小从新链接所需的时间。
FTP链接可能会被保存较长的时间。由于客户端要与FTP服务器进行频繁的命令交互。对于有访问人数上限的FTP服务器,保持一个长链接,可使你不须要排除等待,就直接能够与FTP服务器通讯。
libcurl会缓存DNS的解析结果。
在从此的libcurl版本中,还会添加一些特性来提升数据通讯的效率。
每一个easy handle都会保存最近使用的几个链接,以备重用。默认是5个。能够经过CURLOPT_MAXCONNECTS属性来设置保存链接的数量。
若是你不想重用链接,将CURLOPT_FRESH_CONNECT属性设置为1。这样每次提交请求时,libcurl都会先关闭之前建立的链接,而后重 新建立一个新的链接。也能够将CURLOPT_FORBID_REUSE设置为1,这样每次执行完请求,链接就会立刻关闭。
当使用libcurl发送http请求时,它会自动添加一些http头。咱们能够经过CURLOPT_HTTPHEADER属性手动替换、添加或删除相应的HTTP消息头。
Host
http1.1(大部分http1.0)版本都要求客户端请求提供这个信息头。
Pragma
“no-cache”。表示不要缓冲数据。
Accept
“*/*”。表示容许接收任何类型的数据。
Expect
以POST的方式向HTTP服务器提交请求时,libcurl会设置该消息头为”100-continue”,它要求服务器在正式处理该请求以前,返回一个”OK”消息。若是POST的数据很小,libcurl可能不会设置该消息头。
当前愈来愈多的协议都构建在HTTP协议之上(如:soap),这主要归功于HTTP的可靠性,以及被普遍使用的代理支持(能够穿透大部分防火墙)。 这些协议的使用方式与传统HTTP可能有很大的不一样。对此,libcurl做了很好的支持。
自定义请求方式(CustomRequest)
HTTP支持GET, HEAD或者POST提交请求。能够设置CURLOPT_CUSTOMREQUEST来设置自定义的请求方式,libcurl默认以GET方式提交请求:
curl_easy_setopt(easy_handle, CURLOPT_CUSTOMREQUEST, "MYOWNREQUEST");
修改消息头
HTTP协议提供了消息头,请求消息头用于告诉服务器如何处理请求;响应消息头则告诉浏览器如何处理接收到的数据。在libcurl中,你能够自由的添加这些消息头:
struct curl_slist *headers=NULL; /* init to NULL is important */ headers = curl_slist_append(headers, "Hey-server-hey: how are you?"); headers = curl_slist_append(headers, "X-silly-content: yes"); /* pass our list of custom made headers */ curl_easy_setopt(easyhandle, CURLOPT_HTTPHEADER, headers); curl_easy_perform(easyhandle); /* transfer http */ curl_slist_free_all(headers); /* free the header list */
对于已经存在的消息头,能够从新设置它的值:
headers = curl_slist_append(headers, "Accept: Agent-007"); headers = curl_slist_append(headers, "Host: munged.host.line");
删除消息头
对于一个已经存在的消息头,设置它的内容为空,libcurl在发送请求时就不会同时提交该消息头:
headers = curl_slist_append(headers, "Accept:");
(这段文字理解可能有误码)以非GET的方式提交HTTP请求时,若是设置了自定义的消息头”Transfer- Encoding:chunked”,libcurl会分块提交数据,即便要上传的数据量已经知道。在上传数据大小未知的状况下,libcurl自动采用 分块上传数据。(译者注:非GET方式提交请求,提交的数据量每每比较大。)
每一次http请求,都包含一个表示当前使用http版本的消息头。libcurl默认使用HTTP 1.1。能够经过CURLOPT_HTTP_VERSION属性来设置具体的版本号:
curl_easy_setopt(easy_handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
并非因此的协议都像HTTP那样,经过消息头来告诉服务器如何处理请求。对于FTP,你就要使用另外的方式来处理。
发送自定义的命令到ftp服务器,意味着你发送的命令必须是能被ftp服务器理解的命令(FTP协议中定义的命令,参考rfc959)。
下面是一个简单的例子,在文件传输操做操做以前删除指定文件:
headers = curl_slist_append(headers, "DELE file-to-remove"); /* pass the list of custom commands to the handle */ curl_easy_setopt(easyhandle, CURLOPT_QUOTE, headers); //curl_easy_setopt(easyhandle, CURLOPT_POSTQUOTE, headers); // 在数据传输以后操行删除操做
curl_easy_perform(easyhandle); /* transfer ftp data! */ curl_slist_free_all(headers); /* free the header list */
FTP服务器执行命令的顺序,同这些命令被添加到列表中顺序是一致的。发往服务器的命令列表中,只要有一个命令执行失败,ftp服务器就会返回一个错误代码,此时libcurl将直接返回CURLE_QUOTE_ERROR,再也不执行剩余的FTP命令。
将CURLOPT_HEADER设置为1,libcurl获取目标文件的信息,并以HTTP消息头的样式来输出消息头。
FTP自定义CUSTOMREQUEST
使用CURLOPT_CUSTOMREQUEST属性,能够向FTP服务器发送命令。”NLST”是ftp默认的列出文件列表的命令。 下面的代码用于列出FTP服务器上的文件列表:
int main(int argc, char **argv) { curl_global_init(CURL_GLOBAL_WIN32); CURL *easy_handle = curl_easy_init(); curl_easy_setopt(easy_handle, CURLOPT_URL, "ftp://127.0.0.1/");curl_easy_setopt(easy_handle, CURLOPT_CUSTOMREQUEST, "NLST");
curl_easy_perform(easy_handle); curl_easy_cleanup(easy_handle); curl_global_cleanup(); return 0; }
cookie是一个键值对的集合,HTTP服务器发给客户端的cookie,客户端提交请求的时候,也会将cookie发送到服务器。服务器能够根据 cookie来跟踪用户的会话信息。cookie有过时时间,超时后cookie就会失效。cookie有域名和路径限制,cookie只能发给指定域名 和路径的HTTP服务器。
cookie以消息头”Set-Cookie”的形式从HTTP服务器发送到客户端;客户端发以消息头”Cookie”的形式将Cookie提交到 HTTP服务器。为了对这些东西有个直观的概念,下图是FireFox中,使用Firebug跟踪到的cookie消息头:
在libcurl中,能够经过CURLOPT_COOKIE属性来设置发往服务器的cookie:
curl_easy_setopt(easy_handle, CURLOPT_COOKIE, "name1=var1; name2=var2;");
下面的例子演示了如何使用libcurl发送cookie信息给HTTP服务器,代码很是的简单:
int main(int argc, char **argv) { curl_global_init(CURL_GLOBAL_WIN32); CURL *easy_handle = curl_easy_init(); curl_easy_setopt(easy_handle, CURLOPT_URL,http://localhost:2210/Default.aspx); curl_easy_setopt(easy_handle, CURLOPT_COOKIE, "name=JGood; address=HangZhou"); curl_easy_perform(easy_handle); curl_easy_cleanup(easy_handle); curl_global_cleanup(); return 0; }
下图是在ASP.NET Web服务器上调试时跟踪到的Cookie数据:
在实在的应用场景中,你可能须要保存服务器发送给你的cookie,并在接下来的请求中,把这些cookie一并发往服务器。因此,能够把上次从服务器收 到的全部响应头信息保存到文本文件中,当下次须要向服务器发送请求时,经过CURLOPT_COOKIEFILE属性告诉libcurl从该文件中读取 cookie信息。
设置CURLOPT_COOKIEFILE属性意味着激活libcurl的cookie parser。在cookie parser被激活以前,libcurl忽略因此以前接收到的cookie信息。cookie parser被激活以后,cookie信息将被保存内存中,在接下来的请求中,libcurl会自动将这些cookie信息添加到消息头里,你的应用程序 不须要作任何事件。大多数状况下,这已经足够了。须要注意的是,经过CURLOPT_COOKIEFILE属性来激活cookie parser,给CURLOPT_COOKIEFILE属性设置的一个保存cookie信息的文本文件路径,可能并不须要在磁盘上物理存在。
若是你须要使用NetScape或者FireFox浏览器的cookie文件,你只要用这些浏览器的cookie文件的路径来初始化 CURLOPT_COOKIEFILE属性,libcurl会自动分析cookie文件,并在接下来的请求过程当中使用这些cookie信息。
libcurl甚至可以把接收到的cookie信息保存成能被Netscape/Mozilla的浏览器所识别的cookie文件。经过把这些称为 cookie-jar。经过设置CURLOPT_COOKIEJAR选项,在调用curl_easy_cleanup释放easy handle的时候,全部的这些cookie信息都会保存到cookie-jar文件中。这就使得cookie信息能在不一样的easy handle甚至在浏览器之间实现共享。
在使用FTP协议进行数据传输的时候,须要建立两个链接。第一个链接用于传输控制命令,另外一个链接用于传输数据。(关于FTP的通讯过程,请参考这篇文章:http://www.wangjia.net/bo-blog/post/698/)。 FTP通讯须要建立两个链接这个事实每每被不少人忽略。根据第二个链接的发起方是谁,能够分为主动模式与被动模式。libcurl对此都提供了支持。 libcurl默认使用被动模式,由于被动模式能够方便的穿透防火墙,NAT等问题。在被动模式下,libcurl要求ftp服务器打开一个新的端口监 听,而后libcurl链接该端口用于数据传输。若是使用主动模式,程序必须告诉FTP服务器你监听的IP与端口,经过设置 CURLOPT_FTPPORT属性来完成。
(这段文字我理解的很模糊,请读者参考原文)有些协议提供独立于正常数据的 消息头、meta-data。正常的数据流里一般不包括 信息头和元数据。能够将CURLOPT_HEADER设置为1,使信息头、元数据也能出如今数据流中。libcurl的强大之处在于,它可以从数据流中解 析出消息头,….
[ curl_easy_getinfo ]
请参考原文,此处略。
上面介绍的easy interface以同步的方式进行数据传输,curl_easy_perform会一直阻塞到数据传输完毕后返回,且一次操做只能发送一次请求,若是要同时发送多个请求,必须使用多线程。
而multi interface以一种简单的、非阻塞的方式进行传输,它容许在一个线程中,同时提交多个相同类型的请求。 在使用multi interface以前,你应该掌握easy interface的基本使用。由于multi interface是创建在easy interface基础之上的,它只是简单的将多个easy handler添加到一个multi stack,然后同时传输而已。
使用multi interface很简单,首先使用curl_multi_init()函数建立一个multi handler,而后使用curl_easy_init()建立一个或多个easy handler,并按照上面几章介绍的接口正常的设置相关的属性,而后经过curl_multi_add_handler将这些easy handler添加到multi handler,最后调用curl_multi_perform进行数据传输。
curl_multi_perform是异步的、非阻塞的函数。若是它返回CURLM_CALL_MULTI_PERFORM,表示数据通讯正在进行。
经过select()来操做multi interface将会使工做变得简单(译者注:其实每一个easy handler在低层就是一个socket,经过select()来管理这些socket,在有数据可读/可写/异常的时候,通知应用程序)。在调用 select()函数以前,应该使用curl_multi_fdset来初始化fd_set变量。
select()函数返回时,说明受管理的低层socket能够操做相应的操做(接收数据或发送数据,或者链接已经断开),此时应该立刻调用 curl_multi_perform,libcurl将会执行相应操做。使用select()时,应该设置一个较短的超时时间。在调用select() 以前,形成不要忘记经过curl_multi_fdset来初始化fd_set,由于每次操做,fd_set中的文件描述符可能都不同。
若是你想停止multi stack中某一个easy handle的数据通讯,能够调用curl_multi_remove_handle函数将其从multi stack中取出。千万另忘记释放掉easy handle(经过curl_easy_cleanup()函数)。
当multi stack中的一个eash handle完成数据传输的时候,同时运行的传输任务数量就会减小一个。当数量降到0的时候,说明全部的数据传输已经完成。
curl_multi_info_read用于获取当前已经完成的传输任务信息,它返回每个easy handle的CURLcode状态码。能够根据这个状态码来判断每一个easy handle传输是否成功。
下面的例子,演示了如何使用multi interface进行网页抓取:
int main(int argc, char **argv)
{ // 初始化
curl_global_init(CURL_GLOBAL_WIN32);
CURLM *multi_handle = NULL;
CURL *easy_handle1 = NULL;
CURL *easy_handle2 = NULL;
extern size_t save_sina_page(void *buffer, size_t size, size_t count, void *user_p);
extern size_t save_sohu_page(void *buffer, size_t size, size_t count, void *user_p);
FILE *fp_sina = fopen("sina.html", "ab+");
FILE *fp_sohu = fopen("sohu.html", "ab+");
multi_handle = curl_multi_init();
// 设置easy
handle easy_handle1 = curl_easy_init();
curl_easy_setopt(easy_handle1, CURLOPT_URL, "http://www.sina.com.cn");
curl_easy_setopt(easy_handle1, CURLOPT_WRITEFUNCTION, &save_sina_page);
curl_easy_setopt(easy_handle1, CURLOPT_WRITEDATA, fp_sina);
easy_handle2 = curl_easy_init();
curl_easy_setopt(easy_handle2, CURLOPT_URL, "http://www.sohu.com");
curl_easy_setopt(easy_handle2, CURLOPT_WRITEFUNCTION, &save_sohu_page);
curl_easy_setopt(easy_handle2, CURLOPT_WRITEDATA, fp_sohu);
// 添加到multi stack
curl_multi_add_handle(multi_handle, easy_handle1);
curl_multi_add_handle(multi_handle, easy_handle2);
//
int running_handle_count;
while (CURLM_CALL_MULTI_PERFORM == curl_multi_perform(multi_handle, &running_handle_count))
{
cout << running_handle_count << endl;
}
while (running_handle_count)
{
timeval tv; tv.tv_sec = 1;
tv.tv_usec = 0;
int max_fd;
fd_set fd_read;
fd_set fd_write;
fd_set fd_except;
FD_ZERO(&fd_read);
FD_ZERO(&fd_write);
FD_ZERO(&fd_except);
curl_multi_fdset(multi_handle, &fd_read, &fd_write, &fd_except, &max_fd);
int return_code = select(max_fd + 1, &fd_read, &fd_write, &fd_except, &tv);
if (SOCKET_ERROR == return_code)
{
cerr << "select error." << endl; break;
}
else
{
while (CURLM_CALL_MULTI_PERFORM == curl_multi_perform(multi_handle, &running_handle_count))
{
cout << running_handle_count << endl;
}
}
}
// 释放资源
fclose(fp_sina);
fclose(fp_sohu);
curl_easy_cleanup(easy_handle1);
curl_easy_cleanup(easy_handle2);
curl_multi_cleanup(multi_handle);
curl_global_cleanup(); return 0; }
size_t save_sina_page(void *buffer, size_t size, size_t count, void *user_p)
{
return fwrite(buffer, size, count, (FILE *)user_p);
}
size_t save_sohu_page(void *buffer, size_t size, size_t count, void *user_p)
{ return fwrite(buffer, size, count, (FILE *)user_p); }