版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
goaheadwebserver 源码分析1.一个txt文本架构图main()卜-websOpenServer()|--websOpenListen()卜-socketOpenConnection()卜-打开webServer服务器|--初化socket_t结构(注册websAccept()回调函数(socket_tsp->accept=websAccept)等)|--把socket_t结构加入数组socketList卜-websUrlHandlerDefine()| |--初始化websUrlHandlerType结构的websUrlHandler数组| |--将urlPrefix和回调函数绑定在websUrlHandler[websUrlHandlerMax]中|卜-websUrlHandlerDefine(websDefaultHandler)| |--初始化websUrlHandlerType结构的websUrlHandler数组| |--将urlPrefix和回调函数绑定在websUrlHandler[websUrlHandlerMax]中II|卜-websFormDefine()| |--初始化symboltable结构sym_t,把名字和回调函数名放进sym_t结构| |--把sym_t结构放进hash表中|卜-websAspDefine()| |--初始化symboltable结构sym_t,把名字和回调函数名放进sym_t结构| |--把sym_t结构放进hash表中|||(mainloop)|--socketReady(-1)||socketSelect(-1,1000)A |--轮询socketList |--轮询socketList中的handlerMask卜-中的几个变量|--卜-中的几个变量|--改变socketList中的currentEvents|卜-socketProcess()A| |--轮询socketList[]|| |--socketReady()|| 卜-socketDoEvent()I| 卜-如果有新的连接(来自listenfd)就调用socketAccept()|| |--调用socketAlloc()初始化socket_t结构|| |--把socket_t结构加入socketList数组|| |--调用socket_tsp->accept()回调函数II|| |--如果不是新的连接就查找socketList数组调用socket_tsp->handler()回调函数IIwebsAccept()卜-做一些检查卜-socketCreateHandler(sid,SOCKET_READABLE,websSocketEvent,(int)wp)| |--把sid注册为读事件,初始化socket_tsp->handler=websSocketEvent等,更新对应的socketList数组(handlerMask值等)websSocketEvent()|--判断读写操作卜-读websReadEvent()| 卜-websUrlHandlerRequest()| |--查找wbsUrlHandler数组,调用和urlPrefix对应的回调函数(websFormHandler(),websDefaultHandler()等)||--写,调用(wp->writeSocket)回调函数websFormHandler()|--跟据formName查找hash表,调用用户定义的函数websDefaultHandler()I--处理默认的URL请求,包括asp页面卜-websSetRequestSocketHandler()I I--注册默认的写事件函数wp->writeSocket=websDefaultWriteEventI|--socketCreateHandler(wp->sid,SOCKET_WRITABLE,websSocketEvent,(int)wp)I I--把sid注册为写事件,初始化socket_tsp->handler=websSocketEvent等,更新对应的socketList数组websDefaultWriteEvent()II--写数据,不包括asp页面2跟着main走Main函数很简短,所以可以对他的代码进行一行一行注释,如下:intmain(intargc,char**argv){/*Initializethememoryallocator.Allowuseofmallocandstartwitha60Kheap.Foreachpagerequestapprox8KBisallocated.60KBallowsforseveralconcurrentpagerequests.Ifmorespaceisrequired,mallocwillbeusedfortheoverflow.*//*首先分配一个大的内存块(60*1024字节),以后只要是以b开头的对内存操作的函数都是在这个已经分好的内存块上的操作,这些操作在Balloc.c中实现。*/bopen(NULL,(60*1024),B_USE_MALLOC);/*忽略SIGPIPE信号*/signal(SIGPIPE,SIG_IGN);/*Initializethewebserver初始化用户管理部分,打开web服务器,注册URL处理函数。用户管理部分在um.c中实现,Web服务器的初始化是在default.c和webs.c中实现url处理函数在handler.c中实现*/if(initWebs()<0){return-1;}/*初始化Ssl认证部分注:在这个文档中对ssl认证不做研究*/#ifdefWEBS_SSL_SUPPORTwebsSSLOpen();#endif/*Basiceventloop.SocketReadyreturnstruewhenasocketisreadyforservice.SocketSelectwillblockuntilaneventoccurs.SocketProcesswillactuallydotheservicing.*//*主循环*/while(!finished){/*socketReady()函数检查是否有准备好的sock事件socketSelect()函数首先把各个sock感兴趣的事件(sp->handlerMask)注册给三个集合(读,写,例外),然后调用select系统调用,然后更新各个sock的sp->currentEvents,表示各个sock的当前状态。这两个函数在sockGen.c中实现,他们主要操作的数据是socket」变量socketList中的handlerMask和currentEvents,socketList在sock.c中定义并主要由该文件中的socketAlloc,socketFree和socketPtr三个函数维护。*/if(socketReady(-1)||socketSelect(-1,1000)){/*该函数处理具体的sock事件1,调用socketReady(sid)对socketList[sid]进行检查,看是否有sock事件2,如果有sock事件,则调用socketDoEvent()函数,对事件进行处理*/socketProcess(-1);}/*该函数在cgi.c中实现,检查cgiRec变量cgilist,首先把cgi的结果输出,如果有的话,然后看cgi进程是否已对号束,如果结束,就清理该cgi进程。Cgilist在函数websCgiHandler和websCgiCleanup中维护。*/websCgiCleanup();/*该函数在websuemf.c中实现,功能是检查sched_t变量sched,断开超时的连接,sched变量在emfSchedCallback和emfUnschedCallback中维护*/emfSchedProcess();}/*退出时的清理工作,永远不会执行到这里*/#ifdefWEBS_SSL_SUPPORTwebsSSLClose();#endif#ifdefUSER_MANAGEMENT_SUPPORTumClose();#endif/*Closethesocketmodule,reportmemoryleaksandclosethememoryallocator*/websCloseServer();socketClose();#ifdefB_STATSmemLeaks();#endifbclose();return0;}3.—些想法找出他们共同的数据结构找出对这些数据结构维护(操作)的函数从http的get或者是post流程来看程序4, 整体架构如何掌握5, 分模块,从全局的角度看各个模块的功能6,从main函数起,按树型结构一层层分析下去选择第五种方法:1, sock模块,专门处理网络链接这一块,有这么几个文件:sock.c和sockGen.c,sock.c是(维护)处理链接的socket_t数据结构,sockGen.c是(维护)处理链接的。2, 对http协议数据进行操作(读取和分析),webc.c文件3,对具体数据的操作(aspform…),handler.c文件选择第三种方法来看程序:假设有个http请求:从这个http请求到服务器的处理,然后返回这样一个过程来看goahead是怎么操作的?1,写一个http请求的url和一个head1,写一个http请求的post的head注:因为这次要看通整个goahead代码,所以一下子不知道以什么思路来看。上面是一些想法,不知道从哪里开始分析一个项目的代码,也不知道取舍哪些进行程序结构和功能方面的分析。后来的结果是写出了下面的文字。4.goaheadmainloop源码分析socketReady(-l)函数分析socketReady函数检查已建立连接的socket中是否有以下事件,如果检查到一个,就返回1,如果没有检查到,就返回零。sp->flags&SOCKET_CONNRESET,如果该socket的flag标志为SOCKET_CONNRESET(该标志在哪里设置(初始化)的?),则调用函数socketCloseConnection(该函数后面会解释)关闭该socket连接,然后返回0;sp->currentEvents&sp->handlerMask,如果该socket当前的事件和他要处理的事件相同,就返回1,告诉调用socketReady的函数有socket准备好被处理了;sp->handlerMask&SOCKET_READABLE&&socketInputBuffered(sid)>0,如果该socket要处理的事件是SOCKET_READABLE并且该socket的缓存中有可读的数据,则调用socketSelect函数(为什么在这里要调用这个函数,看了下socketSelect,应该是为了设置sp->currentEvents|=SOCKET_READABLE,所以这里应可以优化),然后返回1,告诉调用socketReady的函数有socket准备好被处理了;socketReady函数根据传入的参数sid决定是检查id为sid的socket(当sid大于0),还是遍历整个socketList(当sid小于0),如果以上3个条件中没有一个满足,则返回0。socketSelect(-1,1000)函数分析socketSelect函数是系统调用select的外包函数,该函数的主要功能就是监听(?)注册的socket事件集合,然后修改sp->currentEvents变量。流程如下:
在主函数中,对socketSelect的调用是这样的:if(socketReady(-l)||socketSelect(-1,1000))这样做并没有对socketSelect的返回值进行检查,也就是说当socketSelect返回-1时,该条件也会满足,从而程序也会往下走,所以,这个地方也是可以优化的。socketProcess(-l)函数分析socketProcess处理到达的socket事件,如果传入的参数是小于0,则会处理所有的socket的事件,如果大于0,则会处理指定的socket的事件。下面是主要过程:if(socketReady(sid)){socketDoEvent(sp);socketReady()函数请看上面的解释,但不明白这里为什么还要用到这个函数,应该也是个可以优化的地方,我现在想到一个过程,应该是这样的:lf(socketSelect()){/*lf(socketReady())socketDoEvent();*/socketProcess();}后注:走完websGetlnput()函数的分析后,因为这时仔细看到了更多的代码,上面的这个优化是不行的,因为socketReady(intsid)函数中,是sp->currentEvents,sp->handlerMask这几个标志位来判断是否有数据读写。socketDoEvent()是对已连接的socket通过改变sp->currentEvents和sp->handlerMask来分阶段的去处理数据,并不是一路执行到底直到把这个连接关闭的。socketSelect()是主要还是用在有新连接到来的时候,有新连接到来才会使这个函数返回真。socketDoEvent大致分两个阶段去处理一个连接,1是READ阶段,READ处理成功,便会设置状态到WRITE阶段,却不执行WRITE动作,2是WRITE阶段,WRITE执行完后才会结束这个连接。当第一次主循环时,socketDoEvent()执行的是READ,所以,如果按上一个代码段,第二次执行循环时,如socketSelect()中没有新连接或数据到来,就不会往下执行了,而已有数据的连接将得不到立即的处理。socketReady(sid)可以检查已有连接是否有数据准备好读写,所以在这里优化是错误的。下面看看socketDoEvent函数的实现:
(1)socketDoEvent函数首先对socket的当前事件进行检查,如果是读事件并且是服务器监听socket上的读事件,说明有新连接到来,于是调用socketAccept()欢迎新连接,并使currentEvents为0,然后马上返回。如果当前不是读事件但是该socket原感兴趣的是读事件并且socket缓存中确有数据可读,那就置currentEvents为可读,这一步在socketReady函数中有做过,所以这里应该是可以去掉的。如果当前是写事件,那就看看该socket的写缓存中有没有数据,如果有并且有SOCKET_FLUSHING标志就全部输出该写缓存,这是为新的写事件做清理工作。调用事件处理函数sp->handler,该函数指针分别在两个地方进行初始化:1,在websDefaultHandler()函数中注册写事件,该函数在什么时候被调?
2,在websAccept()函数中注册读事件两处都指向websSocketEvent()函数。等下解释这个函数。(5)把currentEvents置为0。socketAccept()函数分析socketAccept()函数接收一个新的连接,并且调用用户注册的接收函数,这一个过程后,就把对socketAccept()函数接收一个新的连接,并且调用用户注册的接收函数,这一个过程后,就把对socket_t结构的处理转换到了webs_t结构。Sp->accept函数指针在socketOpenConnection()函数中调用socketAlloc()函数注册给监听socket的socket_t数据结构,当socketAccept()函数处理新连接时,就会把自已的Sp->accept扌旨针及其他几个属性通过调用socketAlloc(sp->host,sp->port,sp->accept,
sp->flags)函数又给了新的连接,注意,调用完这个后,会更新新的nsp->flags&=~SOCKET_LISTENING,把该连接和监听连接区别开。然后,监听socket用自已的Sp->accept调用到websAccept()函数,但是传递的第一个参数是新连接的id:nid。这也应该是个技巧吧。websAccept()函数功能:池Iport'池Iport'【认疋门£讪}I.OCXIR1:QULST如製ipacid"上和也评盘杓电LHL访询.则仪轻口n->flagn\\I-:BSiI.C1CA1btE':QUk:S'i/snck«[C:e;i[ellibti^3ed讣d,S(X細IKLAIJAFilJ?.webs$CKki:tTArtiiE.迺也经过websAccept()函数后,将走出socket层,来到webs_t结构层,以后对读和写的操作都通过操作webs_t这个数据结构来完成。websSocketEvent()函数分析websSocketEvent()函数处理socket的读和写事件:
(1)websSocketEvent()函数根据mask决定调用读还是写函数,wp->writeSocket函数指针在websDefaultHandler()中通过调用websSetRequestSocketHandler()进行注册,指向websDefaultWriteEvent()函数。(2)websReadEvent()函数:websReadEvent()函数处理读事件,我们怀疑这个函数有问题,会导致web服务器宕机,因此要重点分析。
Accept:image/gif,image/x-xbitmap,image/jpeg,image/pjpeg,application/x-shockwave-flash,application/vnd.ms-excel,application/vnd.ms-powerpoint,application/msword,*/*Referer:0/forms.aspAccept-Language:zh-cnContent-Type:application/x-www-form-urlencodedAccept-Encoding:gzip,deflateUser-Agent:Mozilla/4.0(compatible;MSIE6.0;WindowsNT5.1;SV1;.NETCLR2.0.50727;CIBA)Host:0Content-Length:32Connection:Keep-AliveCache-Control:no-cachename=adfs&address=fdsafdsa&ok=OK(1)WEBS_BEGIN,该状态是在websAccept()函数调用wbesAlloc()函数初始化wp结构时被设置的,如果是在这个状态,该函数就调用websParseFirst()函数分析http头的第一行数据,在websParseFirst()中,主要会对wp结构的下面几个数据进行操作:wp->flags|=WEBS_POST_REQUEST;wp->query=bstrdup(B_L,query);wp->host=bstrdup(B_L,host);wp->path=bstrdup(B_L,path);wp->protocol=bstrdup(B_L,proto);wp->protoVersion=bstrdup(B_L,protoVer);对wp->flags几个重要的赋值:WEBS_CGI_REQUEST,WEBS_ASP,WEBS_LOCAL_PAGE等,分析好第一行后,调用ringqFlush()函数把wp->header做相当于清零的操作,把各个指针都指向这个缓冲的开始处。如果websParseFirst()函数调用成功,就转到WEBS_HEADER状态:wp->state=WEBS_HEADER。按程序的流程,这个时候仍在循环中,马上就会转到WEBS_HEADER状态机,但是会这样吗?(2)WEBS_HEADER,首先(1)以后(经过WEBS_BEGIN),除非websGetlnput()()函数调用不成功,否则就会执行到WEBS_HEADER状态机,这个时候我们就要看看websGetInput()函数了,因为这个函数如果调用不成功,是不会执行到WEBS_HEADER的。websGetInput()函数分析:函数功能:接收客户端的数据;函数返回值:-1,表示出错或者请求已经被处理0,表示告诉调用者还有更多的数据待读取1,表示数据已经读好。websGetInput()函数的处理过程:■i-K.qrtilHli■iia.b.REgZi.^TLZ(f 玳岸I■i-K.qrtilHli■iia.b.REgZi.^TLZ(f 玳岸I;内.(i*MrE..«{Al;七盘讥订1注意上图中的两个注解,1是读取socket时,一次最多读256个字节,2是post的数据长度在websParseRequest(wp)函数中得到,也就是说如果一个连接首次调用websGetlnput()函数,应该执行的是==0的那条分支。下面试着走一个流程,当websGetlnput()函数接收上例post头时,会怎么处理。首先,程序会进入==0分支,接着会进入socketGets()函数,我们在这个函数里去走一圈:①socketGets()函数从socket中读取一行,然后返回,如现在读到的是:POST/goform/formTestHTTP/1.1明显程序将直接走到〃输出:这里,并返回1,websGetInput()函数的上层调用函数websReadEvent()开始进入状态机WEBS_BEGIN进行处理,分析post头的第一行,分析成功将状态转到WEBS_HEADER,这时第二次调用websGetInput()。这里wp结构除了state变量变了,其他都没变,所以len还是0,还是调用socketGets()函数从socket中读取一行,我们这都假设读成功,先不想不成功的事,现在我们到WEBS_HEADER状态机,把读到的数据放入wp->header缓存变量中,这样一直读到Authorization:BasicYWRtaW46YWRtaW4=这时,wp->header缓存中存放了除第一行以外的head,好,到这时,websReadEvent()还会再调用一次websGetInput(),我们看这回会有什么情况。另外先说下WEBS_HEADER状态机中为什么会有ringqPutStr(&wp->header,T(""));这一句,是因为在socketGets()函数以””为一行结束标志来返回这一行,但是并没有包括””,所以在这里总是会加上一个以便后面的程序做分析。接着读,还是走到socketGets()函数,这时将会读到””这一行,在socketGets()函数中这样处理:因为’’是不加入缓存的,当读到’’时,程序这样处理:if(c==''){len=ringqLen(lq);if(len>0){*buf=ballocAscToUni((char*)lq->servp,len);}else{*buf=NULL;}ringqFlush(lq);returnlen;}这里用得很巧妙,因为'’是不加入缓存,所以len=ringqLen(lq將为0,这时理所当然返回0了,也就是读完head了,我们看当websGetInput()收到socketGets()的0返回值时,会怎么样处理。④按上图,程序就会走到wp->state这个条件分支,当前状态是WEBS_HEADER,所以会执行websParseRequest()函数,这个函数会尽情的分解这个头文件,而马上要起到作用的是在分解时处理的一个变量:wp->flags|=WEBS_CLEN。这是根据头中的Content-Length:116得到的。如果头中没有这行,会怎么办?那么首先wp->flags中不会有WEBS_CLEN这一位,并且wp->clen也不会被初始化。下一步进入wp->flags条件,就是上图①中的入口,程序如下:if(wp->flags&WEBS_POST_REQUEST){if(wp->flags&WEBS_CLEN){wp->state=WEBS_POST_CLEN;clen=wp->clen;}else{wp->state=WEBS_POST;clen=1;}if(clen>0){return0;}return1;}如果是WEBS_POST_REQUEST,则继续到条件wp->flags,如果是WEBS_CLEN(如果是标准http头,肯定是有这个的),就把wp->state=WEBS_POST_CLEN,然后置clen=wp->clen,注意,这里clen可能是0,Content-Length:0如果wp->flags中没有WEBS_CLEN这一位,则设置wp->state=WEBS_POST和clen=1,接着根据clen的值大于0返回0,上面说了有可能等于零的情况,那么会返回1(表示没有数据可读了)。我们先看websGetlnput()返回0给上级函数websReadEvent()时的情况。在websReadEvent()中,如果websGetInput()返回0,则表明还有数据没读完,这时将会再次调用websGetInput(),这时程序会走进if(wp->state==WEBS_POST_CLEN){len=(wp->clen>WEBS_SOCKET_BUFSIZ)?WEBS_SOCKET_BUFSIZ:wp->clen;}else{len=0;},那么这里条件len将会>0,于是我们走进上图中的len>0分支,调用socketRead()函数,这里不讨论出错情况,socketRead()函数将读完最大长度不超过WEBS_SOCKET_BUFSIZ的post数据,然后websGetInput()返回1,我们又回到websReadEvent()函数。在websReadEvent()函数我们进入WEBS_POST_CLEN状态机,在这个处理模块将读完整个post数据,然后送websUrlHandlerRequest(wp)函数进行处理。这时websReadEvent()函数算是功得圆满了,置done为1退出函数。上面就是读客户端post数据并且该数据标有长度情况下的整个流程。在没有长度的情况下,如果有浏览器访问,除非浏览器有bug,正常是不会出现这种情况的。没长度时len=0,websGetInput()进入socketGets()那分支,然后一直读,读到出错(nbytesvO)或者是读完(nbytes=0)为止,我们先看<0那个分支,如果小于0就到条件EOF,如果是socket读完(走Y那个公支),那也就是正常结束情况,于是进入条件wp->state,如果是WEBS_POST状态机,则调用websUrlHandlerRequest(wp)函数进行处理,也就是在这里走完了post没有len情况下的流程。而我们再看回头看websReadEvent()函数中WEBS_POST状态机的功能只是把数据读到wp->header中。如果不是在WEBS_POST状态机,程序将调用websDone(wp,0)函数直接把该连接关闭,为什么呢?如果程序能走到这里,又不是WEBS_POST,那更不可能是WEBS_POST_LEN状态,那只有可能是WEBS_BEGIN或WEBS_HEADER状态,WEBS_BEGIN只读第一行POST/goform/wirelessAdvaneedHTTP/1.1而读这一行是不会出现EOF状态的,1是如果下面还有数据,出现EOF就说明这个head有问题,2是如果下面没有数据,应该是以””结尾的,如③所分析,socketGets()返回0,websGetInput()不会进入小于0的分支,所以这里程序的处理是把这个连接关闭了。if(wp->state==WEBS_POST){websUrlHandlerRequest(wp);}else{websDone(wp,0);}现在websGetInput()函数还有一个分支没有分析,就是在④的情况下,没有进入上图①的接口,这种情况下会发生什么?首先请看下面的http头:GET/forms.aspHTTP/1.1Accept:image/gif,image/x-xbitmap,image/jpeg,image/pjpeg,application/x-shockwave-flash,application/vnd.ms-excel,application/vnd.ms-powerpoint,application/msword,*/*Accept-Language:zh-cnAccept-Encoding:gzip,deflateUser-Agent:Mozilla/4.0(compatible;MSIE6.0;WindowsNT5.1;SV1;.NETCLR2.0.50727;CIBA)Host:0Connection:Keep-Alive程序不进入①是因为wp->flags&WEBS_POST_REQUEST这个条件不成立,所以我给出上面的一个GET头,这时wp->flags中应该在WEBS_BEGIN状态机下的websParseFirst()函数中没有被设置为WEBS_POST_REQUEST,这里不得不插入一段websParseFirst()函数中的代码说明情况:if(gstrcmp(op,T("GET"))!=0){if(gstrcmp(op,T("POST"))==0){wp->flags|=WEBS_POST_REQUEST;}elseif(gstrcmp(op,T("HEAD"))==0){wp->flags|=WEBS_HEAD_REQUE
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 2024版化工行业泵送合同3篇
- 二零二五年度楼板浇注材料供应与施工合同4篇
- 二零二五年度二手重型机械租赁合同范本4篇
- 2025年度宠物健康监测服务与个人健康管理合同3篇
- 2025版专业录音棚录音师劳务服务合同范本4篇
- 二零二五年度现代简约风格床具购买合同模板3篇
- 2025年度马戏团演出品牌形象与合作开发合同4篇
- 2025年度餐饮连锁企业加盟管理服务合同3篇
- 2025版森林资源苗木养护与保护合同4篇
- 二零二四年企业智能化仓储技术集成合同3篇
- 【探迹科技】2024知识产权行业发展趋势报告-从工业轰鸣到数智浪潮知识产权成为竞争市场的“矛与盾”
- 《中国政法大学》课件
- GB/T 35270-2024婴幼儿背带(袋)
- 2024-2025学年高二上学期期末数学试卷(新题型:19题)(基础篇)(含答案)
- 海外市场开拓计划
- 2024年度国家社会科学基金项目课题指南
- 七上-动点、动角问题12道好题-解析
- 2024年九省联考新高考 数学试卷(含答案解析)
- 红色历史研学旅行课程设计
- 下运动神经元损害综合征疾病演示课件
- 2023中考地理真题(含解析)
评论
0/150
提交评论