怎么實(shí)現(xiàn)一個(gè)網(wǎng)站的Web Serve?Web服務(wù)器可以解析(handles)HTTP協(xié)議。當(dāng)Web服務(wù)器接收到一個(gè)HTTP請(qǐng)求(request),會(huì)返回一個(gè)HTTP響應(yīng)(response),例如送回一個(gè)HTML頁(yè)面。
怎么實(shí)現(xiàn)一個(gè)網(wǎng)站的Web Serve?Web服務(wù)器可以解析(handles)HTTP協(xié)議。當(dāng)Web服務(wù)器接收到一個(gè)HTTP請(qǐng)求(request),會(huì)返回一個(gè)HTTP響應(yīng)(response),例如送回一個(gè)HTML頁(yè)面。
定時(shí)器 Timer
如果一個(gè)請(qǐng)求在建立連接后遲遲沒(méi)有發(fā)送數(shù)據(jù),或者對(duì)方突然斷電,應(yīng)該如何處理?我們需要實(shí)現(xiàn)定時(shí)器來(lái)處理超時(shí)的請(qǐng)求。Vino 定時(shí)器的實(shí)現(xiàn)參考了 Nginx 的設(shè)計(jì),Nginx 使用一顆紅黑樹來(lái)存儲(chǔ)各個(gè)定時(shí)事件,每次事件循環(huán)時(shí)從紅黑樹中不斷找出最小(早)的事件,如果超時(shí)則觸發(fā)超時(shí)處理。為了簡(jiǎn)化實(shí)現(xiàn),在 Vino 中,我實(shí)現(xiàn)了一個(gè)小頂堆來(lái)存儲(chǔ)定時(shí)事件,如果被處理的定時(shí)事件同時(shí)支持長(zhǎng)連接,那么在該請(qǐng)求處理完畢后會(huì)更新該請(qǐng)求對(duì)應(yīng)的定時(shí)器,也就是重新計(jì)時(shí)。定時(shí)器相關(guān)代碼見(jiàn) vn_event_timer.h 和 vn_event_timer.c。
HTTP Parser
由于網(wǎng)絡(luò)的不確定性,我們并不能保證一次就能讀取所有的請(qǐng)求數(shù)據(jù)。因此,對(duì)于每一個(gè)請(qǐng)求,我們都會(huì)開辟一段緩沖區(qū)用于保存已經(jīng)讀取到的數(shù)據(jù)。同時(shí),我們需要同時(shí)對(duì)讀取到的數(shù)據(jù)進(jìn)行解析,以保證讀取到的數(shù)據(jù)都是合理的數(shù)據(jù),例如,假設(shè)目前緩沖區(qū)內(nèi)的數(shù)據(jù)為 GET /index.
html HTT,那么下一次讀取到的字符必須為 P,否則,應(yīng)立即檢測(cè)出當(dāng)前請(qǐng)求是一個(gè)異常的請(qǐng)求,并主動(dòng)關(guān)閉當(dāng)前的連接。
基于以上分析,我們需要實(shí)現(xiàn)一個(gè) HTTP 狀態(tài)機(jī)(Parser)來(lái)維持當(dāng)前的解析狀態(tài),Vino 狀態(tài)機(jī)的實(shí)現(xiàn)參考了 Nginx 的設(shè)計(jì),并對(duì) Nginx 的實(shí)現(xiàn)做了簡(jiǎn)化。HTTP Parser 相關(guān)代碼見(jiàn) vn_
http_parse.h 和 vn_http_parse.c。
Memory Pool
我們一般使用 malloc/calloc/free 來(lái)分配/釋放內(nèi)存,但是這些函數(shù)對(duì)于一些需要長(zhǎng)時(shí)間運(yùn)行的程序來(lái)說(shuō)會(huì)有一些弊端。頻繁使用這些函數(shù)分配和釋放內(nèi)存,會(huì)導(dǎo)致內(nèi)存碎片,不容易讓系統(tǒng)直接回收內(nèi)存。典型的例子就是大并發(fā)頻繁分配和回收內(nèi)存,會(huì)導(dǎo)致進(jìn)程的內(nèi)存產(chǎn)生碎片,并且不會(huì)立馬被系統(tǒng)回收。
使用內(nèi)存池分配內(nèi)存,可以在一定程度上提升內(nèi)存分配的效率,不需要每次都調(diào)用 malloc/calloc 函數(shù)。同時(shí),使用內(nèi)存池使得內(nèi)存管理更加簡(jiǎn)單。在 Vino 中,針對(duì)每一個(gè)請(qǐng)求,Vino 都會(huì)為其分配一或多個(gè)內(nèi)存池(各個(gè)內(nèi)存池形成一個(gè)單鏈表),在請(qǐng)求處理完畢后,一并釋放所有的內(nèi)存。
Vino 內(nèi)存池的實(shí)現(xiàn)依舊參考了 Nginx 的實(shí)現(xiàn),并做了簡(jiǎn)化,Memory Pool 相關(guān)代碼見(jiàn) vn_palloc.h 和 vn_palloc.c。
其他
在開發(fā) Vino 的過(guò)程中,還有許多需要考慮和權(quán)衡的地方。響應(yīng)請(qǐng)求時(shí),如果用戶請(qǐng)求的是一個(gè)很大的文件,導(dǎo)致寫緩沖區(qū)滿,我們?nèi)绾胃玫脑O(shè)計(jì)響應(yīng)緩沖區(qū)?如何更高效的設(shè)計(jì)底層數(shù)據(jù)結(jié)構(gòu)(如字符串、鏈表、小頂堆等)?如何更優(yōu)雅的解析命令行參數(shù)?如何對(duì)特定信號(hào)進(jìn)行處理?如何更健壯的處理錯(cuò)誤信息?當(dāng)代碼的數(shù)量達(dá)到一定程度后,如何更快的定位異常代碼?
Vino 的開發(fā) & 重構(gòu)暫時(shí)告一段落,源碼放在了 GitHub 上。當(dāng)然,Vino 還有許多不足之處,以及未實(shí)現(xiàn)的特性。
僅支持 HTTP GET 方法,暫不支持其他 HTTP method。
暫不支持動(dòng)態(tài)請(qǐng)求的處理。
支持的 HTTP/1.1 特性有限。