libcurl提供了一组C语言API函数直接调用。首先需要提到的两个函数就是curl_global_init()和 curl_global_cleanup()。libcurl要用到一系列的全局常量,curl_global_init()函数就是初始化这些变量,并分配一些全局资源;curl_global_cleanup()则负责释放这些资源。因此一般情况下,在调用libcurl函数之前,先用 curl_global_init(CURL_GLOBAL_ALL)做初始化,在调用完毕后,用curl_global_cleanup()退出。
需要注意的是,这些全局变量和资源并不是线程安全的,因此,在多线程应用的环境中,最好不要多次调用curl_global_init()和curl_global_cleanup(),调用其他函数并不会改变这些全局变量和资源。
libcurl支持3种不同的接口调用方式,分别是"easy"、"multi"和"share"模式。
libcurl-easy是一组同步接口,函数都 是curl_easy_*形式,这种模式调用curl_easy_perform()函数进行URL数据传输,直到传输完成函数才返回;
libcurl- multi是一组异步接口,函数都是curl_multi_*形式,调用curl_multi_perform()函数进行传输,但是每次调用只传一片数据,我们可以用select()函数控制多个下载任务进行同步下载,来实现在一个线程中同时下载多个文件;
libcurl-share允许在多线程中操作共享数据。下面以libcurl-easy为例讲一下libcurl的函数。
1、CURL *curl_easy_init()
此函数需要最先被调用,返回CRUL easy句柄;后续其他函数调用都要用到这个句柄。如果没有调用curl_global_init(),该函数自动调用,但是考虑到线程安全的问题,最好自己调用curl_global_init()。
2、CURLcode curl_easy_setopt(CURL *handle, CURLoption option, parameter)
所有参数和选项设置都是通过这个函数完成的,它告诉libcurl怎样去进行传输。参数handle即为curl_easy_init()返回的句柄,后 面根据option的类型,设置相应的parameter值,该函数每次调用只能设置一个选项。具体的option讲解在这两篇博文中有较全面的介绍:
http://blog.163.com/wangsen_315/blog/static/9461414200882384555217/
http://blog.sina.com.cn/s/blog_4f9fc6e10100einf.html
这里只总结一下与下载有关的常用选项设置。
CURLOPT_URL
字符串类型,该选项设置要处理的URL地址,该选项是进行curl_easy_perform之前唯一必须要设置的选项。
CURLOPT_COOKIE
字符串类型,设置http头中的cookie信息。
CURLOPT_COOKIEFILE
字符串类型,同CURLOPT_COOKIE,不过cookie信息从文件中读取。
CURLOPT_FOLLOWLOCATION
布尔值类型,该参数设置为非零值表示follow服务器返回的重定向信息。
CURLOPT_POSTFIELDS
字符串类型,提交http的post操作字符串数据。
CURLOPT_TIMEOUT
long数值类型,设置函数执行的最长时间,时间单位为s。
CURLOPT_CONNECTTIMEOUT
long数值类型,设置连接服务器最长时间,时间单位为s;当置为0时表示无限长。
CURLOPT_MAX_RECV_SPEED_LARGE
curl_off_t类型数据,指定下载过程中最大速度,单位bytes/s。
CURLOPT_HEADERFUNCTION
函数指针类型,该选项设置一个处理接收到的header数据的回调函数,函数原型为:
size_t function( void *ptr, size_t size, size_t nmemb, void *stream);
其中,ptr指向接收到的header头数据,数据大小为size*nmemb,stream指向调用CURLOPT_WRITEHEADER选项设置的参数。该回调函数应返回实际处理的数据量大小,或者出错返回-1。
CURLOPT_WRITEFUNCTION
函数指针类型,该选项设置一个处理接收到的下载数据的回调函数,函数原型为:
size_t function( void *ptr, size_t size, size_t nmemb, void *stream);
其中,ptr指向接收到的数据,数据大小为size*nmemb,stream指向调用CURLOPT_WRITEDATA选项设置的参数。
如果函数指针置为NULL,则会调用默认的函数,将数据写入到由CURLOPT_WRITEDATA指定的FILE*中。
CURLOPT_HTTPHEADER
curl_slist结构体类型,该选项自定义请求头信息。
CURLOPT_NOPROGRESS
布尔值类型,设置该值为非零值关闭PHP为CRUL传输显示的进度条。
CURLOPT_INFILESIZE
当你上传一个文件到远程站点,这个选项告诉PHP你上传文件的大小。
CURLOPT_VERBOSE
如果你想CURL报告每一件意外的事情,设置这个选项为一个非零值。
CURLOPT_HEADER
如果你想把一个头包含在输出中,设置这个选项为一个非零值。
CURLOPT_NOBODY
如果你不想在输出中包含body部分,设置这个选项为一个非零值。
CURLOPT_FAILONERROR
如果你想让PHP在发生错误(HTTP代码返回大于等于300)时,不显示,设置这个选项为一人非零值。默认行为是返回一个正常页,忽略代码。
CURLOPT_UPLOAD:如果你想让PHP为上传做准备,设置这个选项为一个非零值。
*CURLOPT_POST: 如果你想PHP去做一个正规的HTTPPOST,设置这个选项为一个非零值。这个POST是普通的application/x-www-from-urlencoded 类型,多数被HTML表单使用。
*CURLOPT_FTPLISTONLY:设置这个选项为非零值,PHP将列出FTP的目录名列表。
*CURLOPT_FTPAPPEND:设置这个选项为一个非零值,PHP将应用远程文件代替覆盖它。
*CURLOPT_NETRC: 设置这个选项为一个非零值,PHP将在你的 ~./netrc文件中查找你要建立连接的远程站点的用户名及密码。
*CURLOPT_FOLLOWLOCATION: 设置这个选项为一个非零值(象“Location:“)的头,服务器会把它当做HTTP头的一部分发送(注意这是递归的,PHP将发送形如“Location: “的头)。
*CURLOPT_PUT:设置这个选项为一个非零值去用HTTP上传一个文件。要上传这个文件必须设置CURLOPT_INFILE和 CURLOPT_INFILESIZE选项.*CURLOPT_MUTE:设置这个选项为一个非零值,PHP对于CURL函数将完全沉默。
*CURLOPT_LOW_SPEED_LIMIT:设置一个长整形数,控制传送多少字节。
*CURLOPT_LOW_SPEED_TIME:设置一个长整形数,控制多少秒传送CURLOPT_LOW_SPEED_LIMIT规定的字节数。
*CURLOPT_RESUME_FROM:传递一个包含字节偏移地址的长整形参数,(你想转移到的开始表单)。
*CURLOPT_SSLVERSION:传递一个包含SSL版本的长参数。默认PHP将被它自己努力的确定,在更多的安全中你必须手工设置。
*CURLOPT_TIMECONDITION:传递一个长参数,指定怎么处理CURLOPT_TIMEVALUE参数。你可以设置这个参数为TIMECOND_IFMODSINCE或 TIMECOND_ISUNMODSINCE。这仅用于HTTP。
*CURLOPT_TIMEVALUE:传递一个从1970-1-1开始到现在的秒数。这个时间将被CURLOPT_TIMEVALUE选项作为指定值使用,或被默认TIMECOND_IFMODSINCE使用。下列选项的值将被作为字符串:
*CURLOPT_USERPWD:传递一个形如[username]:[password]风格的字符串,作用PHP去连接。
*CURLOPT_PROXYUSERPWD: 传递一个形如[username]:[password]格式的字符串去连接HTTP代理。
*CURLOPT_RANGE:传递一个你想指定的范围。它应该是”X-Y”格式,X或Y是被除外的。HTTP传送同样支持几个间隔,用逗句来分隔(X-Y,N-M)。
*CURLOPT_POSTFIELDS: 传递一个作为HTTP“POST”操作的所有数据的字符串。
*CURLOPT_REFERER:在HTTP请求中包含一个”referer”头的字符串。
*CURLOPT_USERAGENT:在HTTP请求中包含一个”user-agent”头的字符串。
*CURLOPT_FTPPORT: 传递一个包含被ftp“POST”指令使用的IP地址。这个POST指令告诉远程服务器去连接我们指定的IP地址。这个字符串可以是一个IP地址,一个主机名,一个网络界面名(在UNIX下),或是‘-’(使用系统默认IP地址)。
*CURLOPT_COOKIE: 传递一个包含HTTP cookie的头连接。
*CURLOPT_SSLCERT: 传递一个包含PEM格式证书的字符串。
*CURLOPT_SSLCERTPASSWD:传递一个包含使用CURLOPT_SSLCERT证书必需的密码。
*CURLOPT_COOKIEFILE:传递一个包含cookie数据的文件的名字的字符串。这个cookie文件可以是Netscape格式,或是堆存在文件中的HTTP风格的头。
*CURLOPT_CUSTOMREQUEST:当进行HTTP请求时,传递一个字符被GET或HEAD使用。为进行DELETE或其它操作是有益的,更Passa string to be used instead of GET or HEAD when doing an HTTPrequest. This is useful for doing or another, more obscure, HTTPrequest. 注意:在确认你的服务器支持命令先不要去这样做。下列的选项要求一个文件描述(通过使用fopen()函数获得):
*CURLOPT_FILE:这个文件将是你放置传送的输出文件,默认是STDOUT.
*CURLOPT_INFILE: 这个文件是你传送过来的输入文件。
*CURLOPT_STDERR:这个文件写有错误而不是stderr。用来获取需要登录的页面的例子,当前做法是每次或许都登录一次,有需要的人再做改进了:)
3、void curl_easy_reset(CURL *handle )
重新初始化CURL句柄的选项设置。
4、CURLcode curl_easy_getinfo(CURL *curl, CURLINFO info, ... )
查询CRUL会话的内部信息,具体说明请参考curl自带文档。
5、void curl_easy_cleanup(CURL * handle )
该函数与curl_easy_init函数成对出现,handle即为调用curl_easy_init返回的句柄。该函数在CURL会话结束退出时调用,之后handle无效。
Webkit模块
用到的第三方库如下:
cairo |
一个2D绘图库 |
casqt |
Unicode处理用的库,从QT中抽取部分代码形成的 |
expat |
一个XML SAX解析器的库 |
freetype |
矢量字库接口库,用于存取ttf矢量字体文件 |
libcurl |
一个开源的url库,支持HTTP、FTP等协议 |
Libjpeg,libpng |
图像解码库 |
libxml |
基于DOM树的XML解析器 |
libxslt |
XML transform engine |
pthread |
Pthread库, port of the POSIX thread library |
sqlite3 |
一个小型的数据库,据称在型入式平台是存取速度最快的数据库。 开源, 编译后就一个400K的 sqlite.dll。 移植非常方便,纯C写的。 |
wceshunt |
一个用于Windows CE平台下的C常用函数封装库 |
Zlib |
Zlib库。用于解压缩。 |
2. Webkit 源代码由三大模块组成:
1). WebCore,
2). WebKit,
3). JavaScriptCore。
WebCore:排版引擎核心,WebCore包含主要以下模块:Loader, Parser(DOM,Render), Layout,Paint。
WebKit:移植层,主要包含: GUI,File System, Thread,Text,图片编解码等与平台相关的函数。
JavaScriptCore:JS虚拟机,相对独立,主要用于操作DOM, DOM是W3C定义的规范,主要用于定义外部可以操作的浏览器内核的接口,而webcore必须实现DOM规范。
(具体的DOM规范可以查w3c.)
3. WebKit分模块介绍(这里简单列出,后面再具体介绍)
Webkit平台相关
1) CURL网络库
2) libPng, LibJpeg图形处理相关
3) sqlite小型关系数据库
WebCore核心
1) Loader加载资源及Cache 实现(Curl)
2) DOM : HTML词法分析与语法分析
3) DOM : DOM节点与Render节点创建,形成DOM树
4) Render:Render树介绍,RenderBox
5) Layout:排版介绍
6) Css Parser模块
7) Binding-DOM与JavascriptCore绑定的功能
JavascriptCore-javascript引擎
1) API-基本javascript功能
2) Binding与其它功能绑定的功能,如:DOM,C,JNI
3) DerviedSource自动产生的代码
4) PCRE-Perl-Compatible Regular Expressions
5) KJS-Javascript Kernel
4. 页面的整个处理流程—(简单介绍,详细流程在后面笔记中)
1). 用户输入网址后,FrameLoader::load函数会接收到URL。
2). 把URL 请求传给CURL库。
3). CURL发出http请求,得到数据后,传给Loader,开始解析。
4). 通过Dom Builder按W3C的html规范生成Dom树
5). 如果有javascript,JSEngine就通过ECMA-262标准完善Dom树
6). 在生成DOM树的同时, 同步生成Render树。
7). 解析完后, 调用Layout排版
8). Paint出来
libCurl库介绍
前面有说道webkit仅仅是一个页面排版的引擎,所以,对webkit来说,网页数据(html文件,图片,.css,.js文件)的请求与接收都是通过第三方的库:libCurl来处理。
打开webkit开发工程(.sln)即可以看到,libcurl可以被静态或动态链接到主工程中。
Libcurl就是指的curl,只是在webkit工程中,不作为单独的进程存在,而是被编译成动态库。
webkit主要用到curl的以下功能:
1) Http协议。包含:Get, put, Post, Cookie管理。
2) https协议。
3) 本地文件缓存。(前进,后退管理)
Webkit具体调用了哪些curl接口,详见后面Loader模块介绍章节。这里简单列举:
1) curl_global_init(CURL_GLOBAL_ALL);
2) curl_multi_init()
3) curl_share_init()
4) curl_share_setopt()
5) curl_easy_getinfo()
6) curl_multi_fdset()
7) curl_multi_perform()
8) curl_multi_info_read()
9) curl_multi_cleanup()
10) curl_share_cleanup()
11) curl_global_cleanup();
可以看到,由于webkit要支持同时请求多个http数据,所以用到的是curl的multi接口。
在介绍Loader之前,先介绍一下libcurl,打下基础。
以下附一篇libcurl的介绍:
一、概念
1. 为什么要使用libcurl
1) 作为http的客户端,可以直接用socket连接服务器,然后对到的数据进行http解析,但要分析协议头,实现代理…这样太麻烦了。
2) libcurl是一个开源的客户端url传输库,支持FTP,FTPS,TFTP,HTTP,HTTPS,GOPHER,TELNET,DICT,FILE和LDAP,支持Windows,Unix,Linux等平台,简单易用,且库文件占用空间不到200K
2. get和post方式
客户端在http连接时向服务提交数据的方式分为get和post两种
1) Get方式将所要传输的数据附在网址后面,然后一起送达服务器,它的优点是效率比较高;缺点是安全性差、数据不超过1024个字符、必须是7位的ASCII编码;查询时经常用此方法。
2) Post通过Http post处理发送数据,它的优点是安全性较强、支持数据量大、支持字符多;缺点是效率相对低;编辑修改时多使用此方法。
3. cookie与session
1) cookie
cookie是发送到客户浏览器的文本串句柄,并保存在客户机硬盘上,可以用来在某个Web站点会话之间持久地保持数据。cookie在客户端。
2) session
session是访问者从到达某个特定主页到离开为止的那段时间。每一访问者都会单独获得一个session,实现站点多个用户之间在所有页面中共享信息。session在服务器上。
3) libcurl中使用cookie
保存cookie, 使之后的链接与此链接使用相同的cookie
a) 在关闭链接的时候把cookie写入指定的文件
curl_easy_setopt(curl, CURLOPT_COOKIEJAR, "/tmp/cookie.txt");
b) 取用现在有的cookie,而不重新得到cookie
curl_easy_setopt(curl, CURLOPT_COOKIEFILE, "/tmp/cookie.txt");
b) http与https的区别
1) Http是明文发送,任何人都可以拦截并读取内容
2) Https是加密传输协议,用它传输的内容都是加密过的,https是http的扩展,其安全基础是SSL协议
c) base64编码
1) 为什么要使用base64编码
如果要传一段包含特殊字符比较多的数据,直接上传就需要处理转意符之类的很多问题,用base64编码,它可以把数据转成可读的字串,base64由a-z, A-Z, +/总计64个字符组成。
2) 传送base64编码的注意事项
由于base64的组成部分有加号,而加号是url中的转意字符,所以无论是get方式还是post,传到服务器的过程中,都会把加号转成空格,所以在传base64之前需要把base64编码后的加号替换成”%2B”,这样就可以正常发送了。
二、例程
d) 代码
e) 编译
g++ main.cpp -o main -lcurl
三、Loader模块介绍
前面说过, webkit只是一个排版引擎,在Webkit排版/渲染一个网页之前, 它肯定需要从网络上、或者本地文件系统中读到网页的http数据,对吧,对webkit来讲,他要的就是数据,不管你是从网络读的还是本地文件读的。
Loader就是这样一个模块,它承上启下,不仅负责为webkit引擎提供数据,还控制着webkit的绘制。另外,它同时还与提供数据的“来源”打交道。
先简单举例说明:
用户输入一个url,这时是Loader接收url请求,它把url传递给curl,设置curl的回调函数,当curl读到数据,loader把数据传递给Parser,开始生成DOM。
下面重点介绍一下与Loader相关的数据结构和模块。
Frame:可以看做是浏览器外壳调用Loader的总入口,它就像我们印象中的一个网页,它关注的是页面的显示 (FrameView) 、页面数据的加载(FrameLoader) 、页面内的各种控制器 (Editor, EventHandler, ScriptController, etc.) 等等,它包含以下模块(只列出重点):
Document
Page
FrameView
RenderView
FrameLoader
DOMWindow
下面分别介绍(PS: 必须要了解这些概念,不然后面的东东都无法理解):
1)Document:这个类的爷爷类是 Node ,它是 DOM 树各元素的基类; Document 有个子类是 HTMLDocument ,它是整个文档 DOM 树的根结点,这样就明白了:原来 Document 就是描述具体文档的代码,看一下它的头文件,就更明白了,它的属性与方法就是围绕着各种各样的结点: Text, Comment , CDATASection , Element……
2)Page: 我的理解是,Page与webview外部接口相关, page与frame是一对多的关系,同时Frame与FrameView是一一对应的,Frameview关注UI,Page关注数据与接口。现在的浏览器一般都提供同时打开多个窗口,每一个窗口对应的数据就是这个Page在管理了。
在 page.cpp 文件里,还有个重要的全局指针变量: static HashSet
3)FrameView: 可以理解为为一个网页的ViewPort, 它提供一个显示区域,同时包含的有Render根节点、layout排版相关接口、Scroll相关等。FrameView是Layout排版的总入口。
4)RenderView: 与FrameView差不多,只是分工不同,它管理与Render树相关的东东。
5) FrameLoader:重点,FrameLoader类将Documents加载到Frames。当点击一个链接时,FrameLoader创建一个新的处于“policy”状态的DocumentLoader对象,一旦webkit指示FrameLoader将本次加载视为一个导航(navigation),FrameLoader就推动DocumentLoader进入“provisional”状态,(在该状态,DocumentLoader发调用CURL发起一个网络请求,并等待是html还是下载文件。)同时, DocumentLoader会创建一个MainResourceLoader对象(该对象在后面单独介绍)。
6)。DOMWindow: 实现了Dom的一些接口,如CreateNode等。后面可以详细讲讲。
上面介绍的概念比较多,我也不晓得好不好理解,没理解也不怕,多去看看代码,这是必须的。
Webkit的Loader有两条加载数据的主线
1. MainResourceLoader: 该模块主要加载主网页请求。后面称为MainResource。
2. DocLoader: 该模块除了主网页外的所有子请求,如:.js文件,图片资源,.css文件。 后面称为SubResource。
MainResource部分:
FrameLoader->DocumentLoader->MainResourceLoader-ResourceHandle DocumentLoader经历状态:1)"policy" 2) "provisional" 3) "commited"分别是等待、作为navigation发送network request、文件下载完毕
Subresource部分:
DocLoader->Cache->[CacheObjects] CacheImage->SubresourceLoader->ResourceHandle 。当请求一个资源时,首先查看Cache中是否存在该对象,如果存在直接返回;如果不存在,创建该Cache对象(如CacheImage),然后创建一个SubresourceLoader,加载资源。
举例说明:
加载图片时, DocLoader首先询问Cache, 在内存中是否也存在(CachedImage对象),如果已存在,则直接加载,即省了时间又省了流量。如果图片不在Cache中,Cache首先创建一个新的CachedImage对象来代表该图片,然后由CachedImage对象调用Loader对象发起一个网络请求,Loader对象创建SubResourceLoader。后面的流程就一样了,SubResourceLoader也是直接把ResourceHandle打交道的。
接下来跟踪一下Loader发送请求的代码实现:
1. 用户输入URL后,最先调用的接口是:
FrameLoader::load(const ResourceRequest& request)
ResourceRequest包含了:
KURL(处理url的一个类)、setHTTPHeaderField、setHTTPContentType等与HTTP头部相关的函数
2. Load()通过ResourceRequest数据调用createDocumentLoader(request, substituteData)来创建一个DocumentLoader。
3.Load()函数继续给request设置HttpAccept, Cache-Control HTTP头等信息。
4. 设置FrameLoader::checkNavigationPolicy函数进入"Policy"状态。
5.判断该url是否在Cache中等一系列状态判断后,进入DocumentLoader::startLoadingMainResource函数准备加载MainResource。该函数首先会创建调用MainResourceLoader。
6.进入MainResourceLoader::load函数,调用illSendRequest(r, ResourceResponse());做发送请求前的准备。
7.调用PolicyCheck检查policy的状态后,进入 FrameLoader::callContinueLoadAfterNavigationPolicy继续往下走。
8:在MainResourceLoader::loadNow(ResourceRequest& r)函数里创建ResourceHandle,在创建ResourceHandle函数中,调用start函数,start函数把ResourceHandle自已添加到ResourceHandleManager的m_resourceHandleList队列里。
同时,调用m_downloadTimer.startOneShot激活网页请求下载的定时器。(这是个毫秒级的定时器,采用定时器的原因也是为了实现异步的请法)
可以看到m_downloadTimer的定义: Timer
m_downloadTimer是实现的一个定时器模块类,在它的构造函数里已经传入了回调函数的地址:ResourceHandleManager::downloadTimerCallback。
9. 一路返回到Load()函数,并返回到调用源,函数执行完毕。
10. ResourceHandleManager::downloadTimerCallback回调函数被定时器调用。
11. 可以看到downloadTimerCallback函数的代码:
调用libcurl库的接口curl_multi_fdset, curl_multi_perform等查询数据。
webkit应用场景再举例:
用户给出一个 URL (直接输入或者点击链接或者 JavaScript 解析等方式)。然后浏览器外壳调用 FrameLoader 来装载页面。 FrameLoader 首先检查一些条件 (policyCheck()) ,如 URL 是否非空、 URL 是否可达,用户是否取消等等。然后通过 DocumentLoader 启动一个 MainResourceLoader来装载页面。MainResourceLoader 调用 network 模块中的接口来下载页面内容( ResourceHandle ),实际上这里的Resourcehandle 已经是平台相关的内容了,接收到数据以后,会有回调函数,告诉MainResourceLoader 数据已经接收到了。然后一路返回到 FrameLoader 开始调用HTMLTokenizer 解析 HTML 文本。解析过程中,如果遇到 Javascript 脚本的话,也会调用 Javascript 引擎( Webkit 中的 JavascriptCore , chrome 中的 V8 )来解析。数据被解析完了以后,生成了一个一个的 node ,生成 DOM 树和 Render 树,然后通过 FrameLoaderClient 调用外部的壳把内容显示出来。”
Loader 是在WebKit 里面一个很重要的连接器,通过loader 发起IO下载网页,图片等数据,再通过loader发起解析,以及最后的渲染功能。
LIbcurl简单介绍
其实关于Liccurl的介绍最好的是看官方文档:http://curl.haxx.se/ 几乎大部分的信息里面都能够查找到。
在这边简要介绍:
1)跨平台特性,几乎所有平台都可以使用
2)有许多其他语言的包装,如PHP、PYTHON等,也就是很多语言都可以使用libcurl
3)Libcurl的所有接口被设计成线程安全(线程安全的意思是:在多线程之中可以同时调用一个API而不会互相影响,也就是函数可重入),另外要特别注意的是,任何一个libcurl的handle都不应该在多个线程之间共享,另外若使用HTTPS、FTPS需要OpenSSL或GnuTls的支持。
4)支持IPV6,前提是在编译的时候打开相应的选项
5)Libcurl性能很不错,但是若通过其他语言(非C语言)性能会有一定减弱,这和其他语言本身有关系
6)Libcurl提供三种handle:easy_handle、multi_handle、share_handle
easy_handle:为libcurl的最基础部分,所有的操作都是在easy_handle上进行的,比如发送、请求数据都是在其上进行的。如果直接在easy_handle执行操作 curl_easy_perform 函数是阻塞的(即需要等到完成才返回)
multi_handle:libcurl为异步操作提供的接口,允许调用方在一个线程中处理多个操作(就是easy_handle上的操作,注意是单线程下的),内部multi_handle采用堆栈的方式保存多个easy_handle,然后在一个线程中可以同时对多个easy_handle进行处理,multi_handle的执行操作 curl_multi_perform 函数是立即返回的,不会阻塞
share_handle:有时候多个easy_handle需要分享一些信息,比如cookie,当一个连接获取一个新的cookie,就可以将这个cookie共享到所有的连接上
一些应用实例
1、实现cookie共享
1)场景:客户端与服务器之间为了提高传输性能,建立了多个http连接。
服务区为了管理这个客户端的信息要使用一个会话来保存该客户端的一些信息,为了方便将会话信息保存在cookie之中。
当服务器检查到某个http连接没有带cookie或者cookie失效时,会自动设置一个新的cookie。
客户端希望当获取新的cookie时,马上生效到所有到该服务器的http连接上。
2)解决方案:使用libcurl提供的share_handle在多个http连接之间实现共享cookie操作
CURLSH *pShared = curl_share_init( ); ///创建一个share_handle
curl_share_setopt(pShared,CURLSHOPT_SHARE,CURL_LOCK_DATA_COOKIE); ///设置在盖share_handle上共享的cookie
CURL* pCurl = curl_easy_init();
curl_easy_setopt(pCurl, CURLOPT_SHARE, pShared); ///创建easy_handle并设置器share属性
curl_easy_setopt(pCurl,CURLOPT_COOKIEFILE,""); ///设置给easy_handle的连接添加上cookie支持
///可以以同样的方式添加多个easy_handle到该share_handle之中实现cookie共享,然后再easy_handle上执行的操作就能够自动共享cookie
2、实现HEAD请求
1)场景:有时候你想查询服务器某种资源的状态,比如某个文件的属性:修改时间,大小等等,但是并不像具体得到该文件,这时就是HEAD请求出场的时候
2)解决方案:起始使用libcurl很容易实现
主要设置该easy_handle的NOBODY属性即可
curl_easy_setopt(curl,CURLOPT_NOBODY ,1L ); ///告诉libcurl我想发起一个HEAD请求
3、实现断点下载
1)场景:当从服务器下载一个大文件时,可能需要相当长的时间来完成,在这过程中若出现网络超时或者客户端或者服务器宕机的情况时,若恢复时再从头开始下载势必浪费,这时可以使用从断点处下载。
2)解决方案:在HTTP的GET请求之中可以设置range头部告诉服务器要从指定位置取数据,libcurl如下:
curl_easy_setopt(curl, CURLOPT_RANGE,"100-"); //设置了该属性后,发送的GET请求,会有 “Range:100-“ 头部告诉服务器需要100字节后的数据
4、实现断点上传
1)场景:类似断点下载,就是向服务器传输了一部分数据后异常,当服务恢复时就可以使用断点上传
2)解决方案:需要两个步骤来实现断点上传,第一个不使用一个HEAD请求,查询服务器已经保存的该文件大小,服务器应该在HEAD应答的 Content-Length 头部中说明该文件服务器持有的大小,然后客户端在通过一个POST或者PUT请求并且设置 Content_Range 告诉服务器上传的位置。
发起HEAD请求上面已经叙述,这里特别之处在于需要读取服务器对HEAD应答报文的Content_Length部分,需要告诉libcurl你要读取header内容:
curl_easy_setopt(m_pCURL, CURLOPT_NOBODY, 1L); ///告诉libcurl发起HEAD请求
curl_easy_setopt(m_pCURL, CURLOPT_HEADERFUNCTION, pIoReadHeader_cb); ///告诉libcurl要读取应答报文的HEADER,当libcurl收到一个完整的header时就会调用该回调函数,其格式如下:int OnReadHeader(char *ptr, size_t size, size_t nmemb, void *userdata) 只要在回调函数中查看是否包含 Content-Length 字符串然后解析后面的内容即可。
curl_easy_setopt(m_pCURL, CURLOPT_WRITEHEADER, pPrivateData); ///设置私有数据,该数据会被传递到回调函数的 userdata 参数之中
///假如收到服务器返回的Content_Length为100,这时候POST时就只要从100字节开始上传,并且设置Content_Range头部标示出开始位置:
///注意格式: Content_Range:begin-end/size 以这里为例,假如文件大小为1000字节,从100开始那么Content_range应该就是 100-999/1000
curl_easy_setopt(curl, CURLOPT_RANGE, "100-999"); ///设置前面部分 100-999
curl_easy_setopt(curl, CURLOPT_INFILESIZE_LARGE,(curl_off_t)1000); ///设置size部分为1000
然后就可以使用WRITEFUNCTION等函数进行上传了。