epool
最近看高并發(fā)websocket服務(wù)器的代碼,其中設(shè)計(jì)到epool的使用,參考了這篇文章:
https://segmentfault.com/a/1190000003063859
有一些心得:
- IO 為什么是阻塞的? 首先,當(dāng)用戶程序讀寫IO設(shè)備時(shí),一定會進(jìn)行系統(tǒng)調(diào)用,從而發(fā)生內(nèi)核切換,內(nèi)核調(diào)用硬件讀寫數(shù)據(jù),并將數(shù)據(jù)通過
內(nèi)核的緩存空間拷貝到用戶的緩存空間,再切換回用戶的內(nèi)核進(jìn)程。這中間過程,用戶進(jìn)程是不會進(jìn)行讀寫的,因此IO是阻塞的。 - 非阻塞IO呢? 所謂的異步IO是指,用戶態(tài)程序讀取IO時(shí),如果讀寫不到數(shù)據(jù),則立刻返回。過一會,用戶進(jìn)程再次去讀取IO,以此來輪詢。
- IO 多路復(fù)用。select/pool/epool 屬于單個(gè)process就可以處理多個(gè)IO,他們會輪詢所負(fù)責(zé)的所有socket,當(dāng)有數(shù)據(jù)到達(dá)的時(shí)候,就通知用戶進(jìn)程。
就是通過一個(gè)機(jī)制同時(shí)等待多個(gè)文件描述符,任何一個(gè)感興趣的時(shí)間就緒的時(shí)候,就能通知用戶進(jìn)程。 - epool跟前面兩者的好處在于:1, 沒有文件描述符個(gè)數(shù)的限制;2, 即使文件描述符數(shù)目增大,也不會影響效率。
了解下epool就三個(gè)函數(shù) epool_create, epool_ctl, epoll_wait,分別用于創(chuàng)建epool,將文件描述符-事件放入epool,等到事件就緒。epool的wait能返回
就緒的所有文件描述符,而select必須用戶去輪詢所有的文件描述符。
兩種工作模式:
- 水平觸發(fā)(默認(rèn))
告訴你某個(gè)文件描述符就緒了,如果不做任何處理,下次還會觸發(fā)。 - 邊緣觸發(fā)
內(nèi)核第一次通知你,認(rèn)為你會去對文件做相應(yīng)的處理,之后狀態(tài)再次變化的時(shí)候才會通知你,效率更高。
我們怎么使用epool實(shí)現(xiàn)高并發(fā)的:
首先服務(wù)器listen在tcp端口上,把這個(gè)文件描述符放入pool,事件是 read|oneshot。(就是只觸發(fā)一次)
掛載的回調(diào)函數(shù):
對listener accept,得到的connection的,首先進(jìn)行websocket upgrade。之后文件描述符號,放入pool,事件是 read| EdgeTriger.
掛載的回調(diào)函數(shù)是: 判斷event如果是Hup,則close連接,文件描述符移除出pool。否則讀取完整message列表,并處理。
最后resume重新把listen的文件描述符繼續(xù)(不然oneshot只觸發(fā)一次)。
一些有趣的bug:
- 測試高并發(fā)大量連接之后,一段時(shí)間后,新進(jìn)來的連接無法建立。原因是這些大量測試連接不會去讀取數(shù)據(jù),協(xié)程池被耗盡,導(dǎo)致write阻塞,但是connection write之前
是有設(shè)置deadline,但是deadline不生效,是由于netpool將偷偷將連接設(shè)置成block ip。 - 某一些websocket連接會斷掉,客戶端是發(fā)送了pong響應(yīng)的,但是服務(wù)端認(rèn)為沒有讀到,認(rèn)為客戶端超時(shí)。原因是每次觸發(fā)讀時(shí)間的時(shí)候,發(fā)現(xiàn)有時(shí)一個(gè)tcp package會有多個(gè)message,
但是原先的reader只讀取一個(gè)message就去做處理,而netpool是edgeTriger的形式,沒有新的數(shù)據(jù)進(jìn)來的時(shí)候,不會再次觸發(fā)讀取,導(dǎo)致漏了pong數(shù)據(jù),修改成讀盡多個(gè)message再返回處理。