现在的位置: 首页 > 综合 > 正文

Libcurl 教程

2013年08月04日 ⁄ 综合 ⁄ 共 8475字 ⁄ 字号 评论关闭
文章目录




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




);



       
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

无法正常工作

   

传输失败总是有原因的。你可能错误的设置了一些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;



       
}












抱歉!评论已关闭.