前段時(shí)間又重新看了一遍socket編程,心血來潮寫了一個(gè)mini型的HTTP服務(wù)器,這就是monkv。
monkv GitHub地址:https://github.com/cuihang/monkv
古人說的好,紙上得來終覺淺,絕知此事要躬行。很多東西的原理,大家都能侃侃而談,但具體操作起來,會(huì)面臨各種各樣棘手的小細(xì)節(jié)。真正的高手,講起大道理來未必有什么過人之處,因?yàn)榇蟮牡览碚l都會(huì)說,真正的高手體現(xiàn)在對(duì)具體細(xì)節(jié)的處理上。所以在這里我記錄一下在monkv開發(fā)過程中出現(xiàn)的各種小問題,作為自己的一種技術(shù)積累,也分享給大家,權(quán)作交流。
如何設(shè)置epoll
epoll有兩種模式,ET和LT模式。至于具體的區(qū)別,網(wǎng)上遍地都是講解,這里不展開,這里為什么會(huì)專門提到epoll模式的問題,因?yàn)樵陂_發(fā)過程中,面臨一個(gè)小問題,就是如何保證一個(gè)請(qǐng)求只被一個(gè)線程或者進(jìn)程解析。
就算沒有看過monkv的源碼,也能猜到monkv的大體架構(gòu),無非是主進(jìn)程或者主線程監(jiān)聽epoll上的客戶端socket可讀事件,如果有新的客戶端可讀事件,就新開一個(gè)進(jìn)程或者線程來處理,這是最傳統(tǒng)的多路復(fù)用模型。那么問題來了,假設(shè)一個(gè)客戶端上有一個(gè)可讀事件,我們新開一個(gè)線程來處理。然后這個(gè)客戶端上又發(fā)生了一個(gè)可讀,如果我們?cè)匍_一個(gè)線程,就意味著兩個(gè)線程同時(shí)在處理一個(gè)請(qǐng)求,這就需要涉及到復(fù)雜的同步機(jī)制。所以最好的辦法就是保證一個(gè)請(qǐng)求只能被一個(gè)線程來處理。
這種情況下怎么辦?可能有人會(huì)說設(shè)置et模式呀,請(qǐng)注意,單純?cè)O(shè)置et模式也無法解決這個(gè)問題,因?yàn)閑t模式下,客戶端新的可讀事件也會(huì)被監(jiān)聽到。
最后的解決方案就是設(shè)置EPOLLONESHOT。具體可以google一下EPOLLONESHOT的用法。
如何保存客戶端的解析狀態(tài)
網(wǎng)絡(luò)傳輸是完全不可控的。什么情況都可能發(fā)送。正常情況下,http報(bào)文會(huì)及時(shí)完整的傳輸?shù)絪erver端。server端進(jìn)行解析就可以了,但如果server端得到的http報(bào)文是殘缺的,又該如何解析呢?
注意一個(gè)問題,這里的殘缺不是指順序紊亂或者缺失,tcp是可靠的,這里的殘缺值得是在某個(gè)監(jiān)聽事件里面得到的數(shù)據(jù)是殘缺的,比如說某個(gè)可讀事件上的數(shù)據(jù)是"GET / HT",很明顯,這是個(gè)殘缺的請(qǐng)求報(bào)文。并且可以猜想,該socket上下一個(gè)監(jiān)聽事件讀出來的數(shù)據(jù)肯定是"TP/1.1\r\n......."。這種情況該如何處理呢?
所以需要保存解析狀態(tài),需要搞一個(gè)類似狀態(tài)機(jī)的東西。否則就像剛才的情況,明明是一段正確的報(bào)文,但兩次監(jiān)聽解析都顯示報(bào)文錯(cuò)誤。
還有一個(gè)問題,是狀態(tài)機(jī)該如何保存的問題。epoll_event結(jié)構(gòu)體中標(biāo)識(shí)socket的字段是data,而data是一個(gè)union,包含標(biāo)識(shí)文件描述符的fd和標(biāo)識(shí)指針的ptr。
指針是最方便的,可以指向一個(gè)自定義的request結(jié)構(gòu)體,將讀取的數(shù)據(jù),解析的狀態(tài)都保存在request中。
如果用fd也行,那么需要單獨(dú)維護(hù)一個(gè)fd到狀態(tài)機(jī)的映射關(guān)系,這樣根據(jù)這個(gè)fd就能確定該socket上的數(shù)據(jù)解析到哪一步了。
線程池的實(shí)現(xiàn)
一般不建議用多進(jìn)程,因?yàn)檫M(jìn)程的開銷太大。從效率角度來說,采用多線程比較好,但一個(gè)請(qǐng)求開一個(gè)線程,請(qǐng)求結(jié)束銷毀線程,這樣的開銷也有一點(diǎn)大,最好的方式就是用線程池。
網(wǎng)上很多線程池的實(shí)現(xiàn)代碼。主要是通過互斥鎖和條件變量的方式實(shí)現(xiàn),monkv中的線程池也是這樣實(shí)現(xiàn)的。有興趣可以研究一下。
下一步的工作
monkv只是一個(gè)非常mini的server,目前只能解析get方法,只能處理靜態(tài)資源,與其說是一個(gè)server,倒不如說是我目前的一個(gè)小玩具,離生產(chǎn)環(huán)境還有遙不可及的距離。其實(shí)寫monkv就是寫著玩,就是要做一個(gè)小模型玩具,只是說目前的monkv是一個(gè)粗糙的模型,我想把monkv細(xì)細(xì)打磨,通過寫著玩來提高自己的網(wǎng)絡(luò)編程能力。所以下一步想做以下工作
- 替換monkv的http解析方法,使之能夠解析更多的http方法
- 添加對(duì)php lua的支持,使之能夠處理動(dòng)態(tài)腳本
- 如果一切順利,還希望能夠做成多進(jìn)程,類似nginx那種派生出多個(gè)子進(jìn)程的架構(gòu)。不過這個(gè)就更加復(fù)雜了,目前在想如何處理驚群效應(yīng),能力有限還是沒有太好的方案
想來想去,目前暫時(shí)就是這些問題。雖然monkv是我自己親手寫的,但我對(duì)monkv背后的技術(shù)真的理解嗎?我想我還是不理解的,很多東西就是這樣,你以為你明白了,再過些年回頭看,你還是沒明白,你之所以覺得自己明白,只是很巧合某些東西沒有被暴露出來而已。任何一個(gè)技術(shù)點(diǎn)背后都有很多很多的細(xì)節(jié)。所以還是那句話,紙上得來終覺淺,絕知此事要躬行。