使用 cURL 和 libcurl 通过 Internet 进行对话

来源:互联网 发布:java识别图片验证码 编辑:程序博客网 时间:2024/05/04 23:49

使用 cURL 和 libcurl 通过 Internet 进行对话

将 libcurl 与 C 以及 Python 结合使用



Tim M. Jones, 顾问工程师,  

2009 年 10 月 29 日

cURL 是一个命令行工具,可以对文件传输使用许多协议,包括 HTTP、FTP、Secure Copy (SCP)、Telnet 等等。但是,除了可以用命令行通过 Internet 与端点对话外,还可以使用 libcurl 编写简单或复杂的程序,以自动化执行应用层的协议任务。本文将介绍 cURL 命令行工具,然后向您展示如何使用 libcurl 以及 C 和 Python 构建一个 HTTP 客户端。

开发 HTTP 和 FTP 之类依赖于应用层协议的应用程序并不复杂,但也不简单。进一步讲,这不是应用程序的重点,因为大部分情况下,协议之上的内容才是真正重要的内容。因此,libcurl 引起了许多人的兴趣,因为它的重点是应用程序而不是开发的各个方面。注意,很少有应用程序开发自己的 TCP/IP 堆栈,所以老话重提:尽可能重用以最小化开发安排并提高应用程序的可靠性。

本文首先简单介绍应用层协议,然后介绍 cURL、libcurl 并解释它们的用法。

Web 协议

如今构建应用程序已与过去大不相同。现在的应用程序需要能够通过网络或 Internet 进行通讯(提供人类可用的网络 API 或接口),还要能支持用户脚本化以提高灵活性。现代应用程序通常使用 HTTP 公开 Web 接口,并通过 Simple Mail Transport Protocol (SMTP) 提供警告通知。这些协议允许您将 Web 浏览器指向设备以获得配置或状态信息,并从设备或常用的电子邮件客户端接收标准电子邮件(分别通过 HTTP 和 SMTP)。

这些 Web 服务通常构建在网络堆栈的套接字层上(见图 1)。套接字层实现一个最先出现在 Berkeley Software Distribution (BSD) 操作系统上的 API,并提取底层传输和网络层协议的详细信息。


图 1. 网络堆栈和 libcurl
图 1. 网络堆栈和 libcurl 

Web 服务发生在客户端和服务器之间的协议对话中。在 HTTP 上下文中,服务器是终端设备,客户端是位于端点上的浏览器。对于 SMTP,服务器是邮件网关或端点用户,客户端是终端设备。在某些情况下,协议对话发生在两个步骤(请求和响应)中,但另一些情况下,需要协商和通讯的通信量更多。这种协商可能增加了大量复杂性,这可以通过 API 进行抽象,比如 libcurl。





回页首

cURL 简介

cURL 的起源与发展
cURL 是 Daniel Stenberg 发明的,但是 600 多名开发人员也做出了巨大的贡献。它无疑是许多应用程序都使用的有用技术之一。

cURL 最初的设计初衷是使用不同的协议(比如 FTP、HTTP、SCP 等)在端点之间移动文件。它最初是一个命令行实用工具,但现在也是一个绑定了 30 多种语言的库。因此,现在不仅可以通过 shell 使用 cURL,您还可以构建合并了这个重要功能的应用程序。libcurl 库也是可以移植的,支持 Linux®、IBM®AIX®操作系统、BSD、Solaris 以及许多其他 UNIX®变体。





回页首

获取和安装 cURL/libcurl

获取和安装 libcurl 非常简单,取决于您所运行的 Linux 发行版。如果运行的是 Ubuntu,您可以使用 apt-get轻松安装这些包。以下行演示了如何为 libcurl 安装 libcurl 和 Python 绑定:

 $ sudo apt-get install libcurl3 $ sudo apt-get install python-pycurl

apt-get实用工具确保该过程满足所有的依赖关系。





回页首

在命令行中使用 cURL

cURL 最开始是一个命令行工具,可以使用 Uniform Resource Locator (URL) 语法执行数据传输。考虑到它在命令行上的流行度,后来创建了一个可以在应用程序中生成这些行为的库。如今,命令行 cURL 是 cURL 库的包装器。本文首先研究 cURL 作为命令行的功能,然后深入探讨如何将它作为库使用。

cURL 的两种常见用法是使用 HTTP 和 FTP 协议进行文件传输。cURL 为这些协议提供一个简单的接口。要使用 HTTP 从网站获取文件,只需告诉 cURL 您要将网页写入到其中的本地文件的文件名、网站的 URL 以及要获取的文件。让我们看一下清单 1 中的简单命令行示例。


清单 1. 使用 cURL 从网站获取文件的示例
 $ curl -o test html www.exampledomain.com  % Total    % Received % Xferd  Average Speed    Time    Time     Time    Current                                  Dload  Upload    Total   Spent    Left    Speed  100 43320  100 43320    0     0  55831       0 --:--:-- --:--:-- --:--:--  89299  $ 

注意,由于我指定了域而不是文件,我将获得根文件(index.html)。要使用 cURL 将该文件移动到 FTP 站点,可以使用 -T选项指定要上传的文件,然后提供 FTP 站点的 URL 以及文件的路径。


清单 2. 使用 cURL 将文件上传到 FTP 站点的示例
 $ curl -T test.html ftp://user:password@ftp.exampledomain.com/ftpdir/  % Total    % Received % Xferd  Average Speed    Time    Time     Time    Current                                  Dload  Upload    Total   Spent    Left    Speed  100 43320    0     0  100 43320      0  38946   0:00:01 0:00:01  --:--:--    124k  $ 

是不是很简单?学习了一些模式之后您会发现,cURL 使用起来非常简单。但是您可以使用的选项非常多 —在 cURL 命令行中请求帮助(使用 --help)可以得到 129 行选项。如果您觉得这还不算太多,那么还有一大批其他控制选项(从详细度到安全性),以及特定于协议的配置项。

从开发人员的角度看,这还不算是 cURL 最令人兴奋的地方。让我们深入了解 cURL 库,学习如何向应用程序添加文件传输协议。





回页首

作为库的 cURL

如果您有 10 年以上的脚本语言经验,您就会注意到它们的标记有很大的变化。Python、Ruby、Perl 等这些脚本语言不仅包含套接字层(C 或 C++ 中也有),还包含了应用层协议 API。这些脚本语言合并了高级功能,可以创建 HTTP 服务器或客户端。libcurl 库为 C 和 C++ 之类的语言添加了类似的功能,但是它可以在不同的语言之间移植。在所有它支持的语言中都能找到与 libcurl 相当的行为,但是由于这些语言的差异很大(设想一下 C 和 Scheme),提供这些行为的方式也很不相同。

libcurl 库以 API 的形式封装清单 1和清单 2中描述的行为,因此它可以被高级语言使用(如今已超过 30 种)。本文提供了 libcurl 的两个示例。第一个示例研究使用 c 构建的简单 HTTP 客户端(适合构建 Web 爬行器),第二个示例是一个使用 Python 创建的简单 HTTP 客户端。





回页首

基于 C 的 HTTP 客户端

C API 在 libcurl 功能上提供了两个 API。easy 接口是一个简单的同步 API(意味着当您使用请求调用 libcurl 时,将能够满足您的请求,直到完成或发生错误)。多接口可以进一步控制 libcurl,您的应用程序可以执行多个同步传输,并控制 libcurl 何时何地移动数据。

该示例使用 easy 接口。该 API 还能控制数据移动过程(使用回调),但正如其名称所示,使用起来非常简单。清单 3 提供了 HTTP 的 C 语言示例。


清单 3. 使用 libcurl easy 接口的 C HTTP 客户端
 #include <stdio.h>  #include <string.h>  #include <curl/curl.h>  #define MAX_BUF  65536  char wr_buf[MAX_BUF+1];  int  wr_index;  /*  * Write data callback function (called within the context of  * curl_easy_perform.  */  size_t write_data( void *buffer, size_t size, size_t nmemb, void *userp )  {   int segsize = size * nmemb;   /* Check to see if this data exceeds the size of our buffer. If so,    * set the user-defined context value and return 0 to indicate a    * problem to curl.    */   if ( wr_index + segsize > MAX_BUF ) {     *(int *)userp = 1;     return 0;   }   /* Copy the data from the curl buffer into our buffer */   memcpy( (void *)&wr_buf[wr_index], buffer, (size_t)segsize );   /* Update the write index */   wr_index += segsize;   /* Null terminate the buffer */   wr_buf[wr_index] = 0;   /* Return the number of bytes received, indicating to curl that all is okay */   return segsize;  }  /*  * Simple curl application to read the index.html file from a Web site.  */  int main( void )  {   CURL *curl;   CURLcode ret;   int  wr_error;   wr_error = 0;   wr_index = 0;   /* First step, init curl */   curl = curl_easy_init();   if (!curl) {     printf("couldn't init curl/n");     return 0;   }   /* Tell curl the URL of the file we're going to retrieve */   curl_easy_setopt( curl, CURLOPT_URL, "www.exampledomain.com" );   /* Tell curl that we'll receive data to the function write_data, and    * also provide it with a context pointer for our error return.    */   curl_easy_setopt( curl, CURLOPT_WRITEDATA, (void *)&wr_error );   curl_easy_setopt( curl, CURLOPT_WRITEFUNCTION, write_data );   /* Allow curl to perform the action */   ret = curl_easy_perform( curl );   printf( "ret = %d (write_error = %d)/n", ret, wr_error );   /* Emit the page if curl indicates that no errors occurred */   if ( ret == 0 ) printf( "%s/n", wr_buf );   curl_easy_cleanup( curl );   return 0;  } 

最上方是必需的 include文件,包括 cURL 根文件。接下来,我定义了两个用于传输的变量。第一个变量是 wr_buf,表示将在其中写入传入数据的缓冲区。wr_index表示缓冲区的当前写入索引。

转到 main函数,该函数使用 easy API 进行设置。所有 cURL 调用都通过维护特定请求状态的句柄进行操作。这称为 CURL指针引用。本例还创建一个特殊的返回码,称为 CURLcode。在使用任何 libcurl 函数之前,您需要调用 curl_easy_init获取 CURL句柄。接下来,注意 curl_easy_setopt调用的数量。它们为特定的操作配置句柄。对于这些调用,您提供句柄、命令和选项。首先,本例使用 CURLOPT_URL指定要获取的 URL。然后,它使用 CURL_WRITEDATA提供一个上下文变量(在本例中,它是内部的 write 错误变量)。最后,它使用 CURLOPT_WRITEFUNCTION指定数据可用时应该调用的函数。在启动 API 之后,API 将使用它读取的数据多次调用该函数。

要开始传输,调用 curl_easy_perform。它的工作是根据之前的配置执行传输。调用该函数时,在完成传输或发生错误之前该函数不会返回。main的最后一步是提交返回状态,提交页面读取,最后使用 curl_easy_cleanup清除(当使用句柄执行完操作后)。

现在看看 write_data函数。该函数是针对特定操作收到数据时调用的回调。注意,当您从网站读取数据时,将写入该数据(write_data)。将向回调提供一个缓冲区(包含可用数据)、成员数量和大小(缓冲中可用数据总量)、上下文指针。第一个任务是确保缓冲区(wr_buf)的空间足以写入数据。如果不够,它将设置上下文指针并返回 0,表示出现问题。否则,它将 cURL 缓冲区的数据复制到您的缓冲区,并增加索引,指向要写入的下一个位置。本例还终止字符串,稍后可以对其使用 printf。最后,它返回 libcurl 操作的字节数量。这将告诉 libcurl 数据被提取,它也可以丢弃该数据。这就是从网站将文件读取到内存的相对简单的方法。





回页首

基于 Python 的 HTTP 客户端

本节提供的示例类似于基于 C 的 HTTP 客户端,不过它使用的是 Python。Python 是一种非常有用的面向对象的脚本语言,在原型化和构建生产软件方面非常突出。示例假设您较熟悉 Python,但使用不多,因此不要期望过高。

这个简单的 Python HTTP 客户端使用 pycurl,如清单 4 所示。


清单 4. 使用 libcurl 的 pycurl接口的 Python HTTP 客户端
 import sys  import pycurl  wr_buf = '' def write_data( buf ):  global wr_buf  wr_buf += buf  def main():  c = pycurl.Curl()  c.setopt( pycurl.URL, 'http://www.exampledomain.com' )  c.setopt( pycurl.WRITEFUNCTION, write_data )  c.perform()  c.close()  main()  sys.stdout.write(wr_buf) 

使用 Python 进行原型化

原型化是 Python 的优势之一。它只需很少的代码就可以实现大量功能。使用 C 也许能获得更好的性能,但是如果您的目的是快速编码以证明某个概念,那么高级脚本语言是无可替代的,比如 Python。

这比 C 语言版本简单的多。它首先导入必需的模块(用于标准系统的 sys和 pycurl模块)。接下来,它定义 write 缓冲区(wr_buf)。像 C 程序中一样,我声明一个 write_data函数。注意,该函数只有一个参数:从 HTTP 服务器中读取的数据缓冲区。我将该缓冲区连接到全局 write 缓冲区。main函数首先创建一个 Curl句柄,然后使用setopt方法为传输定义 URL和 WRITEFUNCTION。它调用 perform方法启动传输并关闭句柄。最后,它调用 main函数,并将 write 缓冲区提交到 stdout。注意,在这种情况下,您不需要错误上下文指针,因为您使用了 Python 字符串连接,这就是说您不会使用大小固定的字符串。





回页首

结束语

本文仅仅简单介绍了 libcurl,介绍了它支持的多种协议和语言。希望这能够展示它如何轻松构建使用应用层协议(如 HTTP)的应用程序。libcurl 网站(见 参考资料)提供了很多示例和有用的文档。下一次开发 Web 浏览器、爬行器或其他有应用层协议要求的应用程序时,试试 libcurl。它一定能大大减少您的开发时间,并找回编码的乐趣。



参考资料

学习
  • cURL是一个命令行工具和库,实现了各种客户端协议。它支持 12 种以上的协议,包括 FTP、HTTP、Telnet 以及其他安全变体。许多平台上都能找到 cURL,包括 Linux、AIX、BSD 和 Solaris,它支持 30 多种语言。
  • PycURL是 libcurl API 之上的一个薄层,PycURL 速度非常快。使用 PycURL,您可以使用 libcurl 库开发 Python 应用程序。
  • 说到应用程序灵活性,您可以在 “用 Guile 编写脚本” 中了解更多有关将脚本功能集成到应用程序的内容。
  • 要收听有关软件开发人员的有趣采访和讨论,请查看 developerWorks 网络广播。
  • 了解最新的 developerWorks 技术活动 和 网络广播。
  • 在 Twitter 上跟随 developerWorks。
  • 查阅最近将在全球举办的面向 IBM 开放源码开发人员的研讨会、交易展览、网络广播和其他 活动。
  • 访问 developerWorks 开源专区获得丰富的 how-to 信息、工具和项目更新,帮助您用开放源码技术进行开发,并与 IBM 产品结合使用。
  • 查看免费的 developerWorks On demand 演示,观看并了解 IBM 及开源技术和产品功能。

获得产品和技术
  • 使用 IBM 试用软件改进您的下一个开发项目,这些软件可以通过下载中心获得。
  • 下载 IBM 产品评估版 或 在 IBM SOA Sandbox 中研究在线试用版,开始使用来自 DB2®、Lotus®、Rational®、Tivoli®和 WebSphere®的应用程序开发工具和中间件产品。

讨论
  • 加入 developerWorks 博客和 developerWorks 社区

原创粉丝点击