这个文档是小编在curl官网上使用谷歌翻译翻译的,详细信息看官网
基本描述
本文档试图描述使用 libcurl 编程时要考虑的一般原则和一些基本方法。本文将主要关注 C 接口,但可能会很好地适用于其他接口,也会讲解一些个C接口相似的接口。本文中用户指的是适应libcurl的人。程序指的是我们使用libcurl书写的代码。
构造
有许多不同的方法来构建 C 程序。本章将假设一个 Unix 风格的构建过程。如果您使用不同的构建系统,您仍然可以阅读本文以获取可能也适用于您的环境的一般信息。
编译程序
你的编译器需要知道 libcurl 头文件的位置。因此,您必须将编译器的包含路径设置为指向您安装它们的目录。'curl-config'[3] 工具可用于获取此信息:
$ curl-config --cflags
将程序与 libcurl 链接
编译程序后,您需要链接目标文件以创建单个可执行文件。为了成功,您需要与 libcurl 链接,还可能与 libcurl 本身所依赖的其他库链接。与 OpenSSL 库类似,但在命令行中甚至可能需要一些标准操作系统库。为了弄清楚要使用哪些标志,'curl-config' 工具再次发挥作用:
$ curl-config --libs
SSL 是否需要
libcurl 可以通过多种方式构建和定制。不同库和构建的不同之处之一是对基于 SSL 的传输的支持,例如 HTTPS 和 FTPS。如果在构建时正确检测到支持的 SSL 库,则 libcurl 将使用 SSL 支持构建。要确定是否已在启用 SSL 支持的情况下构建已安装的 libcurl,请像这样使用“curl-config”:
$ curl-config--功能
如果支持 SSL,关键字 'SSL' 将被写入标准输出,可能还有一些其他功能可以为不同的 libcurl 打开或关闭。
另请参阅下面的“libcurl 提供的功能”。
自动配置宏
当您编写配置脚本以检测 libcurl 并相应地设置变量时,我们提供了一个宏,它可能会完成您在该领域所需的一切。请参阅 docs/libcurl/libcurl.m4 文件 - 它包含有关如何使用它的文档。
可移植世界中的可移植代码
libcurl 背后的人们付出了相当大的努力来使 libcurl 在大量不同的操作系统和环境上工作。
在 libcurl 运行的所有平台上,您都以相同的方式对 libcurl 进行编程。只有几个小细节不同。如果您只是确保编写的代码足够可移植,那么您可以创建一个可移植的程序。libcurl 不应该阻止你这样做。
全局环境的准备
程序必须全局初始化一些 libcurl 功能。这意味着无论您打算使用该库多少次,都应该只执行一次。一次为您的程序的整个生命周期。这是使用
curl_global_init()
它需要一个参数,这是一个位模式。使用CURL_GLOBAL_ALL将使它初始化所有已知的内部子模块,并且可能是一个很好的默认选项。当前指定的两位是:
CURL_GLOBAL_WIN32
它只在 Windows 机器上做任何事情。在 Windows 机器上使用时,它将使 libcurl 初始化 win32 套接字内容。如果没有正确初始化,您的程序将无法正确使用套接字。您应该只为每个应用程序执行一次,因此如果您的程序已经执行此操作或正在使用的另一个库执行此操作,您不应该告诉 libcurl 也执行此操作。
CURL_GLOBAL_SSL
它只对编译和构建启用 SSL 的 libcurls 执行任何操作。在这些系统上,这将使 libcurl 为该应用程序正确初始化 SSL 库。这只需为每个应用程序执行一次,因此如果您的程序或其他库已经执行此操作,则不需要此位。
libcurl 有一个默认的保护机制,可以检测curl_global_init是否在curl_easy_perform被调用时没有被调用,如果是这种情况,libcurl 会以猜测的位模式运行函数本身。请注意,仅仅依赖于这一点并不被认为是好的或好的。
当程序不再使用 libcurl 时,它应该调用curl_global_cleanup,这与 init 调用相反。然后它将执行相反的操作来清理curl_global_init调用初始化的资源。
应避免重复调用curl_global_init和curl_global_cleanup 。它们应该只被调用一次。
libcurl 提供的功能
如果可以我们在运行的时候可以确定libcurl特性是最好的而不是在构建的时候。我们可以通过curl_version_info并检查返回结构的详细信息,程序可以准确地确定当前运行的 libcurl 支持什么。
两个接口
libcurl 首先引入了所谓的简单接口(easy interface)。简单界面中的所有操作都以'curl_easy'为前缀。简单的界面让您可以通过同步和阻塞函数调用进行单次传输。
libcurl 还提供了另一个接口,允许在单个线程中同时进行多个传输,即所谓的多接口(multi interface)。有关该接口的更多信息,请参阅下面的单独章节。您仍然需要先了解简单的界面,因此请继续阅读以更好地理解。
处理简单接口 libcurl
要使用简单的界面,您必须首先为自己创建一个简单的句柄。对于要执行的每个简单会话,您都需要一个句柄。在单线程的时候应该为用于传输的每个线程使用一个句柄。而绝不能在多个线程中共享同一个句柄,这样容易出一些奇奇怪怪的问题。
简单接口的使用步骤如下:
1、easyhandle = curl_easy_init();
curl_easy_init()返回一个简单的句柄。
2、在下一步:需要我们使用简单句柄设置一些属性。而句柄就是即将到来的传输或一系列传输的逻辑实体。设置属性需要用到下面这个函数curl_easy_setopt。它将控制如何进行后续转移。选项在句柄中保持设置,不会因为一次传输会话结束而改变,直到再次设置为不同的值。使用相同句柄的多个请求将使用相同的选项。当然,我们不需要一个选项一个选项的去清除设置,可以使用以下函数将所有设置全部清除curl_easy_reset。我们也可以使用一下函数将当前句柄进行复制克隆curl_easy_duphandle。克隆的句柄将保持和之前句柄一样的属性。在 libcurl 中设置的许多选项都是“字符串”,指向以零字节结尾的数据的指针。当使用curl_easy_setopt设置字符串时,libcurl 会制作自己的副本,以便在设置后不需要将它们保留在您的应用程序中[4]。
3、在句柄中设置的最基本属性之一是 URL。您将首选 URL 设置为使用CURLOPT_URL以类似于以下方式传输:
curl_easy_setopt(句柄,CURLOPT_URL,“http://domain.com/”);
4、 URL 标识了您想要在此处获取的远程资源。由于您编写了一种需要这种传输的应用程序,如果我们需要接收数据,而不是简单地将其传递给标准输出。所以,我们需要编写几个函数来接收url返回的数据。
函数原型如下:
size_t write_data(void *buffer, size_t size, size_t nmemb, void *userp);
可以通过发出类似于以下的函数来告诉 libcurl 将所有数据传递给该函数:
curl_easy_setopt(easyhandle, CURLOPT_WRITEFUNCTION, write_data);
可以通过设置另一个属性来控制回调函数在第四个参数中获取的数据:
curl_easy_setopt(easyhandle, CURLOPT_WRITEDATA, &internal_struct);
使用该属性,可以轻松地在应用程序和 libcurl 调用的函数之间传递本地数据。libcurl 本身不会触及您使用CURLOPT_WRITEDATA 传递的数据。
如果不使用CURLOPT_WRITEFUNCTION设置回调,libcurl 提供了自己的默认内部回调,它将处理数据。然后它将简单地将接收到的数据输出到标准输出。
当然我们可以设置属性CURLOPT_WRITEDATA选项 打开的用于写入的文件,让默认回调将数据写入不同的文件句柄。需要注意的是有些平台,libcurl 将无法对程序打开的文件进行操作。因此如果我们默认使用回调并使用CURLOPT_WRITEDATA传入打开的文件,它将崩溃。因此,您应该避免这种情况,以使您的程序几乎在任何地方都能正常运行。(CURLOPT_WRITEDATA以前称为CURLOPT_FILE。这两个名称仍然有效并且做同样的事情)。
如果您使用 libcurl 作为 win32 DLL,如果您设置CURLOPT_WRITEDATA,则必须使用 CURLOPT_WRITEFUNCTION -否则软件可能出现崩溃。
当然,您还可以设置更多选项,稍后我们将讨论其中的一些。让我们继续实际传输:
5、使用一下函数,发布请求 success = curl_easy_perform(easyhandle);
curl_easy_perform将连接到远程站点,执行必要的命令并接收传输。每当它接收到数据时,它就会调用我们之前设置的回调函数。该函数可能一次获取一个字节,也可能一次获取许多千字节。libcurl 尽可能多地交付。您的回调函数应该返回它“处理”的字节数。如果这与传递给它的字节数不同,libcurl 将中止操作并返回错误代码。
传输完成后,该函数会返回一个返回码,通知您它是否成功完成了任务。如果返回码对您来说不够用,您可以使用CURLOPT_ERRORBUFFER将 libcurl 指向您的缓冲区,该缓冲区也将存储人类可读的错误消息。
如果您随后要传输另一个文件,则该句柄已准备好再次使用。请注意,如果您打算进行另一次传输,甚至最好重新使用现有句柄。然后 libcurl 将尝试重新使用以前的连接。
对于某些协议,下载文件可能涉及登录、设置传输模式、更改当前目录以及最后传输文件数据的复杂过程。libcurl 会为您处理所有这些复杂问题。只需给出文件的 URL,libcurl 就会处理将文件从一台机器移动到另一台机器所需的所有细节。
多线程问题
libcurl 是线程安全的,但也有一些例外。有关更多信息,请参阅libcurl-thread。
当发生错误的时候
由于某种原因,传输总会有失败的时候。您可能设置了错误的 libcurl 选项或误解了 libcurl 选项的实际作用,或者远程服务器可能返回使库混淆的非标准回复,从而使您的程序出问题。
当发生错误的时候,有一个黄金法则:将CURLOPT_VERBOSE选项设置为 1。这将导致库吐出它发送的整个协议细节、一些内部信息和一些接收到的协议数据(尤其是在使用 FTP 时)。如果您使用的是 HTTP,则在接收到的输出中添加标头以进行研究也是一种聪明的方法,在这里可以看到协议执行的细节。在CURLOPT_HEADER设置为 1 的正常正文输出中包含标题。
当然,还有一些错误。我们需要了解它们才能修复它们,因此我们非常依赖您的错误报告。当您报告 libcurl 中的可疑错误时,请尽可能多地提供详细信息:CURLOPT_VERBOSE生成的协议转储、库版本、尽可能多的使用 libcurl 的代码、操作系统名称和版本、编译器名称和版本等等
如果CURLOPT_VERBOSE还不够,您可以使用CURLOPT_DEBUGFUNCTION提高应用程序接收的调试数据的级别。
深入了解所涉及的协议永远不会错,如果您尝试做一些有趣的事情,您可能会了解 libcurl 以及如果您至少简要地研究适当的 RFC 文档来更好地使用它。
将数据上传到远程站点
libcurl 尝试对大多数传输保持协议独立的方法,因此上传到远程 FTP 站点类似于使用 PUT 请求将数据上传到 HTTP 服务器。
当然,首先您要么创建一个简单的句柄,要么重新使用一个现有的句柄。然后像以前一样设置要操作的 URL。这是我们现在将上传的远程 URL。
由于我们编写了一个应用程序,我们很可能希望 libcurl 通过向我们询问来获取上传数据。为此,我们设置了读取回调,自定义指针 libcurl 将传递给我们的读取回调。读取回调应该有一个类似于以下的原型:
size_t function(char *bufptr, size_t size, size_t nitems, void *userp);
其中 bufptr 是指向我们用要上传的数据填充的缓冲区的指针,而 size*nitems 是缓冲区的大小,因此也是我们可以在此调用中返回给 libcurl 的最大数据量。'userp' 指针是我们设置的自定义指针,指向我们的结构,以在应用程序和回调之间传递私有数据。
curl_easy_setopt(easyhandle, CURLOPT_READFUNCTION, read_function);
curl_easy_setopt(easyhandle, CURLOPT_READDATA, &filedata);
告诉 libcurl 我们要上传:
curl_easy_setopt(easyhandle, CURLOPT_UPLOAD, 1L);
在没有事先了解预期文件大小的情况下完成上传时,一些协议将无法正常运行。因此我们需要通过此设置CURLOPT_INFILESIZE_LARGE为所有已知文件设置上传文件大小,如下所示[1]:
/* 在本例中,file_size 必须是 curl_off_t 变量 */
curl_easy_setopt(easyhandle, CURLOPT_INFILESIZE_LARGE, file_size);
当您这次调用curl_easy_perform时,它将执行所有必要的操作,当它调用上传时,它将调用您提供的回调以获取要上传的数据。该程序应在每次调用中返回尽可能多的数据,因为这可能会使上传尽可能快地执行。回调应该返回它在缓冲区中写入的字节数。返回 0 表示上传结束。
密码
许多协议使用甚至要求提供用户名和密码才能下载或上传您选择的数据。libcurl 提供了几种指定它们的方法。
大多数协议都支持您在 URL 本身中指定名称和密码。libcurl 将检测到这一点并相应地使用它们。这是这样写的:
协议://用户:密码@example.com/path/
如果您的用户名或密码中需要任何奇数字母,您应该输入它们的 URL 编码,如 %XX,其中 XX 是两位十六进制数。
libcurl 还提供了设置各种密码的选项。嵌入在 URL 中的用户名和密码可以改为使用CURLOPT_USERPWD选项设置。传递给 libcurl 的参数应该是格式为“user:password”的字符串的 char *。以这样的方式:
curl_easy_setopt(easyhandle, CURLOPT_USERPWD, "myname:thesecret");
有时可能需要名称和密码的另一种情况是那些需要对他们使用的代理进行身份验证的用户。libcurl 为此提供了另一个选项,即CURLOPT_PROXYUSERPWD。他与CURLOPT_USERPWD选项非常相似,如下所示:
curl_easy_setopt(easyhandle, CURLOPT_PROXYUSERPWD, "myname:thesecret");
长期以来,Unix“标准”方式存储 FTP 用户名和密码,即在 $HOME/.netrc 文件中(在 Windows 上,如果 %HOME% 未设置,libcurl 还会检查 %USERPROFILE% 环境变量,并尝试将 _netrc 作为姓名)。该文件应该是私有的,以便只有用户可以阅读它(另请参见“安全注意事项”一章),因为它可能包含纯文本的密码。libcurl 能够使用此文件来确定用于特定主机的用户名和密码集。作为对正常功能的扩展,libcurl 还支持该文件用于非 FTP 协议,例如 HTTP。要让 curl 使用此文件,请使用CURLOPT_NETRC选项:
curl_easy_setopt(easyhandle, CURLOPT_NETRC, 1L);
以及此类 .netrc 文件的基本示例:
machine myhost.mydomain.com
login userlogin
password secretword
所有这些例子都是密码是可选的,或者至少你可以忽略它,让 libcurl 尝试在没有它的情况下完成它的工作。有时密码是可以不用选择的,例如当您使用 SSL 私钥进行安全传输时。
将已知的私钥密码传递给 libcurl:
curl_easy_setopt(easyhandle, CURLOPT_KEYPASSWD, "keypassword");
Http认证
上一节展示了如何设置用户名和密码以获取需要身份验证的 URL。使用 HTTP 协议时,客户端可以通过多种不同方式向服务器提供这些凭据,并且您可以控制 libcurl 将(尝试)使用它们的方式。默认的 HTTP 身份验证方法称为“Basic“,它在 HTTP 请求中以明文形式发送名称和密码,采用 base64 编码。这是不安全的。
在撰写本文时,可以构建 libcurl 以使用:Basic、Digest、NTLM、Negotiate (SPNEGO)。
我们可以告诉 libcurl 与CURLOPT_HTTPAUTH一起使用哪一个,如下所示:
curl_easy_setopt(easyhandle, CURLOPT_HTTPAUTH, CURLAUTH_DIGEST);
当您向代理发送身份验证时,可以使用相同的方式设置身份验证类型,但使用CURLOPT_PROXYAUTH:
curl_easy_setopt(easyhandle, CURLOPT_PROXYAUTH, CURLAUTH_NTLM);
这两个选项都允许您设置多种类型(通过将它们组合在一起),以使 libcurl 从服务器/代理声称支持的类型中选择最安全的一种。然而,这个方法确实增加了一个返回,因为 libcurl 必须首先询问服务器它支持什么:
curl_easy_setopt(easyhandle, CURLOPT_HTTPAUTH, CURLAUTH_DIGEST|CURLAUTH_BASIC);
为方便起见,您可以使用“CURLAUTH_ANY”定义(而不是具有特定类型的列表),它允许 libcurl 使用它想要的任何方法。
当询问多种类型时,libcurl 将按照自己的内部偏好顺序选择它认为“最佳”的可用类型。
Http 发布
我们收到很多关于如何以正确方式使用 libcurl 发出 HTTP POST 的问题。因此,本章将包含使用 libcurl 支持的两种不同版本的 HTTP POST 的示例。
第一个版本是简单的 POST,这是大多数使用 <form> 标签的 HTML 页面使用的最常见的版本。我们提供指向数据的指针并告诉 libcurl 将其全部发布到远程站点:
char *data="name=daniel&project=curl";
curl_easy_setopt(easyhandle,CURLOPT_POSTFIELDS,data);
curl_easy_setopt(easyhandle, CURLOPT_URL, "http://posthere.com/");
curl_easy_perform(easyhandle); /* 发布!*/
够简单吧?由于您使用CURLOPT_POSTFIELDS设置 POST 选项,这会自动切换句柄以在即将到来的请求中使用 POST。
如果您要发布还需要设置 Content-Type: 标头的二进制数据怎么办?好吧,二进制帖子阻止 libcurl 能够对数据执行 strlen() 来计算大小,因此我们必须告诉 libcurl 帖子数据的大小。在 libcurl 请求中设置标头以通用方式完成,通过构建我们自己的标头列表,然后将该列表传递给 libcurl。
结构 curl_slist *headers=NULL;
headers = curl_slist_append(headers, "Content-Type: text/xml");
/* 发布二进制数据 */
curl_easy_setopt(easyhandle, CURLOPT_POSTFIELDS, binaryptr);
/* 设置 postfields 数据的大小 */
curl_easy_setopt(easyhandle, CURLOPT_POSTFIELDSIZE, 23L);
/* 传递我们的自定义标题列表 */
curl_easy_setopt(easyhandle, CURLOPT_HTTPHEADER, headers);
curl_easy_perform(easyhandle); /* 发布!*/
curl_slist_free_all(标题);/* 释放头列表 */
虽然上面的简单示例涵盖了需要 HTTP POST 操作的大多数情况,但它们不支持多部分表单发布。引入了多部分表单作为发布(可能很大)二进制数据的更好方式,并首次记录在RFC 1867中(在RFC 2388中更新)。它们被称为多部分,因为它们是由一系列部分构建的,每个部分都是一个数据单元。每个部分都有自己的名称和内容。实际上,您可以使用上述常规 libcurl POST 支持创建和发布多部分表单,但这需要您自己构建表单并提供给 libcurl。为了让这更容易,libcurl 提供了一个 MIME API,它包含几个函数:使用这些函数,您可以创建和填写一个多部分的表单。
1、函数curl_mime_init创建一个多部分的主体;
2、然后使用函数curl_mime_addpart将新部分附加到多部分正文中。其他部分有三种可能的数据源:使用 curl_mime_data 的内存、使用curl_mime_filedata的文件和使用curl_mime_data_cb的用户定义的数据读取回调。curl_mime_name设置部分(即:表单域)名称,而curl_mime_filename填写远程文件名。函数curl_mime_type可以知道部件的 MIME 类型,curl_mime_headers允许定义部件的标题。当不再需要多部分主体时,您可以使用curl_mime_free将其销毁。
下面的示例设置两个具有纯文本内容的简单文本部分,然后设置一个具有二进制内容的文件并上传整个内容。
curl_mime *multipart = curl_mime_init(easyhandle);
curl_mimepart *part = curl_mime_addpart(multipart);
curl_mime_name(part, "name");
curl_mime_data(part, "daniel", CURL_ZERO_TERMINATED);
part = curl_mime_addpart(multipart);
curl_mime_name(part, "project");
curl_mime_data(part, "curl", CURL_ZERO_TERMINATED);
part = curl_mime_addpart(multipart);
curl_mime_name(part, "logotype-image");
curl_mime_filedata(part, "curl.png");
/* Set the form info */
curl_easy_setopt(easyhandle, CURLOPT_MIMEPOST, multipart);
curl_easy_perform(easyhandle); /* post away! */
/* free the post data again */
curl_mime_free(multipart);
要为单个表单字段发布多个文件,您必须在单独的部分中提供每个文件,所有文件都具有相同的字段名称。尽管函数curl_mime_subparts实现了嵌套的多部分,但这种多文件发布方式已被RFC 7578第 4.3 章弃用。
要从已打开的 FILE 指针设置数据源,请使用:
curl_mime_data_cb(部分,文件大小,(curl_read_callback)fread, (curl_seek_callback) fseek, NULL, 文件指针);
libcurl 仍支持已弃用的curl_formadd函数。然而,它不应该再用于新的设计和使用它的程序应该转换为 MIME API。然而,它在这里被描述为帮助转换。
使用curl_formadd,您可以将部分添加到表单中。添加完部分后,您将发布整个表单。
上面的 MIME API 示例使用此函数表示如下:
struct curl_httppost *post=NULL;
struct curl_httppost *last=NULL;
curl_formadd(&post, &last,
CURLFORM_COPYNAME, "name",
CURLFORM_COPYCONTENTS, "daniel", CURLFORM_END);
curl_formadd(&post, &last,
CURLFORM_COPYNAME, "project",
CURLFORM_COPYCONTENTS, "curl", CURLFORM_END);
curl_formadd(&post, &last,
CURLFORM_COPYNAME, "logotype-image",
CURLFORM_FILECONTENT, "curl.png", CURLFORM_END);
/* Set the form info */
curl_easy_setopt(easyhandle, CURLOPT_HTTPPOST, post);
curl_easy_perform(easyhandle); /* post away! */
/* free the post data again */
curl_formfree(post);
多部分表单是使用 MIME 样式分隔符和标题的部分链。这意味着这些单独的部分中的每一个都有一些标题集,这些标题集描述了各个内容类型、大小等。为了使您的应用程序能够更多地手工制作此表单,libcurl 允许您为此类提供您自己的一组自定义标题。您当然可以为任意数量的部分提供标题,但是这个小示例将展示当您将标题添加到帖子句柄时如何将标题设置到一个特定部分:
struct curl_slist *headers=NULL;
headers = curl_slist_append(headers, "Content-Type: text/xml");
curl_formadd(&post, &last,
CURLFORM_COPYNAME, "logotype-image",
CURLFORM_FILECONTENT, "curl.xml",
CURLFORM_CONTENTHEADER, headers,
CURLFORM_END);
curl_easy_perform(easyhandle); /* post away! */
curl_formfree(post); /* free post */
curl_slist_free_all(headers); /* free custom header list */
由于 easyhandle 上的所有选项都是“粘性的”,因此即使您调用curl_easy_perform,它们在更改之前都保持不变,如果您打算将其作为下一个请求,您可能需要告诉 curl 返回到普通的 GET 请求。您可以使用CURLOPT_HTTPGET选项 强制一个简单的句柄返回到 GET :
curl_easy_setopt(easyhandle, CURLOPT_HTTPGET, 1L);
只需将CURLOPT_POSTFIELDS 设置为 "" 或 NULL 将*不会*阻止 libcurl 执行 POST。它只会使其 POST 没有任何数据要发送!
从弃用的表单 api 转换为 mime api
在构建多部件时必须遵守四个规则:
- 必须在构建多部件之前创建简易手柄。
- 多部分总是通过调用 curl_mime_init(easyhandle) 创建的。
- 每个部分都是通过调用 curl_mime_addpart(multipart) 创建的。
- 完成后,必须使用CURLOPT_MIMEPOST而不是CURLOPT_HTTPPOST将多部分绑定到简单句柄。
以下是对 MIME API 序列的curl_formadd调用 的一些示例:
curl_formadd(&post, &last,
CURLFORM_COPYNAME, "id",
CURLFORM_COPYCONTENTS, "daniel", CURLFORM_END);
CURLFORM_CONTENTHEADER, headers,
CURLFORM_END);
变成:
part = curl_mime_addpart(multipart);
curl_mime_name(part, "id");
curl_mime_data(part, "daniel", CURL_ZERO_TERMINATED);
curl_mime_headers(part, headers, FALSE);
将最后一个curl_mime_headers参数设置为 TRUE 会导致在销毁多部分时自动释放标题,从而节省对curl_slist_free_all的清理调用。
curl_formadd(&post, &last,
CURLFORM_PTRNAME, "logotype-image",
CURLFORM_FILECONTENT, "-",
CURLFORM_END);
becomes:
part = curl_mime_addpart(multipart);
curl_mime_name(part, "logotype-image");
curl_mime_data_cb(part, (curl_off_t) -1, fread, fseek, NULL, stdin);
curl_mime_name始终复制字段名称。curl_mime_file不支持特殊文件名“-” :要读取打开的文件,请使用 fread() 的回调源。由于数据大小未知,传输将被分块。
curl_formadd(&post, &last,
CURLFORM_COPYNAME, "datafile[]",
CURLFORM_FILE, "file1",
CURLFORM_FILE, "file2",
CURLFORM_END);
变成:
part = curl_mime_addpart(multipart);
curl_mime_name(part, "datafile[]");
curl_mime_filedata(part, "file1");
part = curl_mime_addpart(multipart);
curl_mime_name(part, "datafile[]");
curl_mime_filedata(part, "file2");
不推荐使用的多文件字段的多部分/混合实现被转换为具有相同名称的两个不同部分。
curl_easy_setopt(easyhandle, CURLOPT_READFUNCTION, myreadfunc);
curl_formadd(&post, &last,
CURLFORM_COPYNAME, "stream",
CURLFORM_STREAM, arg,
CURLFORM_CONTENTLEN, (curl_off_t) datasize,
CURLFORM_FILENAME, "archive.zip",
CURLFORM_CONTENTTYPE, "application/zip",
CURLFORM_END);
becomes:
part = curl_mime_addpart(multipart);
curl_mime_name(part, "stream");
curl_mime_data_cb(part, (curl_off_t) datasize,
myreadfunc, NULL, NULL, arg);
curl_mime_filename(part, "archive.zip");
curl_mime_type(part, "application/zip");
不使用CURLOPT_READFUNCTION回调:直接从回调读取函数中设置部分源数据来代替。
curl_formadd(&post, &last,
CURLFORM_COPYNAME, "memfile",
CURLFORM_BUFFER, "memfile.bin",
CURLFORM_BUFFERPTR, databuffer,
CURLFORM_BUFFERLENGTH, (long) sizeof databuffer,
CURLFORM_END);
becomes:
part = curl_mime_addpart(multipart);
curl_mime_name(part, "memfile");
curl_mime_data(part, databuffer, (curl_off_t) sizeof databuffer);
curl_mime_filename(part, "memfile.bin");
curl_mime_data总是复制初始数据:数据缓冲区因此可以立即重用。
curl_formadd(&post, &last,
CURLFORM_COPYNAME, "message",
CURLFORM_FILECONTENT, "msg.txt",
CURLFORM_END);
becomes:
part = curl_mime_addpart(multipart);
curl_mime_name(part, "message");
curl_mime_filedata(part, "msg.txt");
curl_mime_filename(part, NULL);
使用curl_mime_filedata将远程文件名设置为副作用:因此有必要为CURLFORM_FILECONTENT仿真清除它。
显示进度
由于历史和传统的原因,libcurl 有一个内置的进度表,可以打开它,然后让它在你的终端中显示一个进度表。
奇怪的是,通过将CURLOPT_NOPROGRESS 设置为零来打开进度表。此选项默认设置为 1。
然而,对于大多数应用程序来说,内置的进度表是无用的,而有趣的是指定进度回调的能力。然后,您传递给 libcurl 的函数指针将不定期地被调用,并提供有关当前传输的信息。
使用CURLOPT_PROGRESSFUNCTION设置进度回调。并传递一个指向与此原型匹配的函数的指针:
int progress_callback(void *clientp,
double dltotal,
double dlnow,
double ultotal,
double ulnow);
如果任何输入参数未知,则将传递 0。第一个参数“clientp”是您使用CURLOPT_PROGRESSDATA传递给 libcurl 的指针。libcurl 不会碰它。
Libcurl 与 C++
在连接 libcurl 时使用 C++ 而不是 C 时,基本上只需要记住一件事:
回调不能是非静态类成员函数
示例 C++ 代码:
A类{
静态 size_t write_data(void *ptr, size_t size, size_t nmemb,
无效*我们的指针)
{
/* 对数据做你想做的事 */
}
}
代理
根据 Merriam-Webster 的说法,“代理人”的含义是:“被授权为他人行事的人”,但也包括“代理人的代理机构、职能或办公室”。
如今,代理非常普遍。公司通常仅通过其代理向员工提供 Internet 访问。网络客户端或用户代理向代理请求文档,代理执行实际请求,然后返回它们。
libcurl 支持 SOCKS 和 HTTP 代理。当需要给定的 URL 时,libcurl 将向代理请求它,而不是尝试连接到 URL 中标识的实际主机。
如果您使用的是 SOCKS 代理,您可能会发现 libcurl 并不完全支持通过它进行的所有操作。
对于 HTTP 代理:代理是 HTTP 代理这一事实对实际发生的情况施加了某些限制。可能不是 HTTP URL 的请求 URL 仍将被传递给 HTTP 代理以传递回 libcurl。这是透明地发生的,应用程序可能不需要知道。我说“可以”,因为有时重要的是要了解通过 HTTP 代理进行的所有操作都使用 HTTP 协议。例如,您不能调用自己的自定义 FTP 命令,甚至不能调用正确的 FTP 目录列表。
代理选项
告诉 libcurl 在给定端口号上使用代理:
curl_easy_setopt(easyhandle, CURLOPT_PROXY, "proxy-host.com:8080");
一些代理在允许请求之前需要用户身份验证,并且您传递的信息类似于:
curl_easy_setopt(easyhandle,CURLOPT_PROXYUSERPWD,“用户:密码”);
如果需要,可以仅在独设CURLOPT_PROXY选项中指定主机名,并使用CURLOPT_PROXYPORT单独设置端口号。
用CURLOPT_PROXYTYPE告诉 libcurl 它是什么类型的代理(如果不是,它将默认假定一个 HTTP 代理):
curl_easy_setopt(easyhandle, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS4);
环境变量
libcurl 自动检查并使用一组环境变量来确定哪些代理用于某些协议。变量的名称遵循一个古老的事实标准,并被构建为“[protocol]_proxy”(注意小写字母)。当输入 URL 为 HTTP 时,这使得变量“http_proxy”检查要使用的代理名称。按照相同的规则,检查名为“ftp_proxy”的变量是否有 FTP URL。同理,代理始终是 HTTP 代理,变量的不同名称只是允许使用不同的 HTTP 代理。
代理环境变量内容的格式应为“[protocol://][user:password@]machine[:port]”。如果存在 protocol:// 部分,则简忽略它(因此http://proxy和 bluerk://proxy 也会这样做),并且可选的端口号指定代理在主机上运行的端口。如果未指定,将使用内部默认端口号,而这个默认的端口不一定是我们想要的。
有两个特殊的环境变量。'all_proxy' 是在未设置协议特定变量的情况下为任何 URL 设置代理的东西,并且'no_proxy' 定义了一个不应使用代理的主机列表,即使变量可能会这样说。如果 'no_proxy' 是一个(“*”),则它匹配所有主机。
要显式禁用 libcurl 对代理环境变量的检查和使用,请将代理名称设置为 " " 一个空字符串 - 使用CURLOPT_PROXY。
SSL 和代理
SSL 用于安全的点对点连接。这涉及到强大的加密和类似加密的东西,这实际上使得代理不可能作为代理任务所在的“中间人”运行。如前所述。相反,让 SSL 在 HTTP 代理上工作的唯一方法是要求代理通过隧道传输所有内容,而不能检查或摆弄流量。
因此,通过 HTTP 代理打开 SSL 连接就是要求代理直接连接到指定端口上的目标主机。这是通过 HTTP 请求 CONNECT 完成的。(“请代理先生,将我连接到那个远程主机”)。
由于此操作的性质,代理不知道通过此隧道传入和传出什么样的数据,这破坏了使用代理所带来的一些优势,例如缓存。许多组织阻止这种隧道到 443 以外的其他目标端口号(这是默认的 HTTPS 端口号)。
通过代理隧道
如上所述,SSL 需要隧道才能工作,并且通常甚至仅限于为 SSL 准备的操作;HTTPS。
然而,这并不是代理隧道唯一一次可能为您或您的应用程序带来好处。
当隧道打开从应用程序到远程机器的直接连接时,它突然也重新引入了通过 HTTP 代理执行非 HTTP 操作的能力。实际上,您可以通过这种方式使用诸如 FTP 上传或 FTP 自定义命令之类的东西。
同样,这通常被代理管理员阻止,并且很少被允许。
告诉 libcurl 像这样使用代理隧道:
curl_easy_setopt(easyhandle, CURLOPT_HTTPPROXYTUNNEL, 1L);
事实上,有时您甚至可能想使用这样的隧道进行普通的 HTTP 操作,因为它使您能够在远程服务器上进行操作,而不是要求代理这样做。libcurl 也不会阻碍这种创新行动!
代理自动配置
网络环境首先想到了这一点。它基于一个带有 JavaScript 的网页(通常使用 .pac 扩展名),当浏览器使用请求的 URL 作为输入执行时,它会向浏览器返回有关如何连接到 URL 的信息。返回的信息可能是“DIRECT”(这意味着不应使用代理)、“PROXY host:port”(告诉浏览器该特定 URL 的代理在哪里)或“SOCKS host:port”(引导浏览器到 SOCKS 代理)。
libcurl 无法解析和识别JavaScript,因此它不支持这一点。那么如果我们遇到这样的问题,我们会如何去解决,下面还有几点建议可供我们参考:
- 根据 JavaScript 的复杂性,编写一个脚本,将其翻译成另一种语言并执行。
- 阅读 JavaScript 代码并用另一种语言重写相同的逻辑。
- 实现一个 JavaScript 解释器;人们过去曾成功使用过 Mozilla JavaScript 引擎。
- 要求您的管理员停止此操作,以进行静态代理设置或类似设置。
坚持是通往幸福的道路
在执行多个请求时可以多次重复使用相同的简单句柄是。
在每个curl_easy_perform操作之后,libcurl 将保持连接处于活动状态并处于打开状态。对同一主机使用相同的简单句柄发送请求之后,后续请求可能只能使用已经打开的连接!这大大减少了网络影响。
即使连接被断开,所有涉及 SSL 的连接再次到同一主机,都将受益于 libcurl 的会话 ID 缓存,这大大减少了重新连接时间。
保持活动状态的 FTP 连接可以节省大量时间,因为跳过了命令-响应往返,而且您不会像在许多仅允许 N 人登录的 FTP 服务器上那样,在未经允许再次登录的情况下被阻止。
libcurl 缓存 DNS 名称解析结果,以更快地查找先前查找的名称。
未来可能还会添加其他有趣的细节来提高后续请求的性能。
每个简单的句柄都会尝试让最后几个连接保持活动一段时间,以防它们再次被使用。您可以使用CURLOPT_MAXCONNECTS选项设置此“缓存”的大小。默认值为 5。更改此值几乎没有任何意义,如果您考虑更改此值,通常只需重新考虑即可。
要强制您即将发出的请求不使用已经存在的连接(如果碰巧有一个连接到您要操作的同一主机,它甚至会先关闭一个),您可以通过将CURLOPT_FRESH_CONNECT 设置为 1来做到这一点。类似的情况,您还可以通过将CURLOPT_FORBID_REUSE 设置为 1 来禁止即将到来的请求“lying”并可能在请求后被重新使用。
libcurl 使用的 Http 标头
当您使用 libcurl 进行 HTTP 请求时,它会自动传递一系列标头。了解和理解这些可能对您有好处。您可以使用CURLOPT_HTTPHEADER选项替换或删除它们。
host
HTTP 1.1 甚至许多 1.0 服务器都需要此标头,并且应该是我们要与之通信的服务器的名称。这包括端口号(如果不是默认值)。
accept
“*/*”。
Expect
在执行 POST 请求时,libcurl 将此标头设置为“100-continue”,以在继续发送 post 的数据部分之前向服务器询问“OK”消息。如果 POSTed 数据量被认为是“小”,libcurl 将不会使用此标头。
自定义操作
当越来越多的协议建立在 HTTP 上进行传输,这也使得HTTP有一个不错的发展。 HTTP 是经过测试且可靠的协议,http协议被广泛部署并具有出色的代理支持。
当您使用其中一种协议时,甚至在进行其他类型的编程时,我们只需要更改传统的 HTTP(或 FTP 或...)方式。只需要更改字词、标题或各种数据。
定制请求
如果只是改变实际的 HTTP 请求关键字是我们想要的,比如当 GET、HEAD 或 POST 不满足我们的要求的时候,就可以使用CURLOPT_CUSTOMREQUEST属性。使用简单:
curl_easy_setopt(easyhandle, CURLOPT_CUSTOMREQUEST, "MYOWNREQUEST");
使用自定义请求时,您更改正在执行的实际请求的请求关键字。因此,默认情况下您发出 GET 请求,然后根据需要替换 POST 关键字。您也可以进行 POST 操作(如前所述)。
修改标题
类似 HTTP 的协议在执行请求时将一系列标头传递给服务器,我们可以自由传递我们需要的数量和标头。添加标题也很容易:
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 */
...如果您认为某些内部生成的标头,例如 Accept: 或 Host: 不包含您希望它们包含的数据,您可以通过简单地设置它们来替换它们:
headers = curl_slist_append(headers, "Accept: Agent-007");
headers = curl_slist_append(headers, "Host: munged.host.line");
删除标题
如果您用没有内容的标题替换现有标题,您将阻止发送标题。例如,如果您想完全阻止发送“Accept:”标头,您可以使用类似以下的代码禁用它:
headers = curl_slist_append(headers, "Accept:");
替换和取消内部标头都应该仔细考虑,并且您应该意识到这样做可能会违反 HTTP 协议。
强制分块传输编码
通过确保在执行非 GET HTTP 操作时请求使用自定义标头“Transfer-Encoding: chunked”,libcurl 将切换到“分块”上传,即使要上传的数据大小可能已知。默认情况下,如果上传数据大小未知,libcurl 通常会自动切换到分块上传。
HTTP 版本
所有 HTTP 请求都包含版本号,以告诉服务器我们支持哪个版本。libcurl 默认使用 HTTP 1.1。一些旧的服务器不喜欢收到 1.1 的请求,当处理像这样顽固的旧东西时,你可以告诉 libcurl 使用 1.0,而不是通过执行以下操作:
curl_easy_setopt(easyhandle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
FTP 自定义命令
并非所有协议都类似于 HTTP,因此当您想要使您的 FTP 传输行为不同时,上述内容可能对您没有帮助。
将自定义命令发送到 FTP 服务器意味着您需要完全按照 FTP 服务器的预期发送命令(RFC959 是一个很好的指南),并且您只能使用单独在控制连接上工作的命令。各种需要数据交换并因此需要数据连接的命令必须由 libcurl 自己判断。另请注意,libcurl 在进行任何传输之前会尽力将目录更改为目标目录,因此如果您更改目录(使用 CWD 或类似的),您可能会混淆 libcurl,然后它可能不会尝试在正确的远程传输文件目录。
一个在操作之前删除给定文件的小例子:
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_perform(easyhandle); /* transfer ftp data! */
curl_slist_free_all(headers); /* free the header list */
如果您希望此操作(或操作链)发生_after_ 数据传输发生,则curl_easy_setopt的选项将改为称为CURLOPT_POSTQUOTE并使用完全相同的方式。
自定义 FTP 命令将按照添加到列表中的相同顺序发送到服务器,如果命令从服务器返回错误代码,则不会发出更多命令,并且 libcurl 将使用错误代码退出( CURLE_QUOTE_ERROR )。请注意,如果您在传输之前使用CURLOPT_QUOTE发送命令,则在引用命令失败时实际上不会发生传输。
如果您将CURLOPT_HEADER 设置为 1,您将告诉 libcurl 获取有关目标文件的信息并输出有关它的“标题”。标头将采用“HTTP 样式”,看起来就像在 HTTP 中一样。启用标头或运行自定义 FTP 命令的选项与CURLOPT_NOBODY结合使用可能很有用。如果设置了此选项,则不会执行实际的文件内容传输。
FTP 自定义 CUSTOMREQUEST
如果您确实想使用您自己定义的 FTP 命令列出 FTP 目录的内容,CURLOPT_CUSTOMREQUEST将执行此操作。“NLST”是列出目录的默认选项,但您可以自由传递您对一个好的替代方案的想法。
没有巧克力片的饼干
在 HTTP 意义上,cookie 是具有关联值的名称。服务器将名称和值发送给客户端,并期望在每个后续请求中将其发送回与特定条件集匹配的服务器。条件包括域名和路径匹配并且cookie没有变得太旧。
在实际情况下,服务器会发送新的 cookie 来替换现有的 cookie 以更新它们。服务器使用 cookie 来“跟踪”用户并保持“会话”。
Cookie 从服务器发送到客户端时带有 Set-Cookie: 标头,它们从客户端发送到服务器时带有 Cookie: 标头。
要将您想要的任何 cookie 发送到服务器,您可以使用CURLOPT_COOKIE设置一个 cookie 字符串,如下所示:
curl_easy_setopt(easyhandle, CURLOPT_COOKIE, "name1=var1; name2=var2;");
在许多情况下,这还不够。您可能希望动态保存远程服务器传递给您的任何 cookie,并确保在以后的请求中相应地使用这些 cookie。
一种方法是将收到的所有标头保存在一个普通文件中,当您发出请求时,您告诉 libcurl 读取以前的标头以确定要使用的 cookie。使用CURLOPT_COOKIEFILE设置头文件以读取 cookie 。
CURLOPT_COOKIEFILE选项还自动启用 libcurl 中的cookie 解析器。在启用 cookie 解析器之前,libcurl 不会解析或理解传入的 cookie,它们将被忽略。但是,启用解析器后,cookie 将被理解,cookie 将保存在内存中,并在使用相同句柄时在后续请求中正确使用。很多时候这已经足够了,您可能根本不需要将 cookie 保存到磁盘。请注意,您指定给CURLOPT_COOKIEFILE的文件不必存在即可启用解析器,因此仅启用解析器而不读取任何 cookie 的常用方法是使用您知道不存在的文件的名称。
如果您希望使用您之前通过 Netscape 或 Mozilla 浏览器收到的现有 cookie,您可以让 libcurl 使用该 cookie 文件作为输入。CURLOPT_COOKIEFILE也用于此目的,因为 libcurl 会自动找出它是什么类型的文件并采取相应措施。
也许 libcurl 提供的最先进的 cookie 操作是将整个内部 cookie 状态保存回 Netscape/Mozilla 格式的 cookie 文件。我们称之为饼干罐。当您使用CURLOPT_COOKIEJAR设置文件名时,将创建该文件名,并在调用curl_easy_cleanup时将所有接收到的 cookie 存储在其中。这使 cookie 能够在多个句柄之间正确传递,而不会丢失任何信息。
我们需要的ftp特性
FTP 传输使用第二个 TCP/IP 连接进行数据传输。这通常是一个你可以忘记和忽略的事实,但有时这个事实会再次困扰你。libcurl 提供了几种不同的方法来定制第二个连接的建立方式。
libcurl 可以再次连接到服务器,也可以告诉服务器重新连接到它。第一个选项是默认选项,它也是最适合防火墙、NAT 或 IP 伪装设置后的所有人的选项。libcurl 然后告诉服务器打开一个新端口并等待第二个连接。默认情况下,首先尝试使用 EPSV,如果这不起作用,则尝试使用 PASV。(EPSV 是原始 FTP 规范的扩展,不存在也不适用于所有 FTP 服务器。)
您可以通过将CURLOPT_FTP_USE_EPSV 设置为零 来阻止 libcurl 首先尝试 EPSV 命令。
在某些情况下,您更愿意让服务器重新连接到您以进行第二次连接。这可能是当服务器可能位于防火墙或其他东西之后并且只允许在单个端口上进行连接。libcurl 然后通知远程服务器要连接到哪个 IP 地址和端口号。这是使用CURLOPT_FTPPORT选项完成的。如果将其设置为“-”,libcurl 将使用系统的“默认 IP 地址”。如果您想使用特定 IP,您可以设置完整的 IP 地址、解析为 IP 地址的主机名,甚至是 libcurl 将从中获取 IP 地址的本地网络接口名称。
在执行“PORT”方法时,libcurl 将在尝试 PORT 之前尝试使用 EPRT 和 LPRT,因为它们使用更多协议。您可以通过将CURLOPT_FTP_USE_EPRT 设置为零 来禁用此行为。
重新访问 smtp 和 imap 的 Mime api
除了支持 HTTP 多部分表单字段外,MIME API 还可用于构建结构化电子邮件消息并通过 SMTP 发送它们或将此类消息附加到 IMAP 目录。
结构化的电子邮件消息可能包含几个部分:一些由 MUA 内联显示,一些是附件。部分也可以构造为多部分,例如包含另一封电子邮件或提供多种文本格式替代方案。这可以嵌套到任何级别。
要构建这样的消息,您需要准备第 n 级多部分,然后使用函数curl_mime_subparts将其作为父多部分的源包含在内。一旦它被绑定到它的父 multi-part,第 n 级 multi-part 就属于它并且不应该被显式释放。
电子邮件消息数据不应该是非 ascii 并且行长是有限的:幸运的是,标准定义了一些传输编码以支持这种不兼容数据的传输。函数curl_mime_encoder告诉部件其源数据必须在发送之前进行编码。它还为该部分生成相应的标题。如果您要发送的部分数据已经在这样的方案中编码,请不要使用此函数(这会过度编码它),而是显式设置相应的部分标头。
在发送这样的消息时,libcurl 会在其前面加上使用CURLOPT_HTTPHEADER设置的标头列表,作为第 0 级 mime 部分标头。
下面是一个使用内联纯文本/html 替代文本和以 base64 编码的文件附件构建电子邮件的示例:
curl_mime *message = curl_mime_init(easyhandle);
/* The inline part is an alternative proposing the html and the text
versions of the email. */
curl_mime *alt = curl_mime_init(easyhandle);
/* HTML message. */
curl_mimepart *part = curl_mime_addpart(alt);
curl_mime_data(part, "<html><body><p>This is HTML</p></body></html>",
CURL_ZERO_TERMINATED);
curl_mime_type(part, "text/html");
/* Text message. */
part = curl_mime_addpart(alt);
curl_mime_data(part, "This is plain text message",
CURL_ZERO_TERMINATED);
/* Create the inline part. */
part = curl_mime_addpart(message);
curl_mime_subparts(part, alt);
curl_mime_type(part, "multipart/alternative");
struct curl_slist *headers = curl_slist_append(NULL,
"Content-Disposition: inline");
curl_mime_headers(part, headers, TRUE);
/* Add the attachment. */
part = curl_mime_addpart(message);
curl_mime_filedata(part, "manual.pdf");
curl_mime_encoder(part, "base64");
/* Build the mail headers. */
headers = curl_slist_append(NULL, "From: me@example.com");
headers = curl_slist_append(headers, "To: you@example.com");
/* Set these into the easy handle. */
curl_easy_setopt(easyhandle, CURLOPT_HTTPHEADER, headers);
curl_easy_setopt(easyhandle, CURLOPT_MIMEPOST, mime);
应该注意的是,将消息附加到 IMAP 目录需要在上传之前知道消息大小。因此,在这种情况下,不可能包含数据大小未知的部分。
标题同样有趣
一些协议提供“标头”,即与正常数据分离的元数据。这些标头默认情况下不包含在普通数据流中,但您可以通过将CURLOPT_HEADER 设置为 1 使它们出现在数据流中。
可能更有用的是 libcurl 能够将标头与数据分开,从而使回调有所不同。例如,您可以通过设置CURLOPT_HEADERDATA设置不同的指针以传递给普通的写回调。
或者,您可以使用CURLOPT_HEADERFUNCTION设置一个完全独立的函数来接收标头。
标头被一一传递给回调函数,您可以依赖这一事实。它使您可以更轻松地添加自定义标头解析器等。
FTP 传输的“标头”等于所有 FTP 服务器响应。它们实际上不是真正的标题,但在这种情况下,我们假装它们是!;-)
转会后信息
多接口
本文档中详细描述的简单接口是一个同步接口,一次传输一个文件,直到完成后才返回。
另一方面,多接口允许您的程序同时在两个方向上传输多个文件,而不会强迫您使用多个线程。这个名字可能会让人觉得多接口是用于多线程程序的,但事实几乎恰恰相反。多接口允许单线程应用程序执行多线程程序可以执行的相同类型的多个同时传输。它允许多线程传输的许多好处,而无需管理和同步许多线程的复杂性。
更复杂的是,甚至还有两个版本的多接口。基于事件的一个,也称为 multi_socket 和为与 select() 一起使用而设计的“普通”。有关基于 multi_socket 事件的 API 的详细信息,请参阅 libcurl-multi.3 手册页,这里的描述是面向 select() 的。
要使用此界面,最好先了解如何使用简易界面的基础知识。多接口只是一种通过将多个简单句柄添加到“多堆栈”中来同时进行多个传输的方法。
您创建所需的简单句柄,每个并发传输一个,并设置所有选项,就像您在上面学到的一样,然后使用 curl_multi_init 创建一个多句柄,并使用curl_multi_add_handle将所有这些简单句柄添加到该多句柄中。
当您添加了当前的句柄后(您仍然可以随时添加新的句柄),您可以通过调用curl_multi_perform开始传输。
curl_multi_perform是异步的。它只会执行现在可以完成的事情,然后将控制权返回给您的程序。它被设计为永不阻塞。您需要继续调用该函数,直到所有传输完成。
此接口的最佳用法是当您对所有可能的文件描述符或套接字执行 select() 以了解何时再次调用 libcurl 时。这也使您可以轻松地等待和响应您自己应用程序的套接字/句柄上的操作。您可以使用curl_multi_fdset找出 select() 的用途,它使用 libcurl 目前使用的特定文件描述符为您填充一组 fd_set 变量。
然后当您调用 select() 时,它将在文件之一处理信号操作时返回,然后您调用curl_multi_perform以允许 libcurl 执行它想要执行的操作。请注意,libcurl 还具有一些超时代码,因此我们建议您在再次调用curl_multi_perform之前不要在 select() 上使用长时间超时。提供curl_multi_timeout是为了帮助您获得合适的超时时间。
您应该使用的另一个预防措施:始终在调用 select() 之前立即调用curl_multi_fdset,因为当前的文件描述符集可能会在任何 curl 函数调用中发生变化。
如果要停止堆栈中的一个简单句柄的传输,可以使用curl_multi_remove_handle删除单个简单句柄。请记住,简单的句柄应该是curl_easy_cleanup编辑的。
当多堆栈中的传输完成时,正在运行的传输计数器(由curl_multi_perform 填写)将减少。当数字达到零时,所有传输都已完成。
curl_multi_info_read可用于获取有关已完成传输的信息。然后,它会返回每次轻松传输的 CURLcode,以便您确定每次传输是否成功。
SSL、证书和其他技巧
[播种、密码、密钥、证书、ENGINE、ca 证书]
在简单句柄之间共享数据
使用easy界面时可以在easy手柄之间共享一些数据,使用multi界面时会自动共享一些数据。
当您将简易句柄添加到多句柄时,这些简易句柄将自动共享大量数据,否则当使用简易界面时,这些数据将保留在每个简易句柄的基础上。
DNS缓存在多句柄内的句柄之间共享,使后续名称解析更快,并且为更好地允许持久连接和连接重用而保留的连接池也被共享。如果您使用的是简单接口,您仍然可以使用共享接口在特定的简单句柄之间共享这些,请参阅libcurl-share。
有些东西永远不会自动共享,而不是在多个句柄中,例如 cookie,因此共享的唯一方法是使用共享界面。
脚注
[1]
在使用未知大小的数据完成 HTTP 上传的情况下,libcurl 7.10.3 及更高版本能够切换到分块传输编码。
[2]
当 libcurl 被构建并用作 DLL 时,这会在 Windows 机器上发生。但是,如果您与静态库链接,您仍然可以在 Windows 上执行此操作。
[3]
curl-config 工具是在构建时生成的(在类 Unix 系统上),应该使用“make install”或类似的指令安装库、头文件、手册页等。
[4]
此行为在 7.17.0 之前的版本中有所不同,其中字符串必须在curl_easy_setopt调用结束后保持有效。