NIO&epoll

??說(shuō)到NIO纵散,涉及到的知識(shí)點(diǎn)有很多梳码,我們來(lái)一一捋一捋。

IO

??IO(InputStream/OutputStream)指的是讀出/寫(xiě)入數(shù)據(jù)伍掀,IO可以分為磁盤(pán)IO和網(wǎng)絡(luò)IO掰茶,圍繞我們今天主題講的是網(wǎng)絡(luò)IO。網(wǎng)絡(luò)IO包括了等待數(shù)據(jù)傳輸和讀寫(xiě)數(shù)據(jù)的過(guò)程硕盹,等待數(shù)據(jù)傳輸其實(shí)就是等待數(shù)據(jù)經(jīng)由網(wǎng)線(xiàn)符匾、網(wǎng)卡、內(nèi)核空間的過(guò)程瘩例,讀寫(xiě)數(shù)據(jù)的過(guò)程是內(nèi)核空間和用戶(hù)空間的互相拷貝的過(guò)程啊胶。
??舉個(gè)例子,在read發(fā)生時(shí)垛贤,很關(guān)鍵的兩點(diǎn)是:

  1. 等待準(zhǔn)備數(shù)據(jù)焰坪。
  2. 將數(shù)據(jù)從內(nèi)核拷貝到用戶(hù)進(jìn)程中去。
    正如萬(wàn)事萬(wàn)物并不是你想要的時(shí)候就有的一樣聘惦,當(dāng)內(nèi)核空間想要拿到數(shù)據(jù)時(shí)某饰,可能數(shù)據(jù)還沒(méi)有傳進(jìn)來(lái),這時(shí),它只能等著數(shù)據(jù)的到達(dá)黔漂,而用戶(hù)進(jìn)程也因此而阻塞诫尽。當(dāng)內(nèi)核空間一直等到數(shù)據(jù)準(zhǔn)備好了,它就會(huì)將數(shù)據(jù)從內(nèi)核空間中拷貝到用戶(hù)內(nèi)存炬守,然后內(nèi)核空間返回結(jié)果牧嫉,用戶(hù)進(jìn)程才解除阻塞狀態(tài),重新運(yùn)行起來(lái)减途。那網(wǎng)絡(luò)IO的網(wǎng)絡(luò)通信是借由誰(shuí)完成的呢酣藻?

Socket

??Java中的網(wǎng)絡(luò)通信是通過(guò)Socket實(shí)現(xiàn)的,我們都熟悉TCP/IP協(xié)議鳍置、Http協(xié)議等辽剧,Socket是TCP/IP協(xié)議的一個(gè)具體的實(shí)現(xiàn)。Socket分為ServerSocket和Socket兩大類(lèi)税产,ServerSocket用于服務(wù)端怕轿,可以通過(guò)方法監(jiān)聽(tīng)請(qǐng)求,監(jiān)聽(tīng)到請(qǐng)求后返回Scoket砖第,Socket用于具體完成數(shù)據(jù)傳輸撤卢,客戶(hù)端直接使用Socket發(fā)起請(qǐng)求并傳輸數(shù)據(jù)。 說(shuō)白了梧兼,網(wǎng)絡(luò)IO就是通過(guò)socket來(lái)進(jìn)行通信的。

如何得知已經(jīng)收到數(shù)據(jù)

??Socket是我們傳輸數(shù)據(jù)的工具智听,那我們?nèi)绾蔚弥呀?jīng)收到數(shù)據(jù)呢羽杰?數(shù)據(jù)從網(wǎng)線(xiàn)傳過(guò)來(lái),在我們電腦上第一步是到達(dá)網(wǎng)卡到推,此時(shí)控制網(wǎng)卡的驅(qū)動(dòng)(網(wǎng)卡驅(qū)動(dòng)程序就是CPU控制和使用網(wǎng)卡的程序)開(kāi)始發(fā)揮作用考赛,網(wǎng)卡通過(guò)中斷通知CPU數(shù)據(jù)已達(dá)的消息。
??中斷允許讓設(shè)備莉测,如鍵盤(pán)颜骤,串口卡,并口等設(shè)備表明它們需要CPU捣卤。一旦CPU接收了中斷請(qǐng)求忍抽,CPU就會(huì)暫時(shí)停止執(zhí)行正在運(yùn)行的程序,并調(diào)用一個(gè)稱(chēng)為中斷服務(wù)程序(interrupt service routine)的特定程序董朝。之后鸠项,CPU會(huì)恢復(fù)執(zhí)行之前被中斷的程序。那么接下來(lái)會(huì)發(fā)生什么子姜??jī)?nèi)核空間還在等著它需要的數(shù)據(jù)呢祟绊,我們繼續(xù)往下走。

阻塞

??操作系統(tǒng)也已經(jīng)知道,數(shù)據(jù)到達(dá)了牧抽。我們現(xiàn)在來(lái)到內(nèi)核空間嘉熊,操作系統(tǒng)為了實(shí)現(xiàn)進(jìn)程調(diào)度,會(huì)把進(jìn)程分為“運(yùn)行”扬舒、“等待”幾種不同的狀態(tài):
運(yùn)行的進(jìn)程能輪流獲得CPU的資源记舆,“等待”狀態(tài)其實(shí)不難理解,回溯一下呼巴,我們??前文里一直在說(shuō)的就是等待網(wǎng)絡(luò)數(shù)據(jù)的到來(lái)啊泽腮,很好理解,擁有CPU資源的當(dāng)前進(jìn)程執(zhí)行到創(chuàng)建socket語(yǔ)句時(shí)衣赶,操作系統(tǒng)會(huì)創(chuàng)建一個(gè)由文件系統(tǒng)管理的對(duì)象诊赊,這個(gè)對(duì)象包含了發(fā)送緩沖區(qū)、接收緩沖區(qū)府瞄、等待隊(duì)列等碧磅,這個(gè)等待隊(duì)列指向所有需要等待該socket的進(jìn)程。當(dāng)程序執(zhí)行到recv()方法時(shí)(不論是客戶(hù)端還是服務(wù)器應(yīng)用程序都用recv函數(shù)從TCP連接的另一端接收數(shù)據(jù))遵馆,如果等待的數(shù)據(jù)還未到達(dá)鲸郊,當(dāng)前進(jìn)程將會(huì)被加入等待隊(duì)列,它的狀態(tài)也就變成了阻塞货邓。
??此時(shí)秆撮,可能你會(huì)疑問(wèn)?操作系統(tǒng)如何知道數(shù)據(jù)到底對(duì)應(yīng)的是哪個(gè)socket换况?我們知道網(wǎng)絡(luò)數(shù)據(jù)包里都包含了IP和端口號(hào)职辨,操作系統(tǒng)會(huì)給每個(gè)socket對(duì)象的索引維護(hù)一個(gè)端口號(hào)以變快速讀取,從而內(nèi)核通過(guò)端口號(hào)找到對(duì)應(yīng)的socket戈二。
??阻塞一般就是用等待隊(duì)列來(lái)實(shí)現(xiàn)舒裤,這個(gè)等待隊(duì)列是給進(jìn)程添加“等待中”隊(duì)列的引用,將進(jìn)程停止在此處并睡眠下觉吭,直到條件滿(mǎn)足時(shí)腾供,才可通過(guò)此處,繼續(xù)運(yùn)行鲜滩。在睡眠等待期間伴鳖,wake up時(shí),喚起來(lái)檢查條件绒北,條件滿(mǎn)足解除阻塞黎侈,不滿(mǎn)足繼續(xù)睡下去。
所以我們來(lái)總結(jié)一下剛剛說(shuō)的過(guò)程:

  1. 等待數(shù)據(jù):進(jìn)程A擁有CPU資源闷游,當(dāng)它執(zhí)行到socket生成一個(gè)socket對(duì)象(包含接收buffer峻汉、發(fā)送buffer贴汪、等待隊(duì)列),繼續(xù)執(zhí)行到recv()方法時(shí)休吠,操作系統(tǒng)講進(jìn)程A從工作隊(duì)列移到等待隊(duì)列扳埂,其他進(jìn)程繼續(xù)輪流執(zhí)行,A被阻塞瘤礁,不往下執(zhí)行代碼阳懂,也不占用CPU資源。
  2. 數(shù)據(jù)來(lái)了:網(wǎng)絡(luò)數(shù)據(jù)經(jīng)由網(wǎng)卡傳進(jìn)內(nèi)存柜思,網(wǎng)卡驅(qū)動(dòng)中斷信號(hào)岩调,CPU做出響應(yīng),執(zhí)行中斷程序赡盘,socket接收數(shù)據(jù)号枕。
  3. 喚醒進(jìn)程:socket收到數(shù)據(jù)后,操作系統(tǒng)將該socket的等待隊(duì)列中的進(jìn)程A的狀態(tài)改為“運(yùn)行中”陨享,進(jìn)程回到工作隊(duì)列中繼續(xù)執(zhí)行代碼葱淳。由于socket的接收buffer有了數(shù)據(jù),recv()方法返回接收到的數(shù)據(jù)抛姑。
    ??總算是完整梳理了內(nèi)核接收數(shù)據(jù)的全過(guò)程赞厕,但你有發(fā)現(xiàn)問(wèn)題嗎?如何做到監(jiān)視全部的socket定硝?

Select

??任何事情都不可能一蹴而就皿桑,解決問(wèn)題先從笨辦法開(kāi)始——select,它的思路是:維護(hù)一個(gè)名為fds的數(shù)組喷斋,數(shù)組里放了全部需要監(jiān)視的socket對(duì)象唁毒,等待數(shù)據(jù)時(shí),進(jìn)程掛起星爪,任意socket收到數(shù)據(jù)后,進(jìn)程被喚醒粉私⊥缣冢看起來(lái)不難,再捋一下它的過(guò)程:

  1. 等待數(shù)據(jù):維護(hù)一個(gè)名為fds的數(shù)組诺核,數(shù)組里加入所有需要監(jiān)視的socket對(duì)象抄肖。調(diào)用select() 方法,操作系統(tǒng)會(huì)把A加入到數(shù)組中所有socket對(duì)象的等待隊(duì)列中窖杀。
  2. 數(shù)據(jù)來(lái)了:網(wǎng)卡收到數(shù)據(jù)后漓摩,網(wǎng)卡驅(qū)動(dòng)發(fā)出中斷,CPU響應(yīng)入客,啟動(dòng)中斷程序管毙,socket接收數(shù)據(jù)腿椎。
  3. 喚醒進(jìn)程:select()返回,進(jìn)程A被喚醒夭咬,重回工作隊(duì)列啃炸。
    補(bǔ)充
    ??當(dāng)程序調(diào)用select時(shí),內(nèi)核會(huì)先遍歷一遍socket卓舵,如果有一個(gè)以上的socket接收緩沖區(qū)有數(shù)據(jù)南用,那么select直接返回,不會(huì)阻塞掏湾。這也是為什么select的返回值有可能大于1的原因之一裹虫。如果沒(méi)有socket有數(shù)據(jù),進(jìn)程才會(huì)阻塞融击。
    ??監(jiān)視所有的socket是做到了筑公,又有新問(wèn)題來(lái)了:如何得知具體是哪個(gè)socket接收到了數(shù)據(jù)?是需要遍歷一遍fds才能得知的砚嘴,此外十酣,進(jìn)程被喚醒,要從所有的socket對(duì)象的等待隊(duì)列中移除际长,又需要去遍歷fds并移除被喚醒的進(jìn)程耸采,再加上補(bǔ)充情況里說(shuō)的那一次遍歷,簡(jiǎn)直可以湊成遍歷三連工育。所以虾宇,不用我再明說(shuō),你肯定知道了如绸,接下來(lái)嘱朽,是這個(gè)系統(tǒng)需要再次進(jìn)化的時(shí)候了。

epoll

??Select遍歷次數(shù)多怔接,開(kāi)銷(xiāo)大的原因有以下兩點(diǎn):

  1. 維護(hù)等待隊(duì)列和阻塞進(jìn)程關(guān)聯(lián)在一起
解決方案:功能分離

將維護(hù)等待隊(duì)列和阻塞進(jìn)程分離開(kāi)搪泳,在epoll方法中,使用epoll_create創(chuàng)建一個(gè)epoll對(duì)象epdf扼脐,epoll_ctl將需要監(jiān)視的socket加入epdf中岸军,調(diào)用epoll_wait接收數(shù)據(jù)。

  1. 需要遍歷才能知道接收到數(shù)據(jù)的socket
解決方案:就緒列表

epoll維護(hù)一個(gè)rdlist瓦侮,list引用收到socket的對(duì)象艰赞,這樣就避免了為尋找接收到數(shù)據(jù)的socket而去遍歷全部socket的低效。進(jìn)程被喚醒后肚吏,只要獲取rdlist的內(nèi)容方妖,就能獲取接收到數(shù)據(jù)的socket對(duì)象。

epoll的過(guò)程:
  1. 創(chuàng)建epoll對(duì)象epdf:當(dāng)某個(gè)進(jìn)程調(diào)用epoll_create方法時(shí)罚攀,內(nèi)核會(huì)創(chuàng)建一個(gè)epoll對(duì)象epdf(eventpoll)党觅,eventpoll跟socket一樣雌澄,是文件系統(tǒng)中的一員,也有等待隊(duì)列仔役,還維護(hù)了rdlist作為它的成員掷伙。
  2. 維護(hù)監(jiān)視列表:創(chuàng)建epoll對(duì)象后,epoll_ctl方法可以添加和刪除所監(jiān)聽(tīng)的socket又兵,內(nèi)核將eventpoll需要的socket加入監(jiān)視列表任柜。所以,socket收到數(shù)據(jù)后沛厨,中斷程序會(huì)直接操作eventpoll對(duì)象宙地,而不是直接操作進(jìn)程。
  3. 接收數(shù)據(jù):當(dāng)socket接收到數(shù)據(jù)后逆皮,中斷程序會(huì)給rdlist里添加收到數(shù)據(jù)的socket的引用宅粥。eventpoll相當(dāng)于socket和進(jìn)程的中介,socket接收數(shù)據(jù)并不直接影響進(jìn)程电谣,而是通過(guò)rdlist來(lái)改變進(jìn)程的狀態(tài)秽梅。rdlist:有引用,epoll_wait返回剿牺;無(wú)引用企垦,rdlist空,阻塞進(jìn)程A晒来。
  4. 阻塞和喚醒進(jìn)程:
    阻塞:進(jìn)程運(yùn)行到epoll_wait時(shí)钞诡,內(nèi)核將進(jìn)程加入eventpoll的等待隊(duì)列。
    喚醒:socket接收到數(shù)據(jù)后湃崩,中斷程序會(huì)做兩件事:(1)修改rdlist(知道是哪個(gè)socket發(fā)生改變)(2) 喚醒eventpoll中的等待隊(duì)列中的進(jìn)程荧降。
    ??epoll值得配圖一張,引用自知乎羅培羽


    epoll.png
值得思考的兩個(gè)問(wèn)題:
rdlist的數(shù)據(jù)結(jié)構(gòu):

??需要滿(mǎn)足的條件是攒读,能夠快速添加socket朵诫,而且epoll_ctl還要監(jiān)聽(tīng),需要頻繁添加薄扁、刪除socket拗窃,無(wú)疑是雙向鏈表最為合適。

索引結(jié)構(gòu):

??維護(hù)等待隊(duì)列和進(jìn)程阻塞分離泌辫,所以需要一個(gè)便于監(jiān)聽(tīng)管理socket的數(shù)據(jù)結(jié)構(gòu),需要滿(mǎn)足便于查找九默、防止重復(fù)添加震放、方便刪除的數(shù)據(jù)結(jié)構(gòu)。之前二叉樹(shù)家族的文章里講過(guò)的數(shù)據(jù)結(jié)構(gòu)很滿(mǎn)足這個(gè)要求驼修,紅黑樹(shù)是一種自平衡二叉查找樹(shù)殿遂,搜索诈铛、插入和刪除時(shí)間復(fù)雜度都是O(log(N)),效率較好墨礁。epoll使用了紅黑樹(shù)作為索引結(jié)構(gòu)幢竹。

NIO及epoll

NIO

??NIO(new IO) ,這個(gè)new是相當(dāng)于BIO來(lái)說(shuō)的恩静,BIO(Blocking IO)是同步阻塞式IO焕毫,服務(wù)器實(shí)現(xiàn)模式為一個(gè)連接一個(gè)線(xiàn)程,即客戶(hù)端有連接請(qǐng)求時(shí)服務(wù)器端就需要啟動(dòng)一個(gè)線(xiàn)程進(jìn)行處理驶乾,如果這個(gè)連接不做任何事情會(huì)造成不必要的線(xiàn)程開(kāi)銷(xiāo)邑飒。為了降低開(kāi)銷(xiāo)的問(wèn)題,就產(chǎn)生了NIO级乐,同步非阻塞式IO疙咸,服務(wù)器實(shí)現(xiàn)模式為一個(gè)請(qǐng)求一個(gè)線(xiàn)程,即客戶(hù)端發(fā)送的連接請(qǐng)求都會(huì)注冊(cè)到多路復(fù)用器上风科,多路復(fù)用器輪詢(xún)到連接有I/O請(qǐng)求時(shí)才啟動(dòng)一個(gè)線(xiàn)程進(jìn)行處理撒轮。

epoll

??epoll是在select的基礎(chǔ)上,改良了幾個(gè)不夠高效的點(diǎn)贼穆,引用了先進(jìn)的數(shù)據(jù)結(jié)構(gòu)题山,實(shí)現(xiàn)了更高效的多路復(fù)用。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末扮惦,一起剝皮案震驚了整個(gè)濱河市臀蛛,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌崖蜜,老刑警劉巖浊仆,帶你破解...
    沈念sama閱讀 218,284評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異豫领,居然都是意外死亡抡柿,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,115評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)等恐,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)洲劣,“玉大人,你說(shuō)我怎么就攤上這事课蔬〈鸦” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,614評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵二跋,是天一觀的道長(zhǎng)战惊。 經(jīng)常有香客問(wèn)我,道長(zhǎng)扎即,這世上最難降的妖魔是什么吞获? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,671評(píng)論 1 293
  • 正文 為了忘掉前任况凉,我火速辦了婚禮,結(jié)果婚禮上各拷,老公的妹妹穿的比我還像新娘刁绒。我一直安慰自己,他們只是感情好烤黍,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,699評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布知市。 她就那樣靜靜地躺著索抓,像睡著了一般失驶。 火紅的嫁衣襯著肌膚如雪掠手。 梳的紋絲不亂的頭發(fā)上溶浴,一...
    開(kāi)封第一講書(shū)人閱讀 51,562評(píng)論 1 305
  • 那天荆几,我揣著相機(jī)與錄音粗井,去河邊找鬼唧取。 笑死航夺,一個(gè)胖子當(dāng)著我的面吹牛媳叨,可吹牛的內(nèi)容都是我干的腥光。 我是一名探鬼主播,決...
    沈念sama閱讀 40,309評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼糊秆,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼武福!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起痘番,我...
    開(kāi)封第一講書(shū)人閱讀 39,223評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤捉片,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后汞舱,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體伍纫,經(jīng)...
    沈念sama閱讀 45,668評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,859評(píng)論 3 336
  • 正文 我和宋清朗相戀三年昂芜,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了莹规。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,981評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡泌神,死狀恐怖良漱,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情欢际,我是刑警寧澤母市,帶...
    沈念sama閱讀 35,705評(píng)論 5 347
  • 正文 年R本政府宣布,位于F島的核電站损趋,受9級(jí)特大地震影響窒篱,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,310評(píng)論 3 330
  • 文/蒙蒙 一墙杯、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧括荡,春花似錦高镐、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,904評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至邑闲,卻和暖如春算行,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背苫耸。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,023評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工州邢, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人褪子。 一個(gè)月前我還...
    沈念sama閱讀 48,146評(píng)論 3 370
  • 正文 我出身青樓量淌,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親嫌褪。 傳聞我的和親對(duì)象是個(gè)殘疾皇子呀枢,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,933評(píng)論 2 355