多種I/O模型及其對socket效率的改進

在讀redis源代碼的過程當中,我一直在考慮一個問題,就是「爲何單線程的redis能作到如此高效?」。爲了弄清楚這個問題,我查閱了一些資料,大概搞清楚了epoll等I/O模型的發展及其原理,如下是一個記錄整理。html

##I/O模型node

###操做系統與網絡I/Oreact

wKioL1SAIOyBfD_sAAFuJXzzVVE649.jpg

上圖來自維基百科,是一個基本的計算機結構。計算機主要完成兩個工做,運算和I/O。由於CPU處理效率以及各類設備的I/O效率千差萬別,因此協調I/O提升效率是操做系統的一個重要任務。各類I/O模型應運而生。linux

####同步和異步git

計算機任務執行的實體是進程,進程和I/O的關係須要理清。根據執行I/O時進程的狀態,能夠把I/O操做分爲同步和異步兩種方式。github

* 同步IO操做:引發進程的阻塞直到IO操做完成redis

* 異步IO操做:IO操做不會引發進程阻塞
編程

其中同步和異步指進程和IO的關係,阻塞和非阻塞指進程的狀態。結合操做系統的進程管理,能夠認爲最優的狀態是:a)進程徹底不被影響,繼續執行直到I/O完成再處理 b)進程徹底掛起,把資源交給其餘進程,I/O完成後再醒來繼續執行。服務器

###網絡IO模型網絡

####阻塞IO

最基礎的方式,問題在於進程阻塞於一個IO就不能響應其餘請求,效率很低。

2121550.png

####非阻塞IO

使用輪詢的方式,會佔用大量的計算資源,應該只有特殊狀況纔會使用到。

2121551.png

####IO複用

關鍵在於複用,一個進程能夠一次性等待多個IO。 

2121552.png   

####信號驅動IO

使用範圍很小,在TCP下信號產生的過於頻繁難以區分含義,因此只在使用UDP協議的程序中應用。「做者能找到的實際使用信號驅動的I/O程序是基於UDP的NTP服務器程序」。與異步IO的理念區別僅僅在因而否由操做系統自動拷貝數據到內核空間,我不明白爲何不直接發展成異步IO,反而要加入這個模型。

2121553.png

####異步IO

整個過程進程都是非阻塞的。

2121554.png

前4種都是同步的,第5種是異步


##socket

###socket基本概念

socket工做在會話層以上,經過綁定並監聽指定端口,接收數據。

wKiom1SAI7Py1BDCAAFaK2h75gQ056.jpg

###socket示例

最簡單的accept()方式是阻塞的,在創建鏈接以前程序掛起。能夠使用while循環accept,不過這種方式read()也是阻塞的,因此在第一個鏈接結束以前,第二個鏈接沒法被處理。

寫代碼中的兩個有趣的點:

* listen函數的backlog有一個magic number 511 

* close()以後有一個time_wait的過程,這時候若是再綁定相同端口會失敗,能夠使用setsockopt() 

##改進

###使用多進程改進

在while循環中,每次accept就fork出一個新進程,這樣就能同時處理多個鏈接。這裏須要處理父進程與子進程的關係,引用計數,信號處理等問題。並且每次建立銷燬進程消耗較大。

###使用select改進

在while循環中,使用select監聽一個fd集合,當其中的fd可讀/寫/異常時從阻塞恢復。能夠同時監聽多個fd。固然讀寫也會阻塞,因此要配合多進程/多線程使用。由於不須要每一個鏈接就生成一個新進程,比fork的方式要更優化。     

存在的問題是:

* fd數量有限

* 每次都循環檢查全部fd效率低

* 用戶態和內核態內存拷貝效率低 

###使用epoll改進

epoll針對select的問題作了優化:

* 上限是最大能夠打開文件的數目 

* 只關注「活躍」的連接不用循環檢查

* 使用內存共享避免拷貝

使用epoll 主要調用3個API :

int epoll_create(int size); //2.6.8以後size參數被忽略,參考
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);

另外特別須要注意的是水平觸發(LT)和邊緣觸發(ET)的概念,以及events的處理(出錯,client關閉等等)。

以上幾種方式我都簡單實現了,代碼見【20】。

###性能對比測試

這篇論文【18】  對select/poll/epoll的性能作了對比測試。能夠看出在全部I/O都活躍的狀況下,select和epoll的性能相近。但存在大量空閒鏈接時,epoll的性能就明顯高於select了,這與epoll的改進思路是相符合的。

在查找資料的過程當中,我發現大部分資料都語焉不詳或者思路不清晰。因此把IO和socket做爲前置知識,結合了一些代碼示例。但願應該能更好的理清思路,理解I/O模型爲何要這樣設計和實際應用中爲何要這樣改進。寫的東西很少,乾貨都在參考文章中。

參考

【1】Unix 五種基本I/O模型的區別 - 語行 - 博客園

【2】Operating Systems: I/O Systems

【3】OSI model - Wikipedia, the free encyclopedia

【4】簡單理解Socket - dolphinX - 博客園

【5】Linux Howtos: C/C++ -> Sockets Tutorial

【6】linux文件設備與I/O:read/write函數 與 阻塞 Block_麪包坊_百度空間

【7】socket編程-listen函數之backlog_飛翔的魚在北京_新浪博客 

【8】[C/C++] 解決Socket連續Bind同一個Port的問題 | 不務正業紀實

【9】linux 多進程 缺點 - 網摘記錄 - ITeye技術網站

【10】The GNU C Library: Server Example 

【11】Linux Epoll介紹和程序實例 - sparkliang的專欄 - 博客頻道 - CSDN.NET

【12】Linux下select, poll和epoll IO模型的詳解 - tianmo2010的專欄 - 博客頻道 - CSDN.NET

【13】UNIX網絡編程--I/O複用:select函數和poll函數講解(六) - 魚思故淵的專欄 - 博客頻道 - CSDN.NET

【14】epoll 或者 kqueue 的原理是什麼? - 知乎 Epoll detailed

【15】How to use epoll? A complete example in C - Banu Blog

【16】epoll(4): I/O event notification facility - Linux man page

【17】epoll_create(2): open epoll file descriptor - Linux man page

【18】https://www.kernel.org/doc/ols/2004/ols2004v1-pages-215-226.pdf

【19】networking - Caveats of select/poll vs. epoll reactors in Twisted - Stack Overflow

【20】mickhan/socket_demo

【21】unix下的I/O------阻塞,非阻塞,同步,異步 - 51CTO.COM

相關文章
相關標籤/搜索