Lighttpd源码分析(网络编程技术与工具)6-9章_第1页
Lighttpd源码分析(网络编程技术与工具)6-9章_第2页
Lighttpd源码分析(网络编程技术与工具)6-9章_第3页
Lighttpd源码分析(网络编程技术与工具)6-9章_第4页
Lighttpd源码分析(网络编程技术与工具)6-9章_第5页
已阅读5页,还剩204页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

Lighttpd源码分析(网络编程技术与工具)6-9章目录\h第6章文件状态缓存器\h6.1概述\h6.2ETag知识36、37、38\h6.2.1ETag的定义\h6.2.2ETag的功能\h6.2.3ETag的优势\h6.2.4Lighttpd中ETag的实现\h6.2.5Lighttpd中ETag的使用\h6.3文件状态缓存器\h6.3.1缓存器设计思路\h6.3.2缓存器结构定义\h6.3.3缓存器实现\h6.4本章总结\h第7章配置信息加载\h7.1概述\h7.2配置信息范例与程序加载结果\h7.2.1Lighttpd配置信息的范例\h7.2.2Lighttpd配置信息范例的加载结果\h7.3加载配置信息的源码分析\h7.3.1Lighttpd配置信息存储结构\h7.3.2Lighttpd配置信息加载的函数调用流程\h7.4客户端连接配置信息\h7.4.1条件配置信息缓存存储结构\h7.4.2客户端连接配置信息动态获取\h7.5本章总结\h第8章I/O多路复用技术模型\h8.1概述\h8.2I/O模型基础知识\h8.2.1I/O模型分类介绍\h8.2.2常见I/O多路复用实现技术\h8.3Lighttpd中多路复用技术模型应用\h8.3.1整合多种复用技术模型的数据结构封装\h8.3.2I/O多路复用技术模型的使用\h8.3.3六种I/O多路复用技术模型的实现\h8.4本章总结\h第9章插件链\h9.1概述\h9.2插件内部结构\h9.2.1数据结构\h9.2.2函数接口\h9.3插件组织结构\h9.3.1串链结构\h9.3.2插件组织结构源码分析\h9.4本章总结第6章文件状态缓存器6.1概述在WebServer中,最重要的是保存在服务器里供客户端请求的文件资源,包括静态页面(如HTML文件、文本文件、Word文件等)、动态文件(如PHP、JSP、ASP等)还有图片文件、声音视频多媒体文件等。存在于服务器上的这些文件又是动态变化的,比如某一时刻某文件被删除或被修改,WebServer必须时刻知道这些变化的发生,才能对客户端的请求做出正确的响应。举个例子,客户端请求访问一个服务器上并不存在的文件资料,WebServer必须返回一个错误。WebServer如何知道这个文件不存在呢?WebServer可以去文件系统对应路径查找,但是对每次客户端的请求,WebServer都去磁盘查找,其效率当然不会理想。因此,文件状态缓存器的作用就是将最近客户端请求过的文件资源的信息缓存在内存中,对于客户端的文件资源请求先查询缓存器,因为有了这个设计,WebServer性能也就能有一定的提升。另外,文件状态缓存器里保存的文件资源信息必须是正确的,也就是要和文件系统同步(某文件资源之前还存在,但是此刻却被Web站点管理员删除了,这个变化必须被实时地反映到缓存器中),即文件系统里文件资源的变化必须要及时地更新到文件状态缓存器中。Lighttpd源码里设计的文件状态缓冲器对于这些问题都已经考虑到了,下面就开始解析这部分以及相关内容。本节相关部分源码:base.hetag.hstat_cache.hetag.cstat_cache.c6.2ETag知识36、37、38在开始分析文件状态缓存器源码之前,先来讲解两个Lighttpd源码文件etag.h和etag.c。虽然这两个文件代码量不多,但是其涉及关于HTTP协议中的一个很重要的设计,这个设计应该在所有的成熟WebServer中都有实现,在Lighttpd中也不例外。6.2.1ETag的定义ETag的定义出现在RFC2616文档的14.19ETag一节。原文如下(第一句):TheETagresponse-headerfieldprovidesthecurrentvalueoftheentitytagfortherequestedvariant.(ETag响应头域提供了请求对应变量(variant)的当前实体标签。)我们可以简单地认为ETag是一个与某一时刻的Web资源相关联的记号(token)。ETag存在的意义就在于能够通过比较某Web资源前后两次对应ETag值让Web服务器获知该Web资源在这段间隔时间内是否有变化发生,而具体如何做到这点就看ETag怎么与某一时刻的Web资源去相对应,即如何由某一时刻的Web资源来生成其对应的ETag(RFC2616对ETag并没有具体值的定义,对于Lighttpd里面如何生成ETag,请待后面的源码分析)。6.2.2ETag的功能让Web服务器获知Web资源是否有变化发生有什么作用?答案就是提高Web服务器性能,节约网络带宽。既然在客户端前后两次资源请求之间的这段时间内被请求的Web资源都没有发生变化,Web服务器当然可以直接返回一个状态码告诉客户端:“该资源自上次传送给你之后未发生任何变化,你可以直接使用本地缓存的副本。”从而Web服务器简简单单的一个状态码返回就完成了一次客户端请求。再完整地看一下带有ETag的Web服务器端和客户端之间请求响应流程,如下所示。第一次请求1)客户端发起一个HTTPGET请求(假设请求一个文件A)。2)Web服务器处理请求并返回响应头和文件内容,其中响应头里就包括有ETag(例如"6a3d8e-47c-6ba-587624053"),状态码200。3)客户端收到Web服务器响应,除了展示内容给最终用户外还会将文件内容、ETag等响应信息缓存起来。第二次请求1)客户端第二次发起HTTPGET请求(当然仍然请求的是文件A),此次客户端除了发送常规请求头外,还会发送一个If-None-Match头,其内容就是第一次请求时服务器返回的ETag"6a3d8e-47c-6ba-587624053"。2)Web服务器判断客户端发送过来的ETag和此刻重新计算出来的ETag是否匹配,如果匹配则表示自客户端第一次请求文件A之后,文件A未发生任何有影响的(之所以这么说,看了之后的内容就懂了)变化,因此If-None-Match为False,于是Web服务器直接返回304。3)客户端收到Web服务器304状态码响应,知道Web服务器上的文件A未发生变化,于是继续使用本地缓存。图6-1是利用Lighttpd做Web服务器时某客户端对站点上同一index.html文件请求两次的具体请求响应消息内容,我们可以看到在该客户端第二次请求index.html文件内容时,Lighttpd服务器并没有发送任何实体数据到客户端,而客户端直接使用本地缓存数据。图6-1Lighttpd服务器与支持ETag客户端的通信流程6.2.3ETag的优势在HTTP协议里,与ETag非常类似的另一个头域为Last-Modified头域。Last-Modified头域(结合If-Modified-Since头域使用)也能完成ETag上面介绍的功能,但是ETag更灵活(即ETag提供更灵活的验证模式)。首先,Last-Modified对文件的新旧检查只能到秒一级。如果文件修改非常频繁,比如在秒以下的时间内进行修改,这种修改Last-Modified无法判断。其次,如果文件的更改对用户的查阅影响并不大(比如一些文件周期性的更改,但是它的内容并不改变而仅仅改变修改时间),也没必要把文件重发给客户端,此时Last-Modified无法做到判断,而使用ETag只需不验证修改时间就可以满足需求(像Apache,可由FileETag指令配置ETag值是由文件的inode(索引节点)、大小、最后修改时间之一或它们的组合来确定)。另外,有些服务器并不能精确地得到文件的最后修改时间。当一个请求里ETag和Last-Modified都存在时,只有两者各自判断都满足返回304状态码的情况下,Web服务器才能返回304状态码。另外,因为ETag的生成计算也是需要发费Web服务器时间的,所以在实际的Web服务器使用中需要权衡处理。6.2.4Lighttpd中ETag的实现从上面叙述可知ETag就是一个字符串,因此Lighttpd中并没有关于ETag的结构化定义,直接采用buffer数据结构体来存储ETag。Lighttpd中关于ETag的实现源码也很简单,仅包含三个函数。1.intetag_is_equal(buffer*etag,constchar*matches)该函数(如清单6-1所示)比较一个非空ETag是否与指定字符串相匹配,匹配返回1,否则返回0。清单6-1函数etag_is_equal//etag.c/*比较一个非空ETag是否与指定字符串相匹配,匹配返回1,否则返回0。*/1.intetag_is_equal(buffer*etag,constchar*matches){2.if(etag&&!buffer_is_empty(etag)&&0==strcmp(etag->ptr,matches))return1;3.return0;4.}5.2.intetag_create(buffer*etag,structstat*st,etag_flags_tflags)该函数(如清单6-2所示)用于根据指定的某文件状态信息创建对应的ETag(事实上此处创建的ETag还不是发送到客户端的最终值,其通过散列之后的值才是实际当作ETag响应头域域值发送到客户端)。通过判断参数flags值来获取文件状态参数st内指定的数据信息创建ETag并保存到buffer类型参数etag内隐性传出,函数返回0。etag_create函数将被stat_cache.c源文件内的stat_cache_get_entry函数(本章接下来会分析到)调用,创建的ETag保存在文件状态缓存器内。参数flags的取值如何,可根据函数的被调用情况向后回溯,首先是stat_cache.c源文件函数stat_cache_get_entry内的sce->etag,&(sce->st),con->etag_flags);调用,接着可以在configfile.c源文件内看到:con->etag_flags=(con->conf.etag_use_mtime?ETAG_USE_MTIME:0)|(con->conf.etag_use_inode?ETAG_USE_INODE:0)|(con->conf.etag_use_size?ETAG_USE_SIZE:0);而con->conf.etag_use_mtime、con->conf.etag_use_inode、con->conf.etag_use_size是用户配置信息,对应于用户在Lighttpd配置文件里对etag.use-mtime、etag.use-inode、etag.use-size三个配置项的值设置。因此参数flags的取值如何由用户决定,即用户可以自由指定ETag由Web资源文件的哪些信息生成,这里开发人员提供了三个文件信息(文件最后修改时间、文件结点号、以字节为单位的文件总大小)的组合选择,如果我们觉得还是无法满足特殊的需求则可自行修改该etag_create函数(体现了ETag提供更灵活验证模式的强大)。清单6-2函数etag_create//etag.h6.typedefenum{ETAG_USE_INODE=1,ETAG_USE_MTIME=2,ETAG_USE_SIZE=4}etag_flags_t;7.//etag_create(buffer*etag,structstat*st,etag_flags_tflags){9.if(0==flags)return0;10.11.buffer_reset(etag);12.13.if(flags&ETAG_USE_INODE){14.buffer_append_off_t(etag,st->st_ino);/*st_ino文件的i-node。*/15.buffer_append_string_len(etag,CONST_STR_LEN("-"));16.}17.18.if(flags&ETAG_USE_SIZE){19.buffer_append_off_t(etag,st->st_size);/*st_size文件大小,以字节计算。*/20.buffer_append_string_len(etag,CONST_STR_LEN("-"));21.}22.23.if(flags&ETAG_USE_MTIME){/*st_mtime文件最后一次被修改的时间,一般只有在用mknod、utime和write时才会改变。*/24.buffer_append_long(etag,st->st_mtime);25.}26.27.return0;28.}29.3.intetag_mutate(buffer*mut,buffer*etag)etag_mutate函数(如清单6-3所示)作用就是获取参数etag的hash值,并保存到参数mut中隐性传出。函数局部变量h的uint32_t类型定义出现在库文件stdint.h(/usr/include/stdint.h)中。#ifndef__uint32_t_definedtypedefunsignedintuint32_t;接下来是一个hash计算方法39,将字符串中字符逐个异或(上一个结果左移5位异或上一个结果右移27位再异或下一个字符值得到下一个结果)得到hash值。在实际应用中,该hash方法(或类似hash计算)得到的hash值是很可靠的,即基本上不会出现两个不同字符串得到同一个hash值的情况。清单6-3函数etag_mutate//etag_mutate(buffer*mut,buffer*etag){31.size_ti;32.uint32_th;33.34.for(h=0,i=0;i<etag->used;++i)h=(h<<5)^(h>>27)^(etag->ptr[i]);35.36.buffer_reset(mut);37.buffer_copy_string_len(mut,CONST_STR_LEN("\""));38.buffer_append_long(mut,h);39.buffer_append_string_len(mut,CONST_STR_LEN("\""));40.41.return0;42.}Lighttpd中ETag的使用Lighttpd中对ETag的使用主要是体现在对比客户端请求消息头域里传递的ETag值和请求文件当前计算出来的ETag值,如果两者相等(可能还接合Last-Modified头域)则直接返回304状态码以减少不必要的数据内容发送。下面,我们就来分析这段实现代码,如清单6-4所示。清单6-4函数http_response_handle_http_response_handle_cachable(server*srv,connection*con,buffer*mtime){45./*46.*14.26If-None-Match47.*[...]48.*Ifnoneoftheentitytagsmatch,thentheserverMAYperformthe49.*requestedmethodasiftheIf-None-Matchheaderfielddidnotexist,50.*butMUSTalsoignoreanyIf-Modified-Sinceheaderfield(s)inthe51.*request.Thatis,ifnoentitytagsmatch,thentheserverMUSTNOT52.*returna304(NotModified)response.53.*//*这里的英文注释来之RFC2616第14.26节,其表明如果If-None-Match不匹配,那么服务器就不能返回状态码304响应,即使根据If-Modified-Since头域来看好像应该是要返回304,原因就在于前面曾提到的ETag能够比Last-Modified头域提供更多的验证信息,如果ETag不匹配就表示该请求文件已经被更新过,只不过Last-Modified头域仅根据时间来检测判断不出来而已,因此此时服务器不能返回状态码304响应。*/54./*last-modifiedhandling*//*con->request.http_if_none_match为字符指针(constchar*)类型变量,初始值为NULL,如果某客户端请求包含有If-None-Match头域,则在解析客户端请求信息后,con->request.http_if_none_match指向If-None-Match头域的字符串域值。此处判断为真则表示客户端请求包含有If-None-Match头域。*/55.if(con->request.http_if_none_match){/*ETag匹配?con->physical.etag存放当前服务器上该请求文件对应的ETag值,con->request.http_if_none_match存放客户端请求头域里的ETag值,此处进行匹配比较。*/56.if(etag_is_equal(con->physical.etag,con->request.http_if_none_match)){/*根据RFC2616第14.26节,If-None-Match头域仅适合GET或HEAD请求方法,对于所有其他方法,服务器必须以412(PreconditionFailed,先决条件失败)状态码响应。*/57.if(con->request.http_method==HTTP_METHOD_GET||58.con->request.http_method==HTTP_METHOD_HEAD){59./*checkifetag+last-modified*//*如果ETag+Last-Modified都存在,则需要两者都完全匹配时,服务器才能返回状态码304。*/60.if(con->request.http_if_modified_since){61.size_tused_len;62.char*semicolon;/*根据RFC2616第2.1节,分号后是注释信息,需要截断。*/63.if(NULL==(semicolon=strchr(con->request.http_if_modified_since,';'))){64.used_len=strlen(con->request.http_if_modified_since);65.}else{66.used_len=semicolon-con->request.http_if_modified_since;67.}68.if(0==strncmp(con->request.http_if_modified_since,mtime->ptr,used_len)){/*比较,Last-Modified也匹配,返回304状态码。*/69.con->http_status=304;70.returnHANDLER_FINISHED;71.}else{/*sizeof运算符得到的字符串长度包含末尾的字符'\0',因此此处结果为30。*/72.charbuf[sizeof("Sat,23Jul200521:20:01GMT")];73.time_tt_header,t_file;74.structtmtm;75./*checkifwecansafelycopythestring*/76.if(used_len>=sizeof(buf)){/*请求头域包含的日期字符串长度太长。*/77.log_error_write(srv,__FILE__,__LINE__,"ssdd","DEBUG:Last-Modifiedcheckfailedasthereceivedtimestampwastoolong:",con->request.http_if_modified_since,used_len,sizeof(buf)-1);78.con->http_status=412;79.con->mode=DIRECT;80.returnHANDLER_FINISHED;81.}82.strncpy(buf,con->request.http_if_modified_since,used_len);83.buf[used_len]='\0';/*函数strptime()原型为char*strptime(constchar*s,constchar*format,structtm*tm);,它完成的功能和前面介绍的strftime()函数相反,其将由s所指向的字符串格式(该格式由控制串format指定)时间转换并存储到由参数tm所指向的structtm结构体格式时间。此处转换请求头域内包含的字符串日期和时间。*/84.if(NULL==strptime(buf,"%a,%d%b%Y%H:%M:%SGMT",&tm)){85.con->http_status=412;86.con->mode=DIRECT;87.returnHANDLER_FINISHED;88.}/*利用函数mktime()返回从UTC时间1970年1月1日0时0分0秒到请求头域指定时间所经过的绝对时间秒数,这是个time_t类型(一般等同于longint类型),因此便于下面的时间进行前后比较。*/89.t_header=mktime(&tm);/*计算服务器上请求文件的修改最后修改时间。*/90.strptime(mtime->ptr,"%a,%d%b%Y%H:%M:%SGMT",&tm);91.t_file=mktime(&tm);/*比较,如果服务器上请求文件的最后修改时间戳比请求头域指定的时间戳新,此时不能返回304状态码,因此函数返回GOON退出。*/92.if(t_file>t_header)returnHANDLER_GO_ON;/*比较,如果服务器上请求文件的最后修改时间戳旧于请求头域指定的时间戳,此时返回304状态码。*/93.con->http_status=304;94.returnHANDLER_FINISHED;95.}96.}else{/*只有ETag存在,则只要ETag匹配服务器就直接返回状态码304。*/97.con->http_status=304;98.returnHANDLER_FINISHED;99.}100.}else{/*根据RFC2616第14.26节,If-None-Match头域仅适合GET或HEAD请求方法,对于所有其他方法,服务器必须以412(PreconditionFailed,先决条件失败)状态码响应。*/101.con->http_status=412;102.con->mode=DIRECT;103.returnHANDLER_FINISHED;104.}105.}/*只有Last-Modified存在的情况,和上面部分代码一致(除了处理错误细节方面),在此略过重复注释。*/106.}elseif(con->request.http_if_modified_since){107.size_tused_len;108.char*semicolon;109.if(NULL==(semicolon=strchr(con->request.http_if_modified_since,';'))){110.used_len=strlen(con->request.http_if_modified_since);111.}else{112.used_len=semicolon-con->request.http_if_modified_since;113.}114.if(0==strncmp(con->request.http_if_modified_since,mtime->ptr,used_len)){115.con->http_status=304;116.returnHANDLER_FINISHED;117.}else{118.charbuf[sizeof("Sat,23Jul200521:20:01GMT")];119.time_tt_header,t_file;120.structtmtm;121./*converttotimestamp*/122.if(used_len>=sizeof(buf))returnHANDLER_GO_ON;123.strncpy(buf,con->request.http_if_modified_since,used_len);124.buf[used_len]='\0';125.if(NULL==strptime(buf,"%a,%d%b%Y%H:%M:%SGMT",&tm)){126./**127.*parsingfailed,let'sgetoutofhere128.*/129.log_error_write(srv,__FILE__,__LINE__,"ss",130."strptime()failedon",buf);131.returnHANDLER_GO_ON;132.}133.t_header=mktime(&tm);134.strptime(mtime->ptr,"%a,%d%b%Y%H:%M:%SGMT",&tm);135.t_file=mktime(&tm);136.if(t_file>t_header)returnHANDLER_GO_ON;137.con->http_status=304;138.returnHANDLER_FINISHED;139.}140.}141.returnHANDLER_GO_ON;142.}函数http_response_handle_cachable的执行流程图如图6-2所示,该流程图把同时存在ETag+Last-Modified和只有Last-Modified存在的两种情况进行了重叠,因为它们的代码仅在处理错误发生时有所不同,差别较小,重叠在一起便于理解整个处理过程。图6-2函数http_response_handle_cachable的执行流程6.3文件状态缓存器6.3.1缓存器设计思路文件状态缓存器用于缓存文件状态:将stat()调用得到的结果(以及附属结果比如由当前文件状态生成的ETag、获知的文件类型等)保存在Lighttpd自己构造的缓存器中,在下一次需要获得文件状态时就直接返回缓存的值,这样提高程序执行效率。当然如果外部文件在其状态被缓存后发生了变化(被修改甚至被删除),这种变化应该同步反应到缓存器中去,否则程序从缓存器中获得的值将是错误的。Lighttpd实现源码通过FAM(Filealterationmonitor40)(或称为sgi_fam)的子系统来获取外部文件系统的变化。该子系统由SiliconGraphics开发,它可以替应用程序监视某一些(需要被监视的)文件,当这些(被监视的)文件发生变化(如被修改或被删除等)时通知(这种通知有两种方式,一种是FAM把被监控文件的变化反应在一个文件描述符内,应用程序通过select或epoll等函数调用来获知,另一种是应用程序直接通过FAM库提供的FAMPending函数调用来获知)应用程序,从而使得应用程序可以做出相应的处理(比如Lighttpd应用程序需要的更新文件状态缓存器)。很显然,这比传统的由应用程序自身每隔一段时间去查询各个被监视的文件是否发生变化要高效得多,而且同步更及时。Lighttpd源码里关于缓存器的实现可以叙述如下:1)对于某文件A(假设文件完整路径为"/web/111/a.htm"),Lighttpd应用程序会在首次获取它的文件状态相关信息时将其缓存到缓存器内(数据结构体stat_cache_entry),同时把文件A所在的目录("/web/111/")添加到FAM监控(数据结构体fam_dir_entry)内,并且此时在保存数据的两个数据结构体stat_cache_entry和fam_dir_entry内保存的版本号(stat_cache_entry.dir_version==fam_dir_entry.version)是一致的。2)当某时刻文件A发生了变化,Lighttpd应用程序从FAM子系统那获得这个变化信息,于是改变文件A所在目录对应的版本号(fam_dir_entry.version)。3)Lighttpd应用程序需要再次获得文件A的文件状态相关信息时会先查询缓存器,并且比较文件A和所在目录的版本号,如果一致则表示文件A在这段时间内未发生变化,于是直接从缓存器内获取文件状态相关信息。如果不一致则表示缓存器内的缓存信息不是最新的,需重新获取。上面去掉了很多细节的描述,具体的情况下面的源码分析部分会详细说明。6.3.2缓存器结构定义与文件状态缓存器有关的数据结构定义在base.h头文件内,stat_cache.c也有部分定义,结构如清单6-5所示。清单6-5文件状态缓存器数据结构定义//base.h143.typedefstruct{144.buffer*name;/*保存文件名。*/145.buffer*etag;/*保存文件对应的ETag,因为计算ETag也是需要时间的。*//*保存文件状态信息,程序通过调用lstat()函数或stat()函数来获得文件状态信息。*/146.structstatst;/*time_t存的是1970年1月1日0时0分0秒算起至今的UTC时间所经过的秒数,time_t是longint类型。#ifndef_TIME_T_DEFINEDtypedeflongtime_t;#define_TIME_T_DEFINED#endif*/147.time_tstat_ts;148.149.#ifdefHAVE_LSTAT150.charis_symlink;/*符号链接标记。*/151.#endif152.153.#ifdefHAVE_FAM_H/*FAM相关字段。*/154.intdir_version;/*文件目录版本号。*/155.intdir_ndx;/*索引。*/156.#endif157.158.buffer*content_type;/*文件类型。*/159.}stat_cache_entry;160.161.typedefstruct{/*缓存文件状态信息的节点都插入到这个files伸展树内。*/162.splay_tree*files;/*thenodesofthetreearestat_cache_entry's*/163.buffer*dir_name;/*forbuildingthedirnamefromthefilename*/164.#ifdefHAVE_FAM_H/*记录被监控文件夹的节点都插入到这个dirs伸展树内。*/165.splay_tree*dirs;/*thenodesofthetreearefam_dir_entry*/166.FAMConnection*fam;167.intfam_fcce_ndx;168.#endif169.buffer*hash_key;/*temp-storeforthehash-key*/170.}stat_cache;171.//stat_cache.c172./*173.*stat-cache174.*wecachethestat()callsinourownstorage175.*thedirectoriesarecachedinFAM176.*ifwegetachange-eventfromFAM,weincrementtheversionintheFAM->dirmapping177.*ifthestat()-cacheisqueriedwecheckiftheversionidforthedirectoryisthe178.*sameandreturnimmediatly.179.*Whatweneed:180.*-foreachstat-cacheentryweneedafastindirectlookuponthedirectoryname181.*-foreachFAMRequestwehavetofindtheversioninthedirectorycache(indexasuserdata)182.*stat<<->directory<->FAMRequest183.*iffileisdeleted,directoryisdirty,fileisrechecked...184.*ifdirectoryisdeleted,directorymappingisremoved185.**/186.187.#ifdefHAVE_FAM_H188.typedefstruct{189.FAMRequest*req;190.FAMConnection*fc;191.buffer*name;192.intversion;/*文件目录版本号。*/193.}fam_dir_entry;194.#endif195.196./*thedirectorynameistoolongtoalwayscompareonit197.*-weneedahash198.*-thehash-keyisusedassortingcriteriaforatree199.*-asplay-treeisusedaswecanusethecachingeffectofit200.*/201./*wewanttocleanupthestat-cacheeveryfewseconds,let'ssay10202.*203.*-removeentrieswhichareoutdatedsince30s204.*-removeentrieswhicharefreshbuthavn'tbeenusedsince60s205.*-ifwedon'thaveastat-cacheentryforadirectory,releaseitfromthemonitor206.*/207.#ifdefDEBUG_STAT_CACHE208.typedefstruct{209.int*ptr;210.size_tused;211.size_tsize;212.}fake_keys;213.staticfake_keysctrl;214.#endif这几个结构体的字段都不多,而且源代码里有Lighttpd开发者的详细英文注释(Lighttpd开发者关于缓存器的注解也列出来了,方便读者理解。),如果将它们放在函数代码里再看就更容易理解了。6.3.3缓存器实现前面讲了文件状态缓存器的数据结构设计,其相关功能设计在stat_cache.c源文件内得以实现,下面就来分析这个文件,将其具体的实现细节介绍给读者。1.stat_cache*stat_cache_init(void)、voidstat_cache_free(stat_cache*sc)stat_cache结构体的初始化和释放操作函数,如清单6-6所示。清单6-6函数stat_cache_init、stat_cache_free//stat_cache.c215.stat_cache*stat_cache_init(void){216.stat_cache*fc=NULL;217.218.fc=calloc(1,sizeof(*fc));219.220.fc->dir_name=buffer_init();221.fc->hash_key=buffer_init();222.#ifdefHAVE_FAM_H223.fc->fam=calloc(1,sizeof(*fc->fam));224.#endif225.226.#ifdefDEBUG_STAT_CACHE227.ctrl.size=0;228.#endif229.230.returnfc;231.}232.233.voidstat_cache_free(stat_cache*sc){234.while(sc->files){235.intosize;236.splay_tree*node=sc->files;237.238.osize=sc->files->size;239.240.stat_cache_entry_free(node->data);/*先释放数据data空间。*//*再释放根节点,并返回新根节点以便继续循环释放。*/241.sc->files=splaytree_delete(sc->files,node->key);242.243.assert(osize-1==splaytree_size(sc->files));244.}245.246.buffer_free(sc->dir_name);247.buffer_free(sc->hash_key);248.249.#ifdefHAVE_FAM_H250.while(sc->dirs){251.intosize;252.splay_tree*node=sc->dirs;253.254.osize=sc->dirs->size;255.256.fam_dir_entry_free(node->data);257.sc->dirs=splaytree_delete(sc->dirs,node->key);258.259.if(osize==1){260.assert(NULL==sc->dirs);261.}else{262.assert(osize==(sc->dirs->size+1));263.}264.}265.if(sc->fam){/*函数FAMClose原型:intFAMClose(FAMConnection*fc);该函数关闭到FAM的连接,执行成功返回0,失败返回-1。*/266.267.FAMClose(sc->fam);268.free(sc->fam);269.}270.#endif271.free(sc);272.}2.staticstat_cache_entry*stat_cache_entry_init(void)、staticvoidstat_cache_entry_free(void*data)stat_cache_entry结构体的初始化和释放操作函数如清单6-7所示。清单6-7函数stat_cache_entry_init、stat_cache_entry_free//stat_cache.c273.staticstat_cache_entry*stat_cache_entry_init(void){274.stat_cache_entry*sce=NULL;275.276.sce=calloc(1,sizeof(*sce));277.278.sce->name=buffer_init();279.sce->etag=buffer_init();280.sce->content_type=buffer_init();281.282.returnsce;283.}284.285.staticvoidstat_cache_entry_free(void*data){286.stat_cache_entry*sce=data;287.if(!sce)return;288.289.buffer_free(sce->etag);290.buffer_free(sce->name);291.buffer_free(sce->content_type);292.293.free(sce);294.}295.3.staticfam_dir_entry*fam_dir_entry_init(void)、staticvoidfam_dir_entry_free(void*data)fam_dir_entry结构体的初始化和释放操作函数如清单6-8所示。清单6-8函数fam_dir_entry_init、fam_dir_entry_free//stat_cache.c296.#ifdefHAVE_FAM_H297.staticfam_dir_entry*fam_dir_entry_init(void){298.fam_dir_entry*fam_dir=NULL;299.300.fam_dir=calloc(1,sizeof(*fam_dir));301.302.fam_dir->name=buffer_init();303.304.returnfam_dir;305.}306.307.staticvoidfam_dir_entry_free(void*data){308.fam_dir_entry*fam_dir=data;309.310.if(!fam_dir)return;/*函数FAMCancelMonitor原型:intFAMCancelMonitor(FAMConnection*fc,FAMRequest*fr);该函数取消对fr指定的文件或文件夹的监视,执行成功返回0,失败返回-1。*/311.FAMCancelMonitor(fam_dir->fc,fam_dir->req);312.313.buffer_free(fam_dir->name);314.free(fam_dir->req);315.316.free(fam_dir);317.}318.#endif319.4.staticintstat_cache_attr_get(buffer*buf,char*name)该函数(如清单6-9所示)获取指定路径文件的文件类型。函数attr_get属于XFS41(Thexattrextensionallowsforthemanipulationofextendedattributesonafilesystem),用于获取文件的扩展属性(关于这方面的更多知识,读者可以查阅注解里提供的网站)。清单6-9函数stat_cache_attr_get//stat_cache.c320.#ifdefHAVE_XATTR321.staticintstat_cache_attr_get(buffer*buf,char*name){322.intattrlen;323.intret;324.325.attrlen=1024;326.buffer_prepare_copy(buf,attrlen);327.attrlen--;328.if(0==(ret=attr_get(name,"Content-Type",buf->ptr,&attrlen,0))){329.buf->used=attrlen+1;330.buf->ptr[attrlen]='\0';331.}332.returnret;333.}334.#endif335.5.staticuint32_thashme(buffer*str)该函数(如清单6-10所示)获取对应字符串(保存在结构体buffer内)的hash值。该函数采用的是非常流行的DJB42hashfunction,俗称"Times33"算法。Times33算法其实很简单,就是不断地乘33。这里是将hash左移5位再加上hash(即hash*32+hash,也就是乘33)。因为Time33在效率和随机性方面都俱佳,所以几乎所有流行的HashMap都采用了它。清单6-10函数hashme//stat_cache.c336./*thefamousDJBhashfunctionforstrings*/337.staticuint32_thashme(buffer*str){338.uint32_thash=5381;339.constchar*s;340.for(s=str->ptr;*s;s++){341.hash=((hash<<5)+hash)+*s;342.}343.344.hash&=~(1<<31);/*stripthehighestbit*/345.346.returnhash;347.}348.6.handler_tstat_cache_handle_fdevent(void*_srv,void*_fce,intrevent)、staticintbuffer_copy_dirname(buffer*dst,buffer*file)前面说了,FAM可以把被监控文件的变化反映在一个文件描述符内使得应用程序通过select或epoll等函数调用来获知这种变化。Lighttpd里正是利用这种方法,将对外部文件变化事件的监控和客户端的访问请求事件的监控统一管理。函数stat_cache_handle_fdevent主要将在外部被监控文件发生变化时(还有Lighttpd断开和FAM的连接时)被调用,用来对外部文件变化事件做相应处理。该函数最新出现在server.c源文件内,用于事件监控处理函数注册,源码如清单6-11所示。清单6-11事件监控处理函数注册349.#ifdefHAVE_FAM_H350./*setupFAM*/351.if(srv->srvconf.stat_cache_engine==STAT_CACHE_ENGINE_FAM){352.if(0!=FAMOpen2(srv->stat_cache->fam,"lighttpd")){353.log_error_write(srv,__FILE__,__LINE__,"s",354."couldnotopenafamconnection,dieing.");355.return-1;356.}357.#ifdefHAVE_FAMNOEXISTS/*FAMNoExists函数是Gamin里新扩展的函数。Gamin是Gnome里的一个项目,实现的也是FAM同样的功能。Gamin项目页里提及说Gamin和FAM2.6.8在二进制和源代码级兼容,只有一点点不同与一个有意义的扩展。此处FAMNoExists函数就是这个有意义的扩展,但是其是否被调用都不影响FAM主要功能。具体内容可以参阅注解提供的网站43。*/358.FAMNoExists(srv->stat_cache->fam);359.#endif/*FAMCONNECTION_GETFD是一个宏,用于从FAMConnection结构体中获取FAM连接的文件描述符。*/360.srv->stat_cache->fam_fcce_ndx=-1;361.fdevent_register(srv->ev,FAMCONNECTION_GETFD(srv->stat_cache->fam),stat_cache_handle_fdevent,NULL);362.fdevent_event_add(srv->ev,&(srv->stat_cache->fam_fcce_ndx),FAMCONNECTION_GETFD(srv->stat_cache->fam),FDEVENT_IN);363.}364.#endif在FAM提供了两个函数(函数FAMOpen44和函数FAMOpen2)用于打开一个到FAM服务器的连接,这两个函数差别很小,仅函数FAMOpen2比FAMOpen多了第二个参数(保存应用程序名称),因此可以告诉FAM服务器更多的一点FAM客户端信息。函数打开连接成功则返回0,否则返回-1。函数原型如清单6-12所示,其中的FAMConnection参数将在FAMOpen(FAMOpen2)成功执行过程中被初始化,在之后的所有FAM函数调用过程中都将使用到这个被初始化的结构体变量。清单6-12函数FAMOpen、FAMOpen2原型365.intFAMOpen(FAMConnection*fc);366.intFAMOpen2(FAMConnection*fc,constchar*appName);367.接下来就是利用fdevent_register和fdevent_event_add将FAM文件描述符加入到多路I/O复用监控(监控FDEVENT_IN事件,这个具体过程后面章节详细讲解)里。如此之后,只要FAM监控的文件有变化发生导致FAM文件描述符有事件(FAMevent)发生,应用程序通过Select或Epoll等获知发生事件,从而调用此处注册的stat_cache_handle_fdevent函数进行处理。回过来再看stat_cache_handle_fdevent函数。函数对FAM通知过来的事件做分别处理,如果是可读事件,表示外部文件有变化,于是更新缓存器内容,如果是挂断事件则注销事件、关闭连接、释放内存,最后返回HANDLER_GO_ON标志继续程序的执行。读者在第一次阅读理解这个函数有困难时,请先阅读后面即将讲到的另一个重要函数stat_cache_get_entry,如清单6-13所示。清单6-13函数stat_cache_get_entry368.//stat_cache.c369.#ifdefHAVE_FAM_H/*handler_t是个枚举类型结构体,定义在settings.h头文件内,在后面的Lighttpd插件分析章节再详细讲解此数据结构。typedefenum{HANDLER_UNSET,HANDLER_GO_ON,HANDLER_FINISHED,HANDLER_COMEBACK,HANDLER_WAIT_FOR_EVENT,HANDLER_ERROR,HANDLER_WAIT_FOR_FD}handler_t;*/370.handler_tstat_cache_handle_fdevent(void*_srv,void*_fce,intrevent){371.size_ti;372.server*srv=_srv;373.stat_cache*sc=srv->stat_cache;374.size_tevents;375.376.UNUSED(_fce);377./**//*可读事件发生。*/378.if((revent&FDEVENT_IN)&&379.sc->fam){/*获取可读事件数目。*/380.events=FAMPending(sc->fam);/*函数FAMPending原型:intFAMPending(FAMConnection*fc);该函数返回正整数表示有FAMevent在队列中,返回0表示没有事件发生,-1表示发生错误。该函数调用后马上返回,即不阻塞等待事件发生。函数FAMNextEvent原型:intFAMNextEvent(FAMConnection*fc,FAMEvent*fe);该函数执行成功返回0,返回-1表示发生错误。*//*对各个可读事件分别进行处理。*/381.for(i=0;i<events;i++){382.FAMEventfe;383.fam_dir_entry*fam_dir;384.splay_tree*node;385.intndx,j;/*FAMNextEvent函数将逐个把事件信息存储到结构体FAMEvent参数fe内隐性传出。typedefstruct{FAMConnection*fc;FAMRequestfr;char*hostname;charfilename[PATH_MAX];void*userdata;FAMCodescode;}FAMEvent;fc是通过函数FAMOpen或FAMOpen2初始化的。fr是通过函数FAMMonitorFile()或FAMMonitorDirectory()调用初始化的(这两个函数后面会讲到)。hostname现在已经不使用了,一般不要用它。filename(发生改变的)被监控文件或文件夹的完整路径或是被监控目录下的文件名。userdata可以指向任何数据,因此提供了从事件监控设置函数到事件发生处理函数之间数据传递方法。code是枚举结构FAMCodes中的一个取值,表示当前发生的是哪个事件(比如,FAMChanged、FAMDeleted)、FAMCreated、FAMMoved等。*/386.FAMNextEvent(sc->fam,&fe);387.388./*handleevent*/389.390.switch(fe.code){391.caseFAMChanged:392.caseFAMDeleted:393.caseFAMMoved:394./*ifthefilenameisadirectoryremovetheentry*//*如果文件名所指的是目录(并且事件是FAMDeleted或FAMMoved)则移除相应节点。获得用户数据,该数据从函数stat_cache_get_entry内传递过来。*/395.fam_dir=fe.userdata;396.fam_dir->version++;/*更新版本号,表示有文件发生变化。*/397.398./*file/dirisstillhere*//*只是目录文件状态改变事件则跳出,不用进行接下来的节点删除操作。*/399.if(fe.code==FAMChanged)break;400.401./*wehave2versions,followandno-follow-symlink*//*有两种情况(followandno-follow-symlink),但是并不知道是哪种情况,因此对这种情况的删除操作都要尝试进行,结果最多就是某一次尝试失败(即找不到那个节点,if判断为假,表示不是这种情况)。*/402.for(j=0;j<2;j++){403.buffer_copy_string(sc->hash_key,fe.filename);/*获取发生改变的文件名。*/404.buffer_append_long(sc->hash_key,j);405.406.ndx=hashme(sc->hash_key);/*如果不是目录移动、删除事件,则这里的伸展操作应该是找不到节点的。*/407.sc->dirs=splaytree_splay(sc->dirs,ndx);408.node=sc->dirs;409.410.if(node&&(node->key==ndx)){411.intosize=splaytree_size(sc->dirs);412.413.fam_dir_entry_free(node->data);414.sc->dirs=splaytree_delete(sc->dirs,ndx);415.416.assert(osize-1==splaytree_size(sc->dirs));417.}418.}419.break;420.default:421.break;422.}423.}424.}425.426.if(revent&FDEVENT_HUP){427./*famclosedtheconnection*/428.srv->stat_cache->fam_fcce_ndx=-1;429.430.fdevent_event_del(srv->ev,&(sc->fam_fcce_ndx),FAMCONNECTION_GETFD(sc->fam));431.fdevent_unregister(srv->ev,FAMCONNECTION_GETFD(sc->fam));432.433.FAMClose(sc->fam);434.free(sc->fam);435.436.sc->fam=NULL;437.}438.439.returnHANDLER_GO_ON;440.}/*获取指定文件所在的父目录路径。*/441.staticintbuffer_copy_dirname(buffer*dst,buffer*file){442.size_ti;443.444.if(buffer_is_empty(file))return-1;445.446.for(i=file->used-1;i+1>0;i--){447.if(file->ptr[i]=='/'){448.buffer_copy_string_len(dst,file->ptr,i);449.return0;450.}451.}452.453.return-1;454.}455.#endif7.staticintstat_cache_lstat(server*srv,buffer*dname,structstat*lst)该函数(如清单6-14所示)检查dname是否为符号链接,是返回0,不是返回1,如果出错返回-1。另外,调用lstat函数获得的文件状态信息放入参数lst中隐性传出。清单6-14函数stat_cache_lstat//stat_cache.c456.#ifdefHAVE_LSTAT457.staticintstat_cache_lstat(server*srv,buffer*dname,structstat*lst){458.if(lstat(dname->ptr,lst)==0){459.returnS_ISLNK(lst->st_mode)?0:1;460.}461.else{462.log_error_write(srv,__FILE__,__LINE__,"sbs","lstatfailedfor:",dname,strerror(errno));463.};464.return-1;465.}466.#endif467.8.handler_tstat_cache_get_entry(server*srv,connection*con,buffer*name,stat_cache_entry**ret_sce)函数stat_cache_get_entry(如清单6-15所示)是缓存器实现的重点。函数首先声明了一些必要的临时变量并进行了初始化,接着将字符串形式(存储在buffer结构体内)的参数name(待查文件完整路径)通过Hash转换成整型索引值,并在当前工作进程的文件状态缓存器中查找(文件状态缓存器以伸展树结构存储,所以节点查找是通过对伸展树的伸展访问进行)。需要注意的是,在伸展树中查找到待查文件记录节点(以下简称为记录节点)仍不能保证该记录节点就是程序原本想要的那个,程序是通过name参数Hash转换得到整型索引值查找的。如果两个不同的name字符串得到同一个整型索引值会怎么样呢?(这种可能当然存在,就算Times33算法随机性再好也不能保证完全没有冲突的发生)。所以程序仍需要对查找到的记录节点内保存的name字符串和函数原本的参数name进行对比判断,只有它们两个也相等时才表示查找成功。查找成功意味着待查文件状态就缓存在文件状态缓存器中,如果该记录节点又是最新的(即记录节点时间戳和当前工作进程时间戳一致)则直接使用即可,无须再次调用stat()函数从而提高效率(这就是文件状态缓存器的关键作用),将结果存入指针参数ret_sce中,并返回执行成功的状态码HANDLER_GO_ON。上面提及的是一切都进行得非常顺利的情况,再来看看不顺利的情况下程序执行流程会怎样,继续往下看。程序接着判断待查文件是否记录有变化(通过比较记录节点与待查文件所在目录的监控节点各自的版本号,如果相等则表示待查文件没有变化),如果没

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论