一、一个txt文本架构图web
main()数组
|缓存
|--websOpenServer()服务器
| |-- websOpenListen()网络
| |--socketOpenConnection()数据结构
| |--打开webServer服务器架构
| |--初化socket_t结构(注册websAccept()回调函数(socket_t sp->accept= websAccept)等)app
| |--把socket_t结构加入数组socketList socket
|函数
|--websUrlHandlerDefine()
| |--初始化websUrlHandlerType结构的websUrlHandler数组
| |--将urlPrefix和回调函数绑定在websUrlHandler[websUrlHandlerMax]中
|
|--websUrlHandlerDefine(websDefaultHandler)
| |--初始化websUrlHandlerType结构的websUrlHandler数组
| |--将urlPrefix和回调函数绑定在websUrlHandler[websUrlHandlerMax]中
|
|--websFormDefine()
| |--初始化symbol table结构sym_t,把名字和回调函数名放进sym_t结构
| |--把sym_t结构放进hash表中
|
|--websAspDefine()
| |--初始化symbol table结构sym_t,把名字和回调函数名放进sym_t结构
| |--把sym_t结构放进hash表中
|
|--(main loop)
| |--socketReady(-1) || socketSelect(-1, 1000)
| | |--轮询socketList |--轮询socketList中的handlerMask
| | |--中的几个变量 |--改变socketList中的currentEvents
| |
| |--socketProcess()
| |--轮询socketList[]
| |--socketReady()
| |--socketDoEvent()
| |--若是有新的链接(来自listenfd)就调用socketAccept()
| | |--调用socketAlloc()初始化socket_t结构
| | |--把socket_t结构加入 socketList数组
| | |--调用socket_t sp->accept()回调函数
| |
| |--若是不是新的链接就查找socketList数组调用socket_t sp->handler()回调函数
|
--|
websAccept()
|--作一些检查
|--socketCreateHandler(sid, SOCKET_READABLE, websSocketEvent, (int) wp)
| |--把sid注册为读事件,初始化socket_t sp->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_t sp->handler = websSocketEvent等, 更新对应的socketList数组
websDefaultWriteEvent()
|
|--写数据,不包括asp页面
二、跟着main走
Main函数很简短,因此能够对他的代码进行一行一行注释,以下:
/* * Main -- entry point from LINUX */ int main(int argc, char** argv) { /* * Initialize the memory allocator. Allow use of malloc and start * with a 60K heap. For each page request approx 8KB is allocated. * 60KB allows for several concurrent page requests. If more space * is required, malloc will be used for the overflow. */ bopen(NULL, (60 * 1024), B_USE_MALLOC); signal(SIGPIPE, SIG_IGN); printf("**************88\n"); /* * Initialize the web server */ if (initWebs() < 0) { return -1; } #ifdef WEBS_SSL_SUPPORT websSSLOpen(); #endif /* * Basic event loop. SocketReady returns true when a socket is ready for * service. SocketSelect will block until an event occurs. SocketProcess * will actually do the servicing. */ while (!finished) { if (socketReady(-1) || socketSelect(-1, 100)) { socketProcess(-1); } websCgiCleanup(); emfSchedProcess(); } #ifdef WEBS_SSL_SUPPORT websSSLClose(); #endif #ifdef USER_MANAGEMENT_SUPPORT umClose(); #endif /* * Close the socket module, report memory leaks and close the memory allocator */ websCloseServer(); socketClose(); #ifdef B_STATS memLeaks(); #endif bclose(); return 0; }
3.一些想法
1, 找出他们共同的数据结构
2, 找出对这些数据结构维护(操做)的函数
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, 对具体数据的操做(asp,form…),handler.c文件
选择第三种方法来看程序:
假设有个http请求:从这个http请求到服务器的处理,而后返回这样一个过程来看goahead是怎么操做的?
1,写一个http请求的url和一个head
1, 写一个http请求的post的head
注:由于此次要看通整个goahead代码,因此一会儿不知道以什么思路来看。上面是一些想法,不知道从哪里开始分析一个项目的代码,也不知道取舍哪些进行程序结构和功能方面的分析。后来的结果是写出了下面的文字。
4.goahead mainloop源码分析
4.1 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。
4.2 socketSelect(-1, 1000)函数分析
socketSelect函数是系统调用select的外包函数,该函数的主要功能就是监听(?)注册的socket事件集合,而后修改sp->currentEvents变量。
流程以下:
在主函数中,对socketSelect的调用是这样的:
if (socketReady(-1) || socketSelect(-1, 1000)),这样作并无对socketSelect的返回值进行检查,也就是说当socketSelect返回-1时,该条件也会知足,从而程序也会往下走,因此,这个地方也是能够优化的。
4.3 socketProcess(-1)函数分析
socketProcess处理到达的socket事件,若是传入的参数是小于0,则会处理全部的socket的事件,若是大于0,则会处理指定的socket的事件。下面是主要过程:
/* * Process socket events */ void socketProcess(int sid) { socket_t *sp; int all; all = 0; if (sid < 0) { all = 1; sid = 0; } /* * Process each socket */ for (; sid < socketMax; sid++) { if ((sp = socketList[sid]) == NULL) { if (! all) { break; } else { continue; } } if (socketReady(sid)) { socketDoEvent(sp); } if (! all) { break; } } }
socketReady()函数请看上面的解释,但不明白这里为何还要用到这个函数,应该也是个能够优化的地方,我如今想到一个过程,应该是这样的:
if(socketSelect ()){ socketProcess(); }
后注:走完websGetInput()函数的分析后,由于这时仔细看到了更多的代码,上面的这个优化是不行的,由于socketReady(int sid)函数中,是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。
4.4 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()函数功能:
通过websAccept()函数后,将走出socket层,来到webs_t结构层,之后对读和写的操做都经过操做webs_t这个数据结构来完成。
4.5 websSocketEvent()函数分析
websSocketEvent()函数处理socket的读和写事件:
(1)websSocketEvent()函数根据mask决定调用读仍是写函数,wp->writeSocket函数指针在websDefaultHandler()中经过调用websSetRequestSocketHandler()进行注册,指向websDefaultWriteEvent()函数。
(2)websReadEvent()函数:
websReadEvent()函数处理读事件,咱们怀疑这个函数有问题,会致使web服务器宕机,所以要重点分析。
咱们来看看该函数中用到的wp结构的四个状态:
先给出一个http的post头,看起来形象点:
POST /goform/formTest HTTP/1.1 Accept: 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-cn Accept-Encoding: gzip, deflate User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 2.0.50727; CIBA) Host: 192.168.90.50 Connection: Keep-Alive
程序不进入①是由于wp->flags & WEBS_POST_REQUEST这个条件不成立,因此我给出上面的一个GET头,这时wp->flags中应该在WEBS_BEGIN状态机下的websParseFirst()函数中没有被设置为WEBS_POST_REQUEST,这里不得不插入一段websParseFirst()函数中的代码说明状况:
/* * Parse the first line of a HTTP request */ static int websParseFirst(webs_t wp, char_t *text) { char_t *op, *proto, *protoVer, *url, *host, *query, *path, *port, *ext; char_t *buf; int testPort; a_assert(websValid(wp)); a_assert(text && *text); /* * Determine the request type: GET, HEAD or POST */ op = gstrtok(text, T(" \t")); if (op == NULL || *op == '\0') { websError(wp, 400, T("Bad HTTP request")); return -1; } if (gstrcmp(op, T("GET")) != 0) { if (gstrcmp(op, T("POST")) == 0) { wp->flags |= WEBS_POST_REQUEST; } else if (gstrcmp(op, T("HEAD")) == 0) { wp->flags |= WEBS_HEAD_REQUEST; } else { websError(wp, 400, T("Bad request type")); return -1; } } /* * Store result in the form (CGI) variable store */ websSetVar(wp, T("REQUEST_METHOD"), op); url = gstrtok(NULL, T(" \t\n")); if (url == NULL || *url == '\0') { websError(wp, 400, T("Bad HTTP request")); return -1; } protoVer = gstrtok(NULL, T(" \t\n")); /* * Parse the URL and store all the various URL components. websUrlParse * returns an allocated buffer in buf which we must free. We support both * proxied and non-proxied requests. Proxied requests will have http://host/ * at the start of the URL. Non-proxied will just be local path names. */ host = path = port = proto = query = ext = NULL; if (websUrlParse(url, &buf, &host, &path, &port, &query, &proto, NULL, &ext) < 0) { websError(wp, 400, T("Bad URL format")); return -1; } wp->url = bstrdup(B_L, url); #ifndef __NO_CGI_BIN if (gstrstr(url, CGI_BIN) != NULL) { wp->flags |= WEBS_CGI_REQUEST; if (wp->flags & WEBS_POST_REQUEST) { wp->cgiStdin = websGetCgiCommName(); } } #endif 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); if ((testPort = socketGetPort(wp->listenSid)) >= 0) { wp->port = testPort; } else { wp->port = gatoi(port); } if (gstrcmp(ext, T(".asp")) == 0) { wp->flags |= WEBS_ASP; } bfree(B_L, buf); websUrlType(url, wp->type, TSZ(wp->type)); #ifdef WEBS_PROXY_SUPPORT /* * Determine if this is a request for local webs data. If it is not a proxied * request from the browser, we won't see the "http://" or the system name, so * we assume it must be talking to us directly for local webs data. * Note: not fully implemented yet. */ if (gstrstr(wp->url, T("http://")) == NULL || ((gstrcmp(wp->host, T("localhost")) == 0 || gstrcmp(wp->host, websHost) == 0) && (wp->port == websPort))) { wp->flags |= WEBS_LOCAL_PAGE; if (gstrcmp(wp->path, T("/")) == 0) { wp->flags |= WEBS_HOME_PAGE; } } #endif ringqFlush(&wp->header); return 0; }
看来,对于GET头,wp->flags并不须要设置一个WEBS_GET_XXX什么的标志位。
缘由说清了,按上图,程序进入websUrlHandlerRequest(wp)函数,websUrlHandlerRequest(wp)函数会怎么处理上面给出的这个GET头,websUrlHandlerRequest(wp)根据urlPrefix来调用注册好的回调函数,先说urlPrefix,他是指/goform/xxx,/cgi_bin/yyy中的/goform和/cgi_bin这些东西,若是一个URL是这样的:/forms.asp那么他的urlPrefix为””,在主main()函数中,注册了对这个urlPrefix的回调函数为:
websUrlHandlerDefine(T(""), NULL, 0, websDefaultHandler, WEBS_HANDLER_LAST);
即websDefaultHandler()函数。websUrlHandlerRequest(wp)函数经过查找websUrlHandler[]数组会获得urlPrefix和回调函数的对应关系,而后调用回调函数,如今咱们进到websDefaultHandler(),在socketProcess(-1)函数分析的第(4)点中有个疑问,到这里就再也不是疑问了。websDefaultHandler()函数的最后调用:
websSetRequestSocketHandler(wp, SOCKET_WRITABLE, websDefaultWriteEvent);
注册该wp链接写事件的回调函数,并把wp的感兴趣的事件handlerMask改成SOCKET_WRITABLE。这样当第二次执行主循环时,想一想回到websSocketEvent()函数,就会调用到写事件的函数websDefaultWriteEvent(),经过wp->writeSocket指针。
4.6 websCgiCleanup()函数分析
该函数清除执行完的CGI进程。
4.7 emfSchedProcess()函数分析
emfSchedProcess()函数检查超时的链接,若是有超时的链接就会把这个链接清理掉,这里要注意程序中是怎么设置超时的起起始时间的。
在websReadEvent()函数中,调用websSetTimeMark(wp)为该链接设置一个时间戳,超时就是相对于这个时间的,可是请想下若是该链接一直没有数据到来的话,仅完成三次握手(不了解内核会不会对这样的链接有个超时机制,若是有,我下面就是白说),由于不可能执行到websReadEvent()函数,那么超时机制将对该链接无效,能够想象有不少这样的恶意链接没有被清除将会浪费系统资源,因此当accept这个链接的时候,就用websSetTimeMark(wp)为该链接设置一个时间戳,这个动做能够放在websAlloc ()函数中,这个函数被websAccept()函数调用,做用是初始化一个新的webs_t结构的链接。若是在超时前有数据来,这个时间戳将在websReadEvent()函数更改为新的;若是没有新的数据,那超时以后,服务器将断开这个链接,这样并不会影响系统运行,所以是可行的。
能够用telnet 192.168.0.50 80这样链接上服务器可是不发任何字符到服务器进行测试。