goaheadwebserver源码分析文档格式.docx
- 文档编号:19935813
- 上传时间:2023-01-12
- 格式:DOCX
- 页数:12
- 大小:21.49KB
goaheadwebserver源码分析文档格式.docx
《goaheadwebserver源码分析文档格式.docx》由会员分享,可在线阅读,更多相关《goaheadwebserver源码分析文档格式.docx(12页珍藏版)》请在冰豆网上搜索。
|||--调用socketAlloc()初始化socket_t结构
|||--把socket_t结构加入socketList数组
|||--调用socket_tsp->
accept()回调函数
|||--如果不是新的连接就查找socketList数组调用socket_tsp->
handler()回调函数
||
--|
websAccept()
|--做一些检查
|--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()
|--处理默认的URL请求,包括asp页面
|--websSetRequestSocketHandler()
||--注册默认的写事件函数wp->
writeSocket=websDefaultWriteEvent
||--socketCreateHandler(wp->
sid,SOCKET_WRITABLE,websSocketEvent,(int)wp)
||--把sid注册为写事件,初始化socket_tsp->
handler=websSocketEvent等,更新对应的socketList数组
websDefaultWriteEvent()
|--写数据,不包括asp页面
2.跟着main走
Main函数很简短,所以可以对他的代码进行一行一行注释,如下:
intmain(intargc,char**argv)
{
/*
*Initializethememoryallocator.Allowuseofmallocandstart
*witha60Kheap.Foreachpagerequestapprox8KBisallocated.
*60KBallowsforseveralconcurrentpagerequests.Ifmorespace
*isrequired,mallocwillbeusedfortheoverflow.
*/
首先分配一个大的内存块(60*1024字节),以后只要是以b开头的对内存操作的函数都是在这个已经分好的内存块上的操作,这些操作在中实现。
bopen(NULL,(60*1024),B_USE_MALLOC);
忽略SIGPIPE信号
signal(SIGPIPE,SIG_IGN);
*Initializethewebserver
初始化用户管理部分,打开web服务器,注册URL处理函数。
用户管理部分在中实现,
Web服务器的初始化是在和中实现
url处理函数在中实现
if(initWebs()<
0){
return-1;
}
初始化Ssl认证部分
注:
在这个文档中对ssl认证不做研究
#ifdefWEBS_SSL_SUPPORT
websSSLOpen();
#endif
*Basiceventloop.SocketReadyreturnstruewhenasocketisreadyfor
*service.SocketSelectwillblockuntilaneventoccurs.SocketProcess
*willactuallydotheservicing.
主循环
while(!
finished){
1,socketReady()函数检查是否有准备好的sock事件
2,socketSelect()函数首先把各个sock感兴趣的事件(sp->
handlerMask)注册给三个集合(读,写,例外),然后调用select系统调用,然后更新各个sock的sp->
currentEvents,表示各个sock的当前状态。
这两个函数在中实现,他们主要操作的数据是socket_t变量socketList中的handlerMask和currentEvents,socketList在中定义并主要由该文件中的socketAlloc,socketFree和socketPtr三个函数维护。
if(socketReady(-1)||socketSelect(-1,1000)){
该函数处理具体的sock事件
1,调用socketReady(sid)对socketList[sid]进行检查,看是否有sock事件
2,如果有sock事件,则调用socketDoEvent()函数,对事件进行处理
socketProcess(-1);
/*
该函数在中实现,检查cgiRec变量cgilist,首先把cgi的结果输出,如果有的话,然后看cgi进程是否已对号束,如果结束,就清理该cgi进程。
Cgilist在函数websCgiHandler和websCgiCleanup中维护。
websCgiCleanup();
该函数在中实现,功能是检查sched_t变量sched,断开超时的连接,sched变量在emfSchedCallback和emfUnschedCallback中维护
emfSchedProcess();
退出时的清理工作,永远不会执行到这里
websSSLClose();
#ifdefUSER_MANAGEMENT_SUPPORT
umClose();
*Closethesocketmodule,reportmemoryleaksandclosethememoryallocator
websCloseServer();
socketClose();
#ifdefB_STATS
memLeaks();
bclose();
return0;
}
3.一些想法
1,找出他们共同的数据结构
2,找出对这些数据结构维护(操作)的函数
3,从http的get或者是post流程来看程序
4,整体架构如何掌握
5,分模块,从全局的角度看各个模块的功能
6,从main函数起,按树型结构一层层分析下去
选择第五种方法:
1,sock模块,专门处理网络链接这一块,有这么几个文件:
和,是(维护)处理链接的socket_t数据结构,是(维护)处理链接的。
2,对http协议数据进行操作(读取和分析),文件
3,对具体数据的操作(asp,form…),文件
选择第三种方法来看程序:
假设有个http请求:
从这个http请求到服务器的处理,然后返回这样一个过程来看goahead是怎么操作的
1,写一个http请求的url和一个head
1,写一个http请求的post的head
因为这次要看通整个goahead代码,所以一下子不知道以什么思路来看。
上面是一些想法,不知道从哪里开始分析一个项目的代码,也不知道取舍哪些进行程序结构和功能方面的分析。
后来的结果是写出了下面的文字。
mainloop源码分析
socketReady(-1)函数分析
socketReady函数检查已建立连接的socket中是否有以下事件,如果检查到一个,就返回1,如果没有检查到,就返回零。
(1)sp->
flags&
SOCKET_CONNRESET,如果该socket的flag标志为SOCKET_CONNRESET(该标志在哪里设置(初始化)的),则调用函数socketCloseConnection(该函数后面会解释)关闭该socket连接,然后返回0;
(2)sp->
currentEvents&
sp->
handlerMask,如果该socket当前的事件和他要处理的事件相同,就返回1,告诉调用socketReady的函数有socket准备好被处理了;
(3)sp->
handlerMask&
SOCKET_READABLE&
&
socketInputBuffered(sid)>
0,如果该socket要处理的事件是SOCKET_READABLE并且该socket的缓存中有可读的数据,则调用socketSelect函数(为什么在这里要调用这个函数,看了下socketSelect,应该是为了设置sp->
currentEvents|=SOCKET_READABLE,所以这里应可以优化),然后返回1,告诉调用socketReady的函数有socket准备好被处理了;
(4)socketReady函数根据传入的参数sid决定是检查id为sid的socket(当sid大于0),还是遍历整个socketList(当sid小于0),如果以上3个条件中没有一个满足,则返回0。
socketSelect(-1,1000)函数分析
socketSelect函数是系统调用select的外包函数,该函数的主要功能就是监听()注册的socket事件集合,然后修改sp->
currentEvents变量。
流程如下:
在主函数中,对socketSelect的调用是这样的:
if(socketReady(-1)||socketSelect(-1,1000)),这样做并没有对socketSelect的返回值进行检查,也就是说当socketSelect返回-1时,该条件也会满足,从而程序也会往下走,所以,这个地方也是可以优化的。
socketProcess(-1)函数分析
socketProcess处理到达的socket事件,如果传入的参数是小于0,则会处理所有的socket的事件,如果大于0,则会处理指定的socket的事件。
下面是主要过程:
if(socketReady(sid)){
socketDoEvent(sp);
socketReady()函数请看上面的解释,但不明白这里为什么还要用到这个函数,应该也是个可以优化的地方,我现在想到一个过程,应该是这样的:
If(socketSelect()){
If(socketReady())
socketDoEvent();
socketProcess();
后注:
走完websGetInput()函数的分析后,因为这时仔细看到了更多的代码,上面的这个优化是不行的,因为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,然后马上返回。
(2)如果当前不是读事件但是该socket原感兴趣的是读事件并且socket缓存中确有数据可读,那就置currentEvents为可读,这一步在socketReady函数中有做过,所以这里应该是可以去掉的。
(3)如果当前是写事件,那就看看该socket的写缓存中有没有数据,如果有并且有SOCKET_FLUSHING标志就全部输出该写缓存,这是为新的写事件做清理工作。
(4)调用事件处理函数sp->
handler,该函数指针分别在两个地方进行初始化:
1,在websDefaultHandler()函数中注册写事件,该函数在什么时候被调
2,在websAccept()函数中注册读事件
两处都指向websSocketEvent()函数。
等下解释这个函数。
(5)把currentEvents置为0。
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->
=~SOCKET_LISTENING,把该连接和监听连接区别开。
然后,监听socket用自已的Sp->
accept调用到websAccept()函数,但是传递的第一个参数是新连接的id:
nid。
这也应该是个技巧吧。
websAccept()函数功能:
经过websAccept()函数后,将走出socket层,来到webs_t结构层,以后对读和写的操作都通过操作webs_t这个数据结构来完成。
websSocketEvent()函数分析
websSocketEvent()函数处理socket的读和写事件:
(1)websSocketEvent()函数根据mask决定调用读还是写函数,wp->
writeSocket函数指针在websDefaultHandler()中通过调用websSetRequestSocketHandler()进行注册,指向websDefaultWriteEvent()函数。
(2)websReadEvent()函数:
websReadEvent()函数处理读事件,我们怀疑这个函数有问题,会导致web服务器宕机,因此要重点分析。
我们来看看该函数中用到的wp结构的四个状态:
先给出一个http的post头,看起来形象点:
POST/goform/formTestHTTP/
Accept:
image/gif,image/x-xbitmap,image/jpeg,image/pjpeg,application/x-shockwave-flash,application/,application/,application/msword,*/*
Referer:
zh-cn
Content-Type:
application/x-www-form-urlencoded
Accept-Encoding:
gzip,deflate
User-Agent:
Mozilla/(compatible;
MSIE;
WindowsNT;
SV1;
.NETCLRCIBA)
Host:
32
Connection:
Keep-Alive
Cache-Control:
no-cache
name=adfs&
address=fdsafdsa&
ok=OK
(1)WEBS_BEGIN,该状态是在websAccept()函数调用wbesAlloc()函数初始化wp结构时被设置的,如果是在这个状态,该函数就调用websParseFirst()函数分析http头的第一行数据,在websParseFirst()中,主要会对wp结构的下面几个数据进行操作:
wp->
flags|=WEBS_POST_REQUEST;
query=bstrdup(B_L,query);
wp->
host=bstrdup(B_L,host);
path=bstrdup(B_L,path);
protocol=bstrdup(B_L,proto);
protoVersion=bstrdup(B_L,protoVer);
对wp->
flags几个重要的赋值:
WEBS_CGI_REQUEST,WEBS_ASP,WEBS_LOCAL_PAGE等,分析好第一行后,调用ringqFlush()函数把wp->
header做相当于清零的操作,把各个指针都指向这个缓冲的开始处。
如果websParseFirst()函数调用成功,就转到WEBS_HEADER状态:
state=WEBS_HEADER。
按程序的流程,这个时候仍在循环中,马上就会转到WEBS_HEADER状态机,但是会这样吗
(2)WEBS_HEADER,首先
(1)以后(经过WEBS_BEGIN),除非websGetInput()()函数调用不成功,否则就会执行到WEBS_HEADER状态机,这个时候我们就要看看websGetInput()函数了,因为这个函数如果调用不成功,是不会执行到WEBS_HEADER的。
websGetInput()函数分析:
函数功能:
接收客户端的数据;
函数返回值:
-1,表示出错或者请求已经被处理
0,表示告诉调用者还有更多的数据待读取
1,表示数据已经读好。
websGetInput()函数的处理过程:
注意上图中的两个注解,1是读取socket时,一次最多读256个字节,2是post的数据长度在websParseRequest(wp)函数中得到,也就是说如果一个连接首次调用websGetInput()函数,应该执行的是==0的那条分支。
下面试着走一个流程,当websGetInput()函数接收上例post头时,会怎么处理。
首先,程序会进入==0分支,接着会进入socketGets()函数,我们在这个函数里去走一圈:
①socketGets()函数从socket中读取一行,然后返回,如现在读到的是:
POST/goform/formTestHTTP/
明显程序将直接走到ETCLRCIBA)
程序不进入①是因为wp->
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){
}elseif(gstrcmp(op,T("
HEAD"
flags|=WEBS_HEAD_REQUEST;
}else{
websError(wp,400,T("
Badrequesttype"
));
}看来,对于GET头,wp->
flags并不需要设置一个WEBS_GET_XXX什么的标志位。
原因说清了,按上图,程序
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- goaheadwebserver 源码 分析