本文不涉及具體代碼,只分析Linux IO演化的心路歷程,學習資料來源網絡,不保證必定正確,如有錯誤,歡迎指出。網絡
BIOsocket
服務端建立socket(80端口),文件描述符3號。函數
當線程調用accept時,阻塞等待3 fd鏈接就緒。學習
網卡(80端口)收到數據,將數據寫入內存,向cpu發出中斷信號,內核得知3 fd有新數據,cpu調用中斷程序響應中斷。spa
線程喚醒,文件描述符爲4號的socket,這時候就要新建線程T1去循環read(阻塞) 4fd了,由於主線程要負責鏈接。線程
弊端:一個線程處理一個鏈接,當有大量短鏈接的時候,就會有大量線程新建和消亡,頻繁地狀態切換,耗費大量資源。不過適用於長鏈接,且每一個鏈接有大量數據交互的狀況。3d
是否是可讓一個線程去處理多個鏈接呢?這樣的入口點在哪裏呢?blog
答曰:在read非阻塞,線程read 4fd,沒有數據,那就去read 5fd,這樣循環遍歷。隊列
因而有了非阻塞式IO事件
如今是能夠一個線程處理多個鏈接了,可是又出現了新的問題。
1.當有大量有效鏈接時(指由數據要交互),一個線程要read 4fd,接着read 5fd,這樣處理不及時。
2.當有大量無效鏈接時,一個線程頻繁的read(系統調用),意味着要頻繁的從用戶態切換到內核態,這也耗費大量資源。
是否是能夠減小read的調用次數呢?等到真的有數據來的時候再read。
因而有了IO多路複用的select模型
線程調用select,把要監聽的socket和對應的期待事件告訴內核,而後阻塞在全部的fd上,當內核發現有事件發生時,再喚醒對應fd上的線程,然後線程就能夠去read。
可是,線程只知道監聽的全部socket上,某些有數據,殊不知道是哪一個,因此要挨個遍歷(每一個socket是非阻塞的)。當有大量無效鏈接時,這一輪當中依然有 不少無效的read。
處理完一輪以後,進入到下一輪的select,而每一輪的select都要把要監聽的socket的fd傳給內核,數目一大,這成本也很高。
基於這兩方面緣由,select的上限設定爲1024。(poll模型基本與select同樣,只是有少許改進,如不限數量(仍然受限於物理),將略過)
其實fd傳一次就夠了吧?之後的每一次都是在上一次的基礎上,要新增就新增,要減掉就減掉,不然要監聽仍是和上一次同樣。
是否是能夠省去無效的read呢?要是我醒來以後,要read的每個socket都是有效的就行了!
因而有了IO多路複用的epoll模型
epoll_create()獲得一個5號epfd指向的是一個mmap共享空間(有關mmap,零拷貝的內容在下一篇講解)。
接着獲得一個7號fd的socket(NONBLOCK),經過epoll_ctl將它加到5號epfd的紅黑樹上。之後每一次要加,就經過調用這個函數加,文件描述符只用傳一次。
epoll_wait(5)線程在eventpoll上阻塞等待(能夠設置超時參數)。
當數據到來,5 epfd的紅黑樹上有事件發生時,中斷程序將會把發生事件的socket加到rdlist這個雙向鏈表中,而後喚醒5 epfd中eventpoll等待隊列中的線程。
線程喚醒後就能夠有效地遍歷雙向鏈表了。
epoll_wait的時候設置水平觸發或者邊緣觸發
event.events = EPOLLIN | EPOLLET;//邊緣觸發,當有新數據到來時觸發,若上一次沒有讀完,需等到下一次有新數據來。Netty中爲邊緣觸發
event.events = EPOLLIN; // LT是默認模式,當socket中有數據(多是上一次遺留),epoll_wait便可返回。jdk nio中爲水平觸發