本站文章均为 李华明Himi 原创,转载务必在明显处注明:
转载自【黑米GameDev街区】 原文链接: http://www.himigame.com/curl-libcurl/878.html
☞ 点击订阅 ☜ 本博客最新动态!及时将最新博文通知您!
注意:如果你的服务器是Java的,那么要注意数据之间的大端小端的处理;否则无法正常获取正确的数据!
本篇介绍使用libcurl编程的一般原则和一些基本方法。本文主要是介绍 c 语言的调用接口,同时也可能很好的适用于其他类 c 语言的接口。
跨平台的可移植代码
libcurl库背后的开发人员投入了相当大的努力确保libcurl可以在很多不同的系统和环境里工作。
全局的准备
程序必须初始化一些libcurl的全局函数。这意味着不管你准备使用libcurl多少次,你都应该,且只初始化一次。当你的程序开始的时候,使用
curl_global_init()
这个函数需要一个参数来告诉它如何来初始化。使用CURL_GLOBAL_ALL ,它将用通常是比较好的默认参数来初始化所有已知的内部子模块。还有两个可选值:
CURL_GLOBAL_WIN32
这个参数只被用在windows 操作系统上。它让libcurl初始化win32套接字的的东西。没有正确的初始化,你的程序将不能正确的使用套接字。你应该只为每个程序做一次这样的操作,如果你的程序的其他库这样了,你就不要再让libcurl这样做。
CURL_GLOBAL_SSL
这个参数会使libcurl具有SSL的能力。你应该只为每个程序做一次这样的操作,如果你的程序的其他库这样了,你就不要再让libcurl这样做。
libcurl有个默认的保护机制,检测如果curl_global_init没有在curl_easy_perform之前被调用,那么libcurl会猜测该使用的初始化模式来执行程序。请注意,依靠默认的保护机制来这么做一点都不好。
当程序不再使用libcurl,请调用curl_global_cleanup函数来对应初始化函数所做的工作,它会做逆向的工作来清理curl_global_init所初始化的资源。
请避免重复的调用curl_global_init和 curl_global_cleanup。他们每个仅被调用一次。
libcurl所支持的功能
确定libcurl所提供功能的最佳办法是在运行的时候而不是在编译的时候。通过调用curl_version_info函数,然后查看返回信息的结构体,你知道当前的libcurl版本所支持的功能了。
使用libcurl的简单接口
首先介绍libcurl的简单接口(easy interface),这些接口都有curl_easy的前缀。 libcurl的最近版本还提供了复杂接口(multi interface)。更多关系该接口的信息将另作讨论,为了更好的理解复杂接口,你仍然需要先了解简单接口。
为了使用简单接口,你先需要创建一个简单接口的句柄。每一个简单接口的数据通信都需要这个句柄。一般来说,你需要为每个准备传输数据的线程使用一个句柄。但你绝不能在多线程里共享相同的句柄。
获取简单句柄
easyhandle = curl_easy_init();
这个函数返回一个简单接口句柄。接下来的操作力,可以在这个`easyhandle设置各种选项。在随后的一个或者多次数据传输中,句柄只是一个逻辑实体。 通过 curl_easy_setopt设置句柄的属性和选项,它们控制随后的数据传输。这些属性和选项的设置一个保存在句柄里直到它再次被设置为其他值。多次网络请求使用相同的句柄,它们的句柄选择和属性也是相同的。
很多用于设置libcurl属性都是字符串,一个指向一段以0结尾数据。当你用字符串设置 curl_easy_setopt(),libcurl会复制这个字符串的一个副本,所以你在设置后不用再保存那个字符串的内存。
在句柄上最经常设置的属性是URL。你可以这样设置它
curl_easy_setopt(handle, CURLOPT_URL, ”http://domain.com/”);
假如你希望得到指定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调用函数之间传递本地数据。使用 CURLOPT_WRITEDATA,libcurl不会处理你所传递的数据。
假如你没用使用CURLOPT_WRITEFUNCTION设置回调函数,libcurl会有默认的处理。它只是把接收到的数据输出到标准输出里。你可以使用传递一个FILE *的打开文件参数,设置默认处理函数CURLOPT_WRITEDATA ,它把得到的数据存放在一个文件里。
这里有一个与平台有关的缺陷。有时候libcurl不能操作一个呗程序所打开的文件。如果你用CURLOPT_WRITEDATA给默认的回调函数传递一个打开的文件,程序可能崩溃。(CURLOPT_WRITEDATA 原来的名称为CURLOPT_FILE,它们是同样的工作机理)。
如果你以 win32 dll的方式使用libcurl,如果你设置了,CURLOPT_WRITEDATA ,也必须用CURLOPT_WRITEFUNCTION ,否则你会遇到程序崩溃。 还有其他很多的选项可以设置,我们放在后面再详细讨论,接着往下看
success = curl_easy_perform(easyhandle);
curl_easy_perform函数将会连接远程的站点,发送必要的命令和接受传输的数据。当它收到了数据,它就会调用我们先前设置的回调函数。这个函数可能一次得到一个字节或者几千个字节。libcurl会尽可能多的,尽可能快的传回数据。我们回调函数返回它所得到数据的大小。如果返回的数据大小与传递给它数据大小不一致,libcurl将会终止操作,并返回一个错误代码。
当数据传输完成,curl_easy_perform返回一个代码来告诉你是否成功。如果你仅返回一个代码还不够,你可以使用CURLOPT_ERRORBUFFER ,让libcurl缓存许多可读的错误信息
如果你还想传输其他数据,已有的句柄可以多次使用。记住,鼓励你使用现有的句柄来传输其他数据,libcurl会尝试已经先前已经建立好的连接。
对于某些协议,下载文件可能涉及到很多复杂的协议用来记录信息,设置传输模式,更改当前的目录并最终传递数据。传递一个文件的URL,libcurl会为你掌管所有细节,把文件从一台机器移动到两一台机器。
多线程问题
最基本的原则是绝对不要同时在多个线程之间共享一个libcurl的句柄。确保任何时候一个句柄只是在一个线程里使用。你可以在多个线程之间传递句柄,但是你不能使用。
libcurl是线程安全的,除了以下两种情况:信号量和SSL/TLS句柄。信号量用于超时失效名字解析(在域名解析的时候)。
如果你通过多线程方式来访问HTTPS 或者 FTPS 网址,你可以使用底层的SSL库,多线程库。这些库可能有它们独有的要求,你要多加注意详细参考
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 它宣称是线程安全,没有任何特殊的要求。 PolarSSL 未知。yassl 未知。axTLS 未知。
当你使用多线程的时候,你应当为所有的句柄设置CURLOPT_NOSIGNAL 选项。所有的时候都可以工作正常除了DNS查询超时的时候。 同样,CURLOPT_DNS_USE_GLOBAL_CACHE 也不是线程安全的。
当libcurl实际无法工作
总是有各种原因导致网络传输失败。你可能设置错误的libcurl选项,误解了libcurl某些选项的实际作用,或者远程服务器返回libcurl一个非标准的应答。
当发生错误的时候,这里有一个黄金法:设置CURLOPT_VERBOSE 选项为1。这将导致libcurl显示出所有发送的实体协议的细节,或者还有一些内部的信息和一些收到协议的数据(尤其是 FTP)。如果你使用HTTP ,CURLOPT_HEADER设为1,请求头/响应头也会被输出,这些头信息将出现在消息的内容中。
如果CURLOPT_VERBOSE 还不够,你设置CURLOPT_DEBUGFUNCTION来调试你的数据。
上传数据到远程站点
libcurl尽量保持与协议无关性,就是上传文件到远程的FTP跟用PUT方式上传数据到HTTP服务器和非常类似的。 我们写一个程序,很可能想libcurl按照我们的要求上传数据。我需要设置如下函数原型的读数据的回调函数
size_t function(char *bufptr, size_t size, size_t nitems, void *userp);
bufptr 参数指向一段准备上传数据的缓冲区,nitem是这段缓冲区的大小,userp是一个用户自定义指针,libcurl不对该指针作任何操作,它只是简单的传递该指针。可以使用该指针在应用程序与libcurl之间传递信息。
curl_easy_setopt(easyhandle, CURLOPT_READFUNCTION, read_function);
curl_easy_setopt(easyhandle, CURLOPT_READDATA, &filedata);
Tell libcurl that we want to upload:
curl_easy_setopt(easyhandle, CURLOPT_UPLOAD, 1L);
有几个协议将不能正常工作当上传的时候没有告诉上传文件的大小。所以设置上传文件的大小请使用CURLOPT_INFILESIZE_LARGE
/* in this example, file_size must be an curl_off_t variable */
curl_easy_setopt(easyhandle, CURLOPT_INFILESIZE_LARGE, file_size);
当你调用curl_easy_perform()的时候,libcurl会执行所有的必要动作,当开始上传的时候,它会调用我的回调函数。程序会尽可能多,尽可能快的的上传数据。回调函数返回写入缓冲区的数据的大小。返回0的时候就表示上传结束了。
密码
许多协议要求用户名和密码才能下载或者上传你所选择的数据。libcurl提供了指定的几种方法。
许多协议都支持用户名和密码包含在指定的URL里。libcurl会检测并相应的使用它们。可以按照这样的格式写
protocol://user:[email protected]/path/
如果你的用户名和密码需要一些奇特的字符,你应该使用URLd编码,像%XX,其中XX是两位十六进制的数字。
libcurl同事也提供了一个设置各种类型密码的选项。设置CURLOPT_USERPWD 选项如下
curl_easy_setopt(easyhandle, CURLOPT_USERPWD, ”myname:thesecret”);
在某些情况下可能会多次用到用户名和密码,可以使用代码来验证身份。libcurl提供一个CURLOPT_PROXYUSERPWD选项来实现这种功能,跟CURLOPT_USERPWD 选项很类似
curl_easy_setopt(easyhandle, CURLOPT_PROXYUSERPWD, ”myname:thesecret”);
HTTP 认证
先前的章节我们显示了如何为需要验证的URL设置用户和密码。当我们使用HTTP协议的时候,客户端有许多不同的向服务器发送身份验证。最基本的HTTP认证为Basic认证,它发送base64编码的明文用户和密码,这不安全。 如今,libcrul支持使用Basic, Digest, NTLM, Negotiate, GSS-Negotiate and SPNEGO等方式的认证,你可以设置CURLOPT_HTTPAUTH 告诉libcurl使用那种认证。
curl_easy_setopt(easyhandle, CURLOPT_HTTPAUTH, CURLAUTH_DIGEST);
当你想代理服务器认证的时候,你可以使用
CURLOPT_PROXYAUTH:
curl_easy_setopt(easyhandle, CURLOPT_PROXYAUTH, CURLAUTH_NTLM);
你可以组合多种认证方式,libcurl会以合理的方式处理 curl_easy_setopt(easyhandle, CURLOPT_HTTPAUTH, CURLAUTH_DIGEST|CURLAUTH_BASIC);
为了方便起见,你还可以使用CURLAUTH_ANY,它允许libcurl使用任何想要的方法。
HTTP POST
HTML最简单的也是最常见的POST是使用