前兩篇文章介紹了select,poll,epoll的基本用法,現在我們來看看它們的區別和適用場景。
首先還是來看常見的select和poll。對於網絡編程來說,一般認為poll比select要高級一些,這主要源於以下幾個原因:
1.poll() 不要求開發者計算最大文件描述符加一的大小。
2.poll() 在應付大數目的文件描述符的時候速度更快,因為對於select()來說內核需要檢查大量描述符對應的fd_set 中的每一個比特位,比較費時。
3.select 可以監控的文件描述符數目是固定的,相對來說也較少(1024或2048),如果需要監控數值比較大的文件描述符,就算所監控的描述符很少,如果分布的很稀疏也會效率很低,對於poll() 函數來說,就可以創建特定大小的數組來保存監控的描述符,而不受文件描述符值大小的影響,而且poll()可以監控的文件數目遠大於select。
4.對於select來說,所監控的fd_set在select返回之後會發生變化,所以在下一次進入select()之前都需要重新初始化需要監控的fd_set,poll() 函數將監控的輸入和輸出事件分開,允許被監控的文件數組被復用而不需要重新初始化。
5.select() 函數的超時參數在返回時也是未定義的,考慮到可移植性,每次在超時之後在下一次進入到select之前都需要重新設置超時參數。
當然也不是說select就沒有優點:
1.select()的可移植性更好,在某些Unix系統上不支持poll()
2.select() 對於超時值提供了更好的精度:微秒,而poll是毫秒。
epoll的優點:
1.支持一個進程打開大數目的socket描述符(FD)
select 最不能忍受的是一個進程所打開的FD是有一定限制的,由FD_SETSIZE設置,默認值是1024/2048。對於那些需要支持的上萬連接數目的IM服務器來說顯然太少了。這時候你一是可以選擇修改這個宏然後重新編譯內核。不過 epoll則沒有這個限制,它所支持的FD上限是最大可以打開文件的數目,這個數字一般遠大於2048,舉個例子,在1GB內存的機器上大約是10萬左右,具體數目可以cat /proc/sys/fs/file-max察看,一般來說這個數目和系統內存關系很大。
2.IO效率不隨FD數目增加而線性下降
傳統的select/poll另一個致命弱點就是當你擁有一個很大的socket集合,不過由於網絡延時,任一時間只有部分的socket是"活躍"的,但是select/poll每次調用都會線性掃描全部的集合,導致效率呈現線性下降。但是epoll不存在這個問題,它只會對"活躍"的socket進行操作---這是因為在內核實現中epoll是根據每個fd上面的callback函數實現的。那麼,只有"活躍"的socket才會主動的去調用 callback函數,其他idle狀態socket則不會,在這點上,epoll實現了一個"偽"AIO,因為這時候推動力在Linux內核。
3.使用mmap加速內核與用戶空間的消息傳遞。
這點實際上涉及到epoll的具體實現了。無論是select,poll還是epoll都需要內核把FD消息通知給用戶空間,如何避免不必要的內存拷貝就很重要,在這點上,epoll是通過內核與用戶空間mmap同一塊內存實現的。
對於poll來說需要將用戶傳入的 pollfd 數組拷貝到內核空間,因為拷貝操作和數組長度相關,時間上這是一個O(n)操作,當事件發生,poll返回將獲得的數據傳送到用戶空間並執行釋放內存和剝離等待隊列等善後工作,向用戶空間拷貝數據與剝離等待隊列等操作的的時間復雜度同樣是O(n)。
這兩天看到一個雲風他們那裡的bug就是因為使用的開源庫中作者使用了非阻塞connect使用select() 來等待超時,但是並未檢查FD_SETSIZE,當文件描述符數目大於這個數目之後就會出現內存越界錯誤,造成coredump。