Libcurl 教程
原文地址:http://curl.haxx.se/libcurl/c/libcurl-tutorial.html
译者: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 提供的功能
在运行时根据libcurl 支持的特性来进行开发,通常比编译时更好。可以通过调用curl_version_info 函数返回的结构体来获取运行时的具体信息,从而确定当前环境下libcurl 支持的一些特性。下面是笔者在visual studio2008 中调用相关函数获取libcurl 版本信息的截图:
使用easy interface
首先介绍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
);