一、一個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這樣鏈接上服務器可是不發任何字符到服務器進行測試。