一敦腔、Linux網(wǎng)絡(luò)I/O模型簡介
Linux的內(nèi)核將所有外部設(shè)備都看做一個(gè)文件來操作余蟹,對一個(gè)文件的讀寫操作會調(diào)用內(nèi)核提供的系統(tǒng)命令迎瞧,返回一個(gè)file descriptor(fd哮缺,文件描述符 )苟穆。而對一個(gè)socket的讀寫也會有相應(yīng)的描述符验辞,稱為socketfd(socket描述符)稿黄,描述符就是一個(gè)數(shù)字,它指向內(nèi)核中的一個(gè)結(jié)構(gòu)體(文件路徑跌造,數(shù)據(jù)區(qū)等一些屬性)杆怕。
根據(jù)UNIX網(wǎng)絡(luò)編程對I/O模型的分類,UNIX提供了5種I/O模型鼻听,分別如下:
-
阻塞I/O模型
最常用的I/O模型就是阻塞I/O模型财著,缺省情形下,所有文件操作都是阻塞的撑碴。我們以套接字接口為例來講解此模型撑教。在進(jìn)程空間中調(diào)用recvfrom,其系統(tǒng)調(diào)用直到數(shù)據(jù)包到達(dá)且被復(fù)制到應(yīng)用進(jìn)程的緩沖區(qū)中或者發(fā)生錯(cuò)誤時(shí)才返回醉拓,在此期間一直會等待伟姐,進(jìn)程在從調(diào)用recvfrom開始到它返回的整段時(shí)間內(nèi)都是被阻塞的,因此被稱為阻塞I/O模型亿卤。
-
非阻塞I/O模型
rcvfrom從應(yīng)用層到內(nèi)核的時(shí)候愤兵,如果該緩沖區(qū)沒有數(shù)據(jù)的話,就直接返回一個(gè)EWOULDBLOCK錯(cuò)誤排吴,一般都對非阻塞I/O模型進(jìn)行輪詢檢查這個(gè)狀態(tài)秆乳,看內(nèi)核是不是有數(shù)據(jù)到來。
-
I/O復(fù)用模型
Linux提供select/poll(I/O復(fù)用模型會用到select或者poll函數(shù)钻哩,這兩個(gè)函數(shù)也會使進(jìn)程阻塞屹堰,但是和阻塞I/O所不同的是,這兩個(gè)函數(shù)可以同時(shí)阻塞多個(gè)I/O操作街氢。而且可以同時(shí)對多個(gè)讀操作扯键,多個(gè)寫操作的I/O函數(shù)進(jìn)行檢測,直到有數(shù)據(jù)可讀或可寫時(shí)珊肃,才真正調(diào)用I/O操作函數(shù))荣刑,進(jìn)程通過將一個(gè)或多個(gè)fd傳遞給select或poll系統(tǒng)調(diào)用馅笙,阻塞在select操作上,這樣select/poll可以幫我們偵測多fd是否處于就緒狀態(tài)厉亏。select/poll是順序掃描fd是否就緒董习,而且支持的fd數(shù)量有限,因此它的使用受到了一些制約叶堆。Linux還提供了一個(gè)epoll系統(tǒng)調(diào)用阱飘,epoll使用基于事件驅(qū)動方式代替順序掃描,因此性能更高虱颗。當(dāng)有fd就緒時(shí)沥匈,立即回調(diào)函數(shù)rollback。
-
信號驅(qū)動I/O模型
首先開啟套接口信號驅(qū)動I/O功能忘渔,并通過系統(tǒng)調(diào)用sigaction執(zhí)行一個(gè)信號處理函數(shù)(此系統(tǒng)調(diào)用立即返回高帖,進(jìn)程繼續(xù)工作,它是非阻塞的)畦粮。當(dāng)數(shù)據(jù)準(zhǔn)備就緒時(shí)散址,就為該進(jìn)程生成一個(gè)SIGIO信號,通過信號回調(diào)通知應(yīng)用程序調(diào)用recvfrom來讀取數(shù)據(jù)宣赔,并通知主循環(huán)函數(shù)處理數(shù)據(jù)预麸。
-
異步I/O
告知內(nèi)核啟動某個(gè)操作,并讓內(nèi)核在整個(gè)操作完成后(包括將數(shù)據(jù)從內(nèi)核復(fù)制到用戶自己的緩沖區(qū))通知我們儒将。這種模型與信號驅(qū)動模型的主要區(qū)別是:信號驅(qū)動I/O由內(nèi)核通知我們何時(shí)可以開始一個(gè)I/O操作吏祸;異步I/O模型由內(nèi)核通知我們I/O操作何時(shí)已經(jīng)完成。
二钩蚊、I/O多路復(fù)用技術(shù)
在I/O編程過程中贡翘,當(dāng)需要同時(shí)處理多個(gè)客戶端接入請求時(shí),可以利用多線程或者I/O多路復(fù)用技術(shù)進(jìn)行處理砰逻。I/O多路復(fù)用技術(shù)通過把多個(gè)I/O的阻塞復(fù)用到同一個(gè)select的阻塞上鸣驱,從而使得系統(tǒng)在單線程的情況下可以同時(shí)處理多個(gè)客戶端請求。與傳統(tǒng)的多線程/多進(jìn)程模型比蝠咆,I/O多路復(fù)用的最大優(yōu)勢是系統(tǒng)開銷小踊东,系統(tǒng)不需要創(chuàng)建新的額外進(jìn)程或者線程,也不需要維護(hù)這些進(jìn)程和線程的運(yùn)行刚操,降底了系統(tǒng)的維護(hù)工作量闸翅,節(jié)省了系統(tǒng)資源,I/O多路復(fù)用的主要應(yīng)用場景如下:
- 服務(wù)器需要同時(shí)處理多個(gè)處于監(jiān)聽狀態(tài)或者多個(gè)連接狀態(tài)的套接字赡茸。
- 服務(wù)器需要同時(shí)處理多種網(wǎng)絡(luò)協(xié)議的套接字缎脾。
目前支持I/O多路復(fù)用的系統(tǒng)調(diào)用有 select祝闻,pselect占卧,poll遗菠,epoll,在Linux網(wǎng)絡(luò)編程過程中华蜒,很長一段時(shí)間都使用select做輪詢和網(wǎng)絡(luò)事件通知辙纬,然而select的一些固有缺陷導(dǎo)致了它的應(yīng)用受到了很大的限制,最終Linux不得不在新的內(nèi)核版本中尋找select的替代方案叭喜,最終選擇了epoll贺拣。epoll與select的原理比較類似,為了克服select的缺點(diǎn)捂蕴,epoll作了很多重大改進(jìn)譬涡,現(xiàn)總結(jié)如下:
支持一個(gè)進(jìn)程打開的socket描述符(FD)不受限制(僅受限于操作系統(tǒng)的最大文件句柄數(shù))。
select最大的缺陷就是單個(gè)進(jìn)程所打開的FD是有一定限制的啥辨,它由FD_SETSIZE設(shè)置涡匀,默認(rèn)值是1024。對于那些需要支持上萬個(gè)TCP連接的大型服務(wù)器來說顯然太少了溉知≡纱瘢可以選擇修改這個(gè)宏,然后重新編譯內(nèi)核级乍,不過這會帶來網(wǎng)絡(luò)效率的下降舌劳。我們也可以通過選擇多進(jìn)程的方案(傳統(tǒng)的Apache方案)解決這個(gè)問題,不過雖然在Linux上創(chuàng)建進(jìn)程的代價(jià)比較小玫荣,但仍舊是不可忽視的甚淡,另外,進(jìn)程間的數(shù)據(jù)交換非常麻煩崇决,對于Java由于沒有共享內(nèi)存材诽,需要通過Socket通信或者其他方式進(jìn)行數(shù)據(jù)同步,這帶來了額外的性能損耗恒傻,增加了程序復(fù)雜度脸侥,所以也不是一種完美的解決方案。值得慶幸的是盈厘,epoll并沒有這個(gè)限制睁枕,它所支持的FD上限是操作系統(tǒng)的最大文件句柄數(shù),這個(gè)數(shù)字遠(yuǎn)遠(yuǎn)大于1024沸手。例如外遇,在1GB內(nèi)存的機(jī)器上大約是10萬個(gè)句柄左右,具體的值可以通過cat/proc/sys/fs/filemax察看契吉,通常情況下這個(gè)值跟系統(tǒng)的內(nèi)存關(guān)系比較大跳仿。I/O效率不會隨著FD數(shù)目的增加而線性下降。
傳統(tǒng)的select/poll另一個(gè)致命弱點(diǎn)就是當(dāng)你擁有一個(gè)很大的socket集合捐晶,由于網(wǎng)絡(luò)延時(shí)或者鏈路空閑菲语,任一時(shí)刻只有少部分的socket是“活躍”的妄辩,但是select/poll每次調(diào)用都會線性掃描全部集合,導(dǎo)致效率呈現(xiàn)線性下降山上。epoll不存在這個(gè)問題眼耀,它只會對“活躍”的socket進(jìn)行操作-這是因?yàn)樵趦?nèi)核實(shí)現(xiàn)中epoll是根據(jù)每個(gè)fd上面的callback函數(shù)實(shí)現(xiàn)的,那么,只有“活躍”的socket才會主動的去調(diào)用callback函數(shù),其他idle狀態(tài)socket則不會晒奕。在這點(diǎn)上,epoll實(shí)現(xiàn)了一個(gè)偽AIO楞黄。針對epoll和select性能對比的benchmark測試表明:如果所有的socket都處于活躍態(tài)。例如一個(gè)高速LAN環(huán)境抡驼,epoll并不比select/poll效率高太多谅辣;相反,如果過多使用epoll_ctl婶恼,效率相比還有稍微的下降桑阶。但是一旦使用idle connections模擬WAN環(huán)境,epoll的效率就遠(yuǎn)在select/poll之上了勾邦。使用mmap加速內(nèi)核與用戶空間的消息傳遞
無論是select蚣录,poll還是epoll都需要內(nèi)核把FD消息通知給用戶空間,如何避免不必要的內(nèi)存復(fù)制就顯得非常重要眷篇,epoll是通過內(nèi)核和用戶空間mmap使用同一塊內(nèi)存實(shí)現(xiàn)萎河。epoll的API更加簡單
用來克服select/poll缺點(diǎn)的方法不只有epoll,epoll只是一種Linux的實(shí)現(xiàn)方案蕉饼。在freeBSD下有kqueue虐杯,而dev/poll是最古老的Solaris的方案,使用難度依次遞增昧港。但epoll更加簡單擎椰。