目标


本文档试图描述使用libcurl编程时要考虑的一般原则和一些基本方法。该文本将主要关注C接口,也能够在其他类型的接口上得到应用,它们通常非常接近于C接口。

本文档将“用户(the user)”称为编写使用libcurl的源代码的人。通常将“程序(the program)”是用户编写的使用libcurl进行传输的收集源代码,该程序在libcurl之外。

要获得有关此处所述的选项和功能的更多详细信息,请参阅其详细手册页。

构建


构建C程序有许多不同的方法。 本章将假设一个Unix风格的构建过程。 如果您使用其他构建系统,您仍然可以阅读此内容以获取可能适用于您的环境的一般信息。

编译程序

您的编译器需要知道如何链接到libcurl。 因此,必须将编译器的include路径设置为指向安装它们的目录。 'curl-config'[3]工具可用于获取此信息:

$ curl-config --cflags

将程序与libcurl链接

编译程序时,需要链接目标文件以创建单个可执行文件。 为了成功,您需要链接libcurl,也可能与libcurl本身依赖的其他库链接。与OpenSSL库一样,但即使命令行上也可能需要一些标准的OS库。 为了弄清楚要使用哪些文件,可再次使用'curl-config'工具:

$ curl-config --libs

SSL

libcurl可以通过多种方式构建和定制。 不同库和构建的不同之处在于是否需要支持基于SSL的传输,如HTTPS和FTPS。 如果在构建时检测到需要支持SSL的库,则将使用SSL支持构建libcurl。需要确定是否已在启用SSL支持的情况下构建已安装的libcurl,请使用'curl-config',如下所示:

$ curl-config --feature

如果支持SSL,则关键字“SSL”将写入stdout,可能还有一些其他功能可以打开或关闭以用于不同的libcurl。

另请参阅"Features libcurl Provides"。

autoconf宏

当您编写配置脚本以相应地检测libcurl和相应的设置变量时,我们提供了一个预先编写的宏,可能会在此时执行您需要的所有操作。 请参阅docs / libcurl / libcurl.m4文件 - 它包含有关如何使用它的文档。

可移植性


 

libcurl背后的人们付出了相当大的努力,使libcurl能够在大量不同的操作系统和环境中运行。

您可以在可运行libcurl的所有平台上以相同的方式编写libcurl。 只有很少的次要考虑因素需要考虑。 如果你只是确保编写足够的可移植代码,你可以很好地创建一个非常便携的程序。

全局方法


该程序必须初始化一些libcurl功能。这意味着无论您打算使用多少次库,都应该“一次”完成。“一次”为您的程序的整个生命周期。使用以下方法:

 curl_global_init()

它需要一个参数,它是一个位模式(bit pattern),告诉libcurl初始化什么。使用CURL_GLOBAL_ALL将使其初始化所有已知的内部子模块,并且可能是一个很好的默认选项。指定的当前两位是:

CURL_GLOBAL_WIN32

只能在windows上执行。当在Windows上使用时,它将使libcurl初始化win32 套接字的相关内容。如果没有正确初始化,您的程序将无法正确使用相关套接字。对于每个应用程序,只执行一次,因此如果您的程序已经执行此操作或使用其他库,则不应该执行此操作。

CURL_GLOBAL_SSL

只能在libcurls上编译和构建SSL的任何内容。在这些系统上,这将使libcurl为此应用程序正确初始化SSL库。这只需要为每个应用程序执行一次,因此如果您的程序或其他库已经执行此操作,则不需要执行此操作。

libcurl有一个默认的保护机制,可以检测 curl_easy_perform 被调用时是否被正常初始化。如果为否,libcurl会使用被猜测的位模式(bit pattern)运行函数本身。 请注意,这并未被考虑的很完善也不能很好的执行。

当程序不再使用libcurl时,它应该调用 curl_global_cleanup,这与init调用相反。 然后它将执行反向操作以清理curl_global_init所初始化的资源。

应避免重复调用curl_global_init和curl_global_cleanup。 在程序的生命周期中只会被调用一次。

当前可执行操作


如果可能的话,在运行时而不是在构建时确定libcurl功能是做好的方式。通过调用 curl_version_info 并检查返回结构的内容,可以确定当前运行的libcurl能支持的内容。

单文件上传与多文件上传


libcurl首先介绍了简易接口(easy interface)。简易接口中的所有操作都以'curl_easy'为前缀。 简易接口允许您使用同步和阻塞函数进行单次传输。

libcurl还提供了另一个接口,允许在单个线程中进行多个同时传输,即所谓的多接口(multi interface)。有关该接口的更多信息将在下一章单独详细介绍。 您仍然需要先了解简易接口,所以请继续阅读以便更好地理解相关内容。

简易接口


要使用简易接口,您必须首先创建一个句柄。 对于要执行的每个相关会话,您需要一个句柄。 基本上,您应该为计划用于传输的每个线程使用一个句柄, 您绝不能在多个线程中共享相同的句柄。

使用如下方法:

easyhandle = curl_easy_init();

它返回一个简单句柄。 使用它,您可以继续执行下一步:设置首选操作。 句柄是即将进行的传输或一系列传输的逻辑实体。

您可以使用 curl_easy_setopt 为此句柄设置属性和选项,控制后续传输或传输的方式。 选择的结果仍保留在句柄中,直到再次设置为不同的值。使用相同句柄的多个请求将使用相同的选项。

如果您在任何时候想要重置句柄选项,您可以调用 curl_easy_reset ,您还可以使用 curl_easy_duphandle 复制一个句柄(包含所有设置选项)。

您在libcurl中设置的许多选项都是“字符串”,是一个零字节大小的指针。 当您使用 curl_easy_setopt 设置,libcurl会创建选项字符串的副本,以便在设置后不需要在应用程序中保留它们[4]。

在句柄中设置的最基本属性之一是URL。 您可以使用以下的方式设置首选URL:

curl_easy_setopt(handle,CURLOPT_URL,“http://domain.com/”);

让我们假设,URL标识了您想要获取的远程资源,编写了一种可用于此传输的应用程序,并希望直接将数据传递给您,而不是简单地将其传递给stdout。 所以,你编写自己的函数来匹配这个原型:

        size_t write_data(void * buffer,size_t size,size_t nmemb,void * userp);

可以将此函数以参数传入以接收数据:

  curl_easy_setopt(easyhandle,CURLOPT_WRITEFUNCTION,write_data);

也可以通过设置相关参数以直接获得的数据:

 curl_easy_setopt(easyhandle,CURLOPT_WRITEDATA,&internal_struct);

使用该属性,您可以轻松地传递数据。 libcurl本身不会触及您使用CURLOPT_WRITEDATA传递的数据。

libcurl提供了默认回调方法,如果不使用 CURLOPT_WRITEFUNCTION 设置回调,它将处理数据。将接收的数据输出到stdout,您可以使用默认回调将数据写入不同的文件句柄,方法是将“FILE *”传递给使用CURLOPT_WRITEDATA选项打开的文件。

现在,在某些平台上[2],libcurl将无法对程序打开的文件进行操作。因此,如果您使用默认回调并使用CURLOPT_WRITEDATA传入打开的文件,它将崩溃。(CURLOPT_WRITEDATA以前称为 CURLOPT_FILE 。两个名称仍然有效并且做同样的事情)

如果您使用libcurl 作为 win32 DLL,如果设置CURLOPT_WRITEDATA,则必须使用CURLOPT_WRITEFUNCTION - 否则您的程序会崩溃。

其他:

 success = curl_easy_perform(easyhandle);

curl_easy_perform 将连接到远程站点,执行必要的命令并接收数据。每当它接收数据时,它就会调用我们之前设置的回调函数。该函数可以一次获得一个字节,也可以一次获得多个千字节。你的回调函数应该返回它获取的字节数。如果和他传递的字节数不同,libcurl将中止操作并返回错误代码。

传输完成后,该函数返回一个返回代码,通知您是否成功执行了任务。如果返回代码不够,您可以使用 CURLOPT_ERRORBUFFER 将libcurl指向您的缓冲区,它也会存储可读的错误消息。

如果您想要传输另一个文件,则可以再次使用该句柄。请注意,如果您打算进行另一次传输,您甚至可以重新使用现有的手柄。然后libcurl将尝试重用以前的连接。

对于某些协议,下载文件可能涉及复杂的登录过程,设置传输模式,更改当前目录以及最后传输文件数据。 libcurl为您解决所有复杂问题。仅仅提供文件的URL,libcurl将处理所需的所有细节。

 

多线程问题


libcurl是线程安全的,但也有一些例外。 有关更多信息,请参阅libcurl-thread

传输失败


由于某种原因,传输总会失败。您可能设置了错误的选项或误解了选项实际执行的操作,或者远程服务器可能返回混淆库的非标准回复,这会使您的程序混乱。

当这些事情发生时,可执行以下操作:

将 CURLOPT_VERBOSE 选项设置为1.它将导致库发出它发送的整个协议详细信息,一些内部信息和一些接收到的协议数据(特别是在使用FTP时)。如果您正在使用HTTP,那么将接收到的输出中的标题添加到研究中也是一种聪明的方法,可以更好地理解服务器的行为方式。使用CURLOPT_HEADER设置1在普通正文输出中包含标题。

当然,还有漏洞。我们需要了解它们才能修复它们,因此我们完全依赖于您的错误报告!当您报告libcurl中的可疑错误时,请尽可能多地包含详细信息:CURLOPT_VERBOSE生成的协议转储,库版本,尽可能使用libcurl的代码,操作系统名称和版本,编译器名称和版本等等

如果 CURLOPT_VERBOSE 不够,则使用 CURLOPT_DEBUGFUNCTION 增加应用程序所接收的调试数据的级别。

深入了解所涉及的协议是不会错的,如果你想尝试做有趣的事情,至少简单地研究适当的RFC文档,你可能会很好地理解 libcurl ,如何更好地使用它。

上传


libcurl尝试传输方法独立于传输协议,因此上传到远程FTP站点非常类似于使用PUT请求将数据上载到HTTP服务器。

当然,首先要么创建一个简单的句柄,要么重新使用一个现有的句柄。设置一个远程上传URL。

由于我们编写了一个应用程序,我们希望libcurl通过询问我们来获取上传数据。为了做到这一点,我们设置了read回调,自定义指针libcurl将传递给我们的read回调。读回调应该有一个类似于以下函数的原型:

 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将检测到这一点并相应地使用它们。这是这样写的:

 protocol://user:password@example.com/path/

如果您的用户名或密码中需要任何奇怪的字母,则应输入URL编码,如%XX,其中XX是两位十六进制数字。

libcurl还提供了设置各种密码的选项。嵌入在URL中的用户名和密码可以使用CURLOPT_USERPWD选项进行设置。传递给libcurl的参数应该是一个char *字符串,格式为“user:password”。以这种方式:

 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文件中。该文件应该是私有的,以便只有用户可以读取它(另请参阅“Security Considerations”一章),因为它可能包含纯文本密码。 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 POST


关于如何以正确的方式使用libcurl发出 POST请求,我们遇到了很多问题。因此,本章将包含使用libcurl支持的两种不同版本的HTTP POST的示例。

第一个版本是使用<form>标签的大多数HTML页面使用的简单POST,这是最常见的版本。我们提供了一个指向数据的指针,并告诉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); / *发帖! * /

 

 

 

好的,那么如果您想发布二进制数据,还需要您设置请求的Content-Type,二进制阻止libcurl能够对数据通过strlen()来计算大小,因此我们必须告诉libcurl post数据的大小。在libcurl请求中通用方式设置标请求头,方法是构建我们自己的表头列表,然后将该列表传递给libcurl。

由于您使用CURLOPT_POSTFIELDS设置POST选项,因此会自动切换句柄以在即将到来的请求中使用POST。

struct curl_slist *headers=NULL;

headers = curl_slist_append(headers, "Content-Type: text/xml");

/* 请求二进制数据 */

curl_easy_setopt(easyhandle, CURLOPT_POSTFIELDS, binaryptr);

/* 设置二进制数据长度 */

curl_easy_setopt(easyhandle, CURLOPT_POSTFIELDSIZE, 23L);

/* 设置请求头 */

curl_easy_setopt(easyhandle, CURLOPT_HTTPHEADER, headers);

curl_easy_perform(easyhandle); /* post away! */

curl_slist_free_all(headers); /* free the header list */

 

虽然上面的简单示例涵盖了需要HTTP POST操作的大多数情况,但它们并不执行 multi-part 操作。multi-part 被引入是作为提交二进制数据的更好方式,首先记录在 RFC 1867 中(在 RFC 2388 中更新)。它们被称为multi-part,因为它们是由一系列部件构建的,每个部分都是一个单独的数据单元。每个部分都有自己的名称和内容。事实上,您可以使用上面描述的常规libcurl POST创建和发布multi-part post表单,但这需要您自己构建一个post表单并提供给libcurl。为了简化这一过程,libcurl提供了一个包含多个函数的MIME API:使用这些函数,您可以创建并填充多部分表单。函数 curl_mime_init 创建一个 multi-part ;然后,您可以使用 curl_mime_addpart 附加进去。部件有三种可能的数据源:使用 curl_mime_data 的内存,使用 curl_mime_filedata 的文件和使用 curl_mime_data_cb 的用户定义的回调。

下面的示例设置两个带有纯文本内容的简单文本部分,然后设置一个带有二进制内容的文件并上传整个文本部分。

curl_mime *multipart = curl_mime_init(easyhandle);

curl_mimepart *part = curl_mime_addpart(mutipart);

curl_mime_name(part, "name");

curl_mime_data(part, "daniel", CURL_ZERO_TERMINATED);

part = curl_mime_addpart(mutipart);

curl_mime_name(part, "project");

curl_mime_data(part, "curl", CURL_ZERO_TERMINATED);

part = curl_mime_addpart(mutipart);

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);

 

要从已打开的FILE指针设置数据源,请使用:单个表单字段可发布多个文件,必须在单独的部分中提供每个文件,所有文件都具有相同的字段名称。 尽管函数 curl_mime_subparts 实现了嵌套的多部分,但 RFC 7578 第4.3章不推荐使用这种多文件上传方式。

  curl_mime_data_cb(part,filesize,(curl_read_callback)fread,(curl_seek_callback)fseek,NULL,filepointer);

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);

 

 

 

Multipart formposts是使用MIM风格分隔符和头部的部分链。 这意味着这些独立部分中的每一个都会获得一些描述内容类型(content-type),大小等的头部。为了使您的应用程序能够进行更多的工作,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 */

 

 

 

由于简易句柄上的所有选项都是“粘性的”,所以即使你调用 curl_easy_perform,它们也会保持不变,如果您打算在下一个请求中执行一个请求,则可能需要告诉curl返回到简单的GET请求。 您可以使用CURLOPT_HTTPGET 选项强制easyhandle返回GET:

curl_easy_setopt(easyhandle,CURLOPT_HTTPGET,1L);

只需将 CURLOPT_POSTFIELDS 设置为NULL将不停止libcurl执行POST。 它只会使POST没有任何数据发送!

将弃用的表单API转换为MIME API


在构建 multi-part 时必须遵守四条规则:

        • 在构建 multi-part 之前必须创建简易手柄。

        • multi-part 始终通过调用curl_mime_init(easyhandle)创建。

        • 每个部分都是通过调用 curl_mime_addpart(multipart)创建的。

        • 完成后,必须使用 CURLOPT_MIMEPOST 而不是 CURLOPT_HTTPPOST 将 multi-part 绑定到简易句柄。

以下是对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_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");

multipart/mixed字段将转换为具有相同名称的两个不同部分。

 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);

变为:

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);

变为:

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);

变为:

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对他进行改变。

C++


 

在连接libcurl时,使用C ++而不是C时,基本上只需要记住一件事:

 

回调不能是非静态类成员函数

 

示例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 */

}

}

 

 

 

代理


 

根据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,“user:password”);

如果需要,可以仅在CURLOPT_PROXY选项中指定主机名,并使用CURLOPT_PROXYPORT分别设置端口号。

告诉libcurl使用CURLOPT_PROXYTYPE它是什么类型的代理(如果没有,它将默认采用HTTP代理):

curl_easy_setopt(easyhandle,CURLOPT_PROXYTYPE,CURLPROXY_SOCKS4);

环境变量

libcurl自动检查并使用一组环境变量来了解某些协议使用的代理。变量的名称遵循一个事实标准,并建立为“[protocol] _proxy”(注意下面的案例)。这使变量'http_proxy'检查输入URL为使用HTTP协议的代理的名称。遵循相同的规则,检查名为“ftp_proxy”的变量为FTP URL。同样,代理总是HTTP代理时,变量名称的不同只意味着HTTP代理的不同。

代理环境变量内容的格式应为“[protocol://] [user:password @] machine [:port]”。如果存在protocol:// part,那么它就被忽略了(所以http:// proxy和bluerk:// proxy将做同样的事情),可选的端口号指定了代理在主机上运行的端口。如果未指定,将使用内部默认端口号,这很可能不是您想要使用端口号。

有两个特殊的环境变量。 'all_proxy'是在未设置协议特定变量的情况下为任何URL设置代理,并且'no_proxy'定义不应使用代理的主机列表,即使变量可能这样。如果'no_proxy'是普通星号(“*”),则它匹配所有主机。

要显式禁用libcurl检查和使用代理环境变量,请使用 CURLOPT_PROXY 将代理名称设置为“” - 空字符串。

SSL和代理

SSL用于安全的点对点连接。这涉及强加密类似的事情,这使代理无法作为如前所述,“人之间”的操作。相反,让SSL在HTTP代理上工作的唯一方法是让代理通过管道传输所有内容,而无需检查或摆弄流量。

因此,通过HTTP代理打开SSL连接是要求代理直接连接到指定端口上的目标主机。这是通过HTTP request CONNECT完成的。 (“请将我连接到该远程主机”)。

由于此操作的性质,代理不知道通过此管道传入和传出的数据类型,这会破坏使用代理所带来的一些优势,例如缓存。许多组织阻止这种管道到443以外的其他目标端口号(这是默认的HTTPS端口号)。

代理管道

如上所述,SSL需要管道工作,并且通常甚至限于用于SSL的操作;

然而,这不是代理管道可能为您或您的应用程序带来唯一好处的。

当管道打开从应用程序到远程计算机的直接连接时,它突然又重新引入了通过HTTP代理执行非HTTP操作的能力。 事实上,您可以通过这种方式使用FTP上传或FTP自定义命令等内容。

同样,代理管理员经常会阻止这种情况,很少被允许。

告诉libcurl使用这样的代理隧道:

  curl_easy_setopt(easyhandle,CURLOPT_HTTPPROXYTUNNEL,1L);

事实上,甚至你可以在这样的管道进行普通的HTTP操作,因为它可以使你直接在远程服务器上运行。

自动代理配置

 

Netscape首先提出了这个问题。它基本上是一个带有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连接节省了大量时间,因为跳过了命令响应往返,并且您也没有在未经许可的情况下被阻止再次登录,就像许多FTP服务器上只允许N个人登录一样同时。

 

libcurl缓存DNS名称解析结果,以便更快地查找以前查找过的名称。

 

其他有趣的细节可以在将来添加,以提高后续请求的性能。

 

每个简单的句柄的最后几个连接将被保存一段时间时间,以防它们再次使用。您可以使用CURLOPT_MAXCONNECTS 选项设置此“缓存”的大小。默认值为5.更改此值时很少有任何意义,如果您想要更改此值,通常只需要再考虑一下。

 

要强制你即将发出的请求不使用已经存在的连接(如果恰好有一个活动用于你将要运行的同一个主机,它甚至会先关闭一个),你可以通过将 CURLOPT_FRESH_CONNECT 设置为1来实现。类似的,您也可以通过将 CURLOPT_FORBID_REUSE 设置为1来禁止即将到来的请求在请求后重新被使用。

 

libcurl 使用的 HTTP Headers


当您使用libcurl执行HTTP请求时,它会自动传递一系列请求头信息。 了解和理解这些可能对你有好处。 您可以使用 CURLOPT_HTTPHEADER 选项替换或删除它们。

Host

HTTP 1.1甚至许多1.0服务器都需要此头部信息,并且应该是我们要与之通信的服务器的名称。 这包括端口号,如果没有的话,使用默认值。

Accept

“*/*”。

Expect

在执行POST请求时,libcurl将此标头设置为“100-continue”,以便在继续发送POST请求的数据部分之前向服务器询问“OK”消息。 如果被发送的数据量被认为是“小”,则libcurl将不使用此标头。

自定义操作


今天越来越多的协议建立在HTTP上。这具有明显的好处,因为HTTP是经过广泛部署且具有出色代理支持的经过测试且可靠的协议。

 

当您使用这些协议之一,甚至在进行其他类型的编程时,您可能需要更改传统的HTTP(或FTP或...)方式。您可能需要更改单词,headers 或各种数据。

 

libcurl也是你的朋友。

 

CUSTOMREQUEST

 

如果只是改变实际的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协议。

分块编码(Transfer-Encoding: chunked)

通过确保请求在执行非GET HTTP操作时使用自定义标头“Transfer-Encoding:chunked”,libcurl将切换到“chunked”上传,即使要上传的数据大小可能已知。 默认情况下,如果上传数据大小未知,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 */

如果您希望此操作(或操作链)数据传输之后进行同样的操作,则 curl_easy_setopt 的选项改为 CURLOPT_POSTQUOTE,并使用完全相同的方式。

自定义FTP命令将按照它们添加到列表的顺序发送到服务器,如果获得从服务器返回的错误代码,则不再发出命令,并且libcurl将返回错误代码(CURLE_QUOTE_ERROR)。 请注意,如果使用CURLOPT_QUOTE在传输之前发送命令,则在quote命令失败时不会发生传输。

如果将CURLOPT_HEADER设置为1,您将告诉libcurl获取有关目标文件的信息并输出关于它的“headers”。 标题将采用“HTTP风格”,与HTTP中的headers类似。

启用标headers或运行自定义FTP命令的选项可能与CURLOPT_NOBODY结合使用。 如果设置此选项,则不会执行实际的文件内容传输。

FTP自定义CUSTOMREQUEST

如果您确实想使用自己定义的FTP命令列出FTP目录的内容,CURLOPT_CUSTOMREQUEST就会这样做。 “NLST”是列出目录的默认值,但您可以自由地传递一个好的选择。

没有巧克力片的饼干(Cookies Without Chocolate Chips)


在HTTP意义上,cookie是具有关联值的名称。服务器将名称和值发送到客户端,并期望在每次后续请求时将其送回与设置的特定条件匹配的服务器。条件包括域名和路径匹配以及cookie没有失效。

在实际情况中,服务器发送新cookie以替换现有cookie以更新它们。服务器使用cookie来“跟踪”用户并保持“会话”。

Cookie通过header Set-Cookie从服务器发送到客户端:它们从客户端发送到具有Cookie header的服务器。

要将您想要的任何cookie发送到服务器,您可以使用 CURLOPT_COOKIE 来设置如下的cookie字符串:

 curl_easy_setopt(easyhandle,CURLOPT_COOKIE,“name1 = var1; name2 = var2;”);

在许多情况下,这还不够。您可能希望动态保存远程服务器传递给您的任何cookie,并确保在以后的请求中相应地使用这些cookie。

一种方法是将您收到的所有 header 保存在普通文件中,当您发出请求时,告诉libcurl读取前面的header 以确定要使用的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文件中。我们称之为cookie-jar。使用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设置为零来禁用此行为。

使用MIME API构建SMTP和IMAP


 

除了支持HTTP multi-part 表单字段之外,MIME API还可用于构建结构化电子邮件并通过SMTP发送或将此类邮件附加到IMAP目录。

 

结构化电子邮件消息可能包含几个部分:一些部分由MUA内联显示,一些是附件。部件也可以构造为multi-part,例如包括另一个电子邮件消息或提供多种文本格式的替代品。这可以嵌套到任何级别。

 

要构建此类消息,请准备多级 multi-part,然后使用函数 curl_mime_subparts 将其作为源多部分的源包含。一旦绑定到其父多部分,则第n级多部分属于它,不应显式释放。

 

电子邮件消息数据应该是ascii并且行长度是有限的:幸运的是,标准定义了一些传输编码以支持这种不兼容数据的传输。函数 curl_mime_encoder 告诉部件必须在发送之前对其源数据进行编码。它还为该部分生成相应的header。如果您要发送的部件数据已经在这样的方案中编码,请不要使用此函数(这会对其进行过度编码),而是显式设置相应部件header。

 

在发送这样的消息时,libcurl将它与使用CURLOPT_HTTPHEADER设置的头列表一起作为0级mime的头部。

下面是一个构建电子邮件的示例,其中包含内联plain / html文本替代和base64编码的文件附件:

curl_mime *message = curl_mime_init(easyhandle);

/* The inline part is an alternative proposing the html and the text

versions of the e-mail. */

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目录需要在上载之前知道消息大小。 因此,在此上下文中不可能包含具有未知数据大小的部分。

额外的Headers操作(Headers Equal Fun)


一些协议提供与正常数据分离的元数据作为“标题”。 默认情况下,这些标头不包含在普通数据流中,但您可以通过将CURLOPT_HEADER设置为1使它们出现在数据流中。

可能更有用的是libcurl能够将headers与数据分开,从而使回调不同。 例如,您可以通过设置CURLOPT_HEADERDATA 来设置不同的指针以传递普通的写回调。

或者,您可以使用CURLOPT_HEADERFUNCTION 设置一个完全独立的函数来接收headers。

headers 将逐个传递给回调函数,它使您可以更轻松地添加自定义标头解析器等。

FTP传输的“Headers”等于所有FTP服务器响应。 它们实际上不是真正的Headers,但在这种情况下我们假装它们是!;-)

传输后信息


See curl_easy_getinfo.

多接口


本文档中详细描述的简单界面是一个同步接口,它一次传输一个文件,并且在完成之前不会返回。

另一方面,多接口允许程序同时在两个方向上传输多个文件,而不会强制您使用多个线程。这个似乎可以使多接口用于多线程程序,但事实恰恰相反。多接口允许单线程应用程序执行相同类型的多个同时传输。它具有允许多线程传输的许多好处,而无需管理和同步许多线程的复杂性。

更复杂一点,甚至还有两个版本的多接口。基于事件的一个,也称为multi_socket,而“normal one”设计用于与select()一起使用。有关基于multi_socket事件的API的详细信息,请参见libcurl-multi.3手册页,此处的描述适用于面向select()的API。

要使用多接口,最好先了解如何使用简易接口的基础知识。多接口只是一种通过将多个简单句柄添加到“multi stack”中来同时进行多次传输的方法。

您可以创建所需的简单句柄,每个并发传输一个,然后像上面学到的那样设置所有选项,然后使用curl_multi_init 创建一个多句柄,并使用curl_multi_add_handle将所有这些简单句柄添加到该多句柄。

当您添加暂时拥有的句柄时(您仍然可以随时添加新句柄),您可以通过调用curl_multi_perform来启动传输。

curl_multi_perform是异步的。它只会执行现在可以执行的操作,然后将控制权返回给您的程序。它旨在永不阻止。您需要继续调用该函数,直到完成所有传输。

此接口的最佳用法是在所有可能的文件描述符或套接字上执行select()以了解何时再次调用libcurl。这也使您可以轻松地等待并响应您自己的应用程序的套接字/句柄上的操作。您可以通过使用curl_multi_fdset 找出要选择的select(),它会为您填充一组fd_set变量,其中包含libcurl目前使用的特定文件描述符。

然后当你调用select()时,它将在其中一个文件处理信号执行时返回,然后你调用curl_multi_perform 以允许libcurl做它想做的事情。请注意,libcurl还具有一些超时代码,因此我们建议您在再次调用 curl_multi_perform 之前不要在select()上使用很长的超时。 curl_multi_timeout 用于帮助您获得合适的超时时间。

您应该使用的另一个预防措施:始终在select()调用之前立即调用curl_multi_fdset ,因为当前的文件描述符集可能会在任何curl函数调用中发生更改。

如果要停止传输堆栈中的一个简易句柄,可以使用curl_multi_remove_handle删除单个简易句柄。 请记住,简单的句柄应该是curl_easy_cleanuped

当multi stack内的传输完成时,运行传输的计数器(由curl_multi_perform填充)将减少。 当数字达到零时,所有传输都完成。

curl_multi_info_read可用于获取有关已完成传输的信息。 然后,它会返回每次简单转移的CURLcode,以便您在每次转移时找出成功。

SSL, 证书 以及其他技巧


[ seeding, passwords, keys, certificates, ENGINE, ca certs ]

在简易句柄之间共享数据


使用简易接口时,您可以在简易句柄之间共享一些数据,并且在使用多接口时,某些数据会自动共享。

当您将简易句柄添加到多句柄时,这些简易句柄将自动共享大量数据,此外当使用简易接口时,这些数据将基于每个简易句柄保留。

DNS缓存在多句柄内的句柄之间共享,使后续名称解析更快,并且用于保持持久连接和连接重用的连接池也是共享的。 如果您使用的是简易接口,您仍然可以使用共享接口在特定的简单句柄之间共享这些,请参阅libcurl-share

有些东西永远不会自动共享,不会在多个句柄中共享,例如cookie,因此共享的唯一方法是使用共享接口

脚注


 

[1]

 

libcurl 7.10.3及更高版本能够在使用未知大小的数据完成HTTP上传的情况下切换到分块传输编码。

 

[2]

 

在Windows上,当构建libcurl并将其用作DLL。如果使用静态库链接,仍可以在Windows上执行此操作。

 

[3]

 

curl-config工具是在构建时生成的(在类Unix系统上),应该执行‘make install'或类似操作,安装头文件,手册页等的类似指令。

 

[4]

 

在7.17.0之前的版本中,此行为是不同的,其中字符串必须在curl_easy_setopt调用结束后保持有效。

其他


libcurl-errorslibcurl-multilibcurl-easy

 

 

Logo

CSDN联合极客时间,共同打造面向开发者的精品内容学习社区,助力成长!

更多推荐