前段時(shí)間又重新看了一遍socket編程甘耿,心血來潮寫了一個mini型的HTTP服務(wù)器操禀,這就是monkv隧枫。
monkv GitHub地址:https://github.com/cuihang/monkv
古人說的好纬乍,紙上得來終覺淺第晰,絕知此事要躬行锁孟。很多東西的原理,大家都能侃侃而談茁瘦,但具體操作起來品抽,會面臨各種各樣棘手的小細(xì)節(jié)。真正的高手甜熔,講起大道理來未必有什么過人之處圆恤,因?yàn)榇蟮牡览碚l都會說,真正的高手體現(xiàn)在對具體細(xì)節(jié)的處理上腔稀。所以在這里我記錄一下在monkv開發(fā)過程中出現(xiàn)的各種小問題盆昙,作為自己的一種技術(shù)積累,也分享給大家焊虏,權(quán)作交流淡喜。
如何設(shè)置epoll
epoll有兩種模式,ET和LT模式诵闭。至于具體的區(qū)別拆火,網(wǎng)上遍地都是講解,這里不展開涂圆,這里為什么會專門提到epoll模式的問題们镜,因?yàn)樵陂_發(fā)過程中,面臨一個小問題润歉,就是如何保證一個請求只被一個線程或者進(jìn)程解析模狭。
就算沒有看過monkv的源碼,也能猜到monkv的大體架構(gòu)踩衩,無非是主進(jìn)程或者主線程監(jiān)聽epoll上的客戶端socket可讀事件嚼鹉,如果有新的客戶端可讀事件贩汉,就新開一個進(jìn)程或者線程來處理,這是最傳統(tǒng)的多路復(fù)用模型锚赤。那么問題來了匹舞,假設(shè)一個客戶端上有一個可讀事件,我們新開一個線程來處理线脚。然后這個客戶端上又發(fā)生了一個可讀赐稽,如果我們再開一個線程,就意味著兩個線程同時(shí)在處理一個請求浑侥,這就需要涉及到復(fù)雜的同步機(jī)制姊舵。所以最好的辦法就是保證一個請求只能被一個線程來處理。
這種情況下怎么辦寓落?可能有人會說設(shè)置et模式呀括丁,請注意,單純設(shè)置et模式也無法解決這個問題伶选,因?yàn)閑t模式下史飞,客戶端新的可讀事件也會被監(jiān)聽到。
最后的解決方案就是設(shè)置EPOLLONESHOT仰税。具體可以google一下EPOLLONESHOT的用法构资。
如何保存客戶端的解析狀態(tài)
網(wǎng)絡(luò)傳輸是完全不可控的。什么情況都可能發(fā)送肖卧。正常情況下蚯窥,http報(bào)文會及時(shí)完整的傳輸?shù)絪erver端掸鹅。server端進(jìn)行解析就可以了塞帐,但如果server端得到的http報(bào)文是殘缺的,又該如何解析呢巍沙?
注意一個問題葵姥,這里的殘缺不是指順序紊亂或者缺失,tcp是可靠的句携,這里的殘缺值得是在某個監(jiān)聽事件里面得到的數(shù)據(jù)是殘缺的榔幸,比如說某個可讀事件上的數(shù)據(jù)是"GET / HT",很明顯矮嫉,這是個殘缺的請求報(bào)文削咆。并且可以猜想,該socket上下一個監(jiān)聽事件讀出來的數(shù)據(jù)肯定是"TP/1.1\r\n......."蠢笋。這種情況該如何處理呢拨齐?
所以需要保存解析狀態(tài),需要搞一個類似狀態(tài)機(jī)的東西昨寞。否則就像剛才的情況瞻惋,明明是一段正確的報(bào)文厦滤,但兩次監(jiān)聽解析都顯示報(bào)文錯誤。
還有一個問題歼狼,是狀態(tài)機(jī)該如何保存的問題掏导。epoll_event結(jié)構(gòu)體中標(biāo)識socket的字段是data,而data是一個union羽峰,包含標(biāo)識文件描述符的fd和標(biāo)識指針的ptr趟咆。
指針是最方便的,可以指向一個自定義的request結(jié)構(gòu)體限寞,將讀取的數(shù)據(jù)忍啸,解析的狀態(tài)都保存在request中。
如果用fd也行履植,那么需要單獨(dú)維護(hù)一個fd到狀態(tài)機(jī)的映射關(guān)系计雌,這樣根據(jù)這個fd就能確定該socket上的數(shù)據(jù)解析到哪一步了。
線程池的實(shí)現(xiàn)
一般不建議用多進(jìn)程玫霎,因?yàn)檫M(jìn)程的開銷太大凿滤。從效率角度來說,采用多線程比較好庶近,但一個請求開一個線程翁脆,請求結(jié)束銷毀線程,這樣的開銷也有一點(diǎn)大鼻种,最好的方式就是用線程池反番。
網(wǎng)上很多線程池的實(shí)現(xiàn)代碼。主要是通過互斥鎖和條件變量的方式實(shí)現(xiàn)叉钥,monkv中的線程池也是這樣實(shí)現(xiàn)的罢缸。有興趣可以研究一下。
下一步的工作
monkv只是一個非常mini的server投队,目前只能解析get方法枫疆,只能處理靜態(tài)資源,與其說是一個server敷鸦,倒不如說是我目前的一個小玩具息楔,離生產(chǎn)環(huán)境還有遙不可及的距離。其實(shí)寫monkv就是寫著玩扒披,就是要做一個小模型玩具值依,只是說目前的monkv是一個粗糙的模型,我想把monkv細(xì)細(xì)打磨碟案,通過寫著玩來提高自己的網(wǎng)絡(luò)編程能力愿险。所以下一步想做以下工作
- 替換monkv的http解析方法,使之能夠解析更多的http方法
- 添加對php lua的支持蟆淀,使之能夠處理動態(tài)腳本
- 如果一切順利拯啦,還希望能夠做成多進(jìn)程澡匪,類似nginx那種派生出多個子進(jìn)程的架構(gòu)。不過這個就更加復(fù)雜了褒链,目前在想如何處理驚群效應(yīng)唁情,能力有限還是沒有太好的方案
想來想去,目前暫時(shí)就是這些問題甫匹。雖然monkv是我自己親手寫的甸鸟,但我對monkv背后的技術(shù)真的理解嗎?我想我還是不理解的兵迅,很多東西就是這樣抢韭,你以為你明白了,再過些年回頭看恍箭,你還是沒明白刻恭,你之所以覺得自己明白,只是很巧合某些東西沒有被暴露出來而已扯夭。任何一個技術(shù)點(diǎn)背后都有很多很多的細(xì)節(jié)鳍贾。所以還是那句話,紙上得來終覺淺交洗,絕知此事要躬行骑科。