Java NIO(一)select 和 epoll底層實(shí)現(xiàn)原理

一 內(nèi)核接受網(wǎng)卡流量的整個(gè)流程

預(yù)備知識(shí):

  • 網(wǎng)絡(luò)編程的核心對象是socket,當(dāng)創(chuàng)建socket時(shí)在底層會(huì)創(chuàng)建一個(gè)由文件系統(tǒng)管理的socket對象。這個(gè)對象包括了發(fā)送緩沖區(qū),接收緩沖區(qū),等待隊(duì)列
  • recv函數(shù)用于從某一個(gè)socket中接受流量,但是這個(gè)函數(shù)在被調(diào)用入進(jìn)程會(huì)一直處于阻塞狀態(tài),直到從該socket收到數(shù)據(jù)為止。

網(wǎng)卡接收流量的流程:

  • 步驟一:進(jìn)程中調(diào)用了recv函數(shù)請求接收指定socket的流量。
  • 步驟二:操作系統(tǒng)將這個(gè)進(jìn)程加入到對應(yīng)socket的等待隊(duì)列中,并從CPU工作隊(duì)列中移除,經(jīng)過這一步后,進(jìn)程會(huì)處理阻塞狀態(tài)。
  • 計(jì)算機(jī)接收到對端傳輸?shù)臄?shù)據(jù),網(wǎng)卡把數(shù)據(jù)寫入到內(nèi)存中。這一步不需要CPU參與(數(shù)據(jù)不經(jīng)過CPU直接從IO設(shè)備寫入內(nèi)存的技術(shù)叫作DMA技術(shù))
  • 數(shù)據(jù)寫完后網(wǎng)卡會(huì)發(fā)送一個(gè)中斷信號(hào),中斷CPU,通知CPU有數(shù)據(jù)到達(dá)。
  • CPU中斷程序響應(yīng)中斷,并把內(nèi)存中的數(shù)據(jù)寫入了對應(yīng)socket的緩沖區(qū)里
  • CPU喚醒進(jìn)程,把進(jìn)程從socket的等待隊(duì)列中移除,然后加入到工作隊(duì)列等待系統(tǒng)調(diào)用。

上面的流程有一個(gè)問題:
revc函數(shù)只能監(jiān)控一個(gè)socket,并且會(huì)導(dǎo)致進(jìn)程一直阻塞在這個(gè)socket中,直到socket中有數(shù)據(jù)返回為止。如果有多個(gè)socket監(jiān)控,則需要?jiǎng)?chuàng)建多個(gè)進(jìn)程,非常浪費(fèi)資源。

而select和epoll就解決了上面的問題,它們讓一個(gè)進(jìn)程可以監(jiān)控多個(gè)socket,下面分別說一下兩個(gè)函數(shù)的實(shí)現(xiàn)細(xì)節(jié)。

二 select的實(shí)現(xiàn)細(xì)節(jié)

select一次監(jiān)控多個(gè)socket的原理很簡單:

  • 它會(huì)把進(jìn)程加入到它需要監(jiān)控的所有socket的等待隊(duì)列中,然后將進(jìn)程從CPU 工作隊(duì)列中移除,進(jìn)入阻塞狀態(tài)。
  • 當(dāng)這些socket中有一個(gè)socket有數(shù)據(jù)返回時(shí),中斷程序會(huì)把進(jìn)程從所以的socket等待隊(duì)列中移除,并把進(jìn)程重新加入到CPU工作隊(duì)列中,讓進(jìn)程進(jìn)就緒狀態(tài)。
  • 進(jìn)程進(jìn)入被CPU調(diào)用到,只需要遍歷所有socket的狀態(tài),就可以知道哪些socket可以讀取數(shù)據(jù)了。
  • 操作完這些可讀取數(shù)據(jù)的socket之后,又會(huì)重復(fù)第一步,把進(jìn)程加入到所有的 socket中,然后讓進(jìn)程進(jìn)入阻塞狀態(tài)。

通過上述的方式,select函數(shù)實(shí)現(xiàn)了在一個(gè)進(jìn)程中監(jiān)控多個(gè)socket的方法。但是這函數(shù)的性能并不高,因?yàn)樗枰貜?fù)把進(jìn)程從所有的socket中加入/移除。因此它監(jiān)控的socket數(shù)量不能太多,底層規(guī)定不能超過1024個(gè)。

epoll函數(shù)針對select的這個(gè)缺陷作了改進(jìn),接下來說說epoll函數(shù)的實(shí)現(xiàn)細(xì)節(jié)。

三 epoll函數(shù)的實(shí)現(xiàn)細(xì)節(jié)

當(dāng)進(jìn)程調(diào)用epoll監(jiān)控多個(gè)socket時(shí),會(huì)在底層創(chuàng)建一個(gè)eventpoll對象,這個(gè)對象中包含一個(gè)重要的隊(duì)列:就緒隊(duì)列

  • 進(jìn)程調(diào)用epoll函數(shù)后,epoll會(huì)把這個(gè)進(jìn)程加入eventpoll對象的等待隊(duì)列中
  • 然后把eventpoll對象加入到所有socket的等待隊(duì)列中,并讓CPU阻塞住
  • 當(dāng)某一個(gè)socket有數(shù)據(jù)返回時(shí),CPU中斷程序會(huì)把這個(gè)socket加入到eventpoll對象的就緒隊(duì)列中,并把eventpoll中等待的進(jìn)程喚醒。
  • 進(jìn)程被喚醒后直接從就緒隊(duì)列中獲取socket讀取數(shù)據(jù)
  • 數(shù)據(jù)讀取完成后,epoll又會(huì)把進(jìn)程加入到eventpoll的等待隊(duì)列中,然后讓CPU阻塞住。

epoll針對select優(yōu)化的點(diǎn):

  • 除了第一次外,epoll不需操作所有socket對象的等待隊(duì)列,只需要操作eventpoll的等待隊(duì)列即可
  • 進(jìn)程被喚醒后,不需要遍歷即可直接知道哪些socket準(zhǔn)備好了。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容