Java NIO SelectorProvider與IO多路復(fù)用

最近在學(xué)習(xí)Netty,看了好多資料,也看了一部分<<Netty in action>>這本書,發(fā)現(xiàn)完全不能理解它的設(shè)計,它的組件.

聯(lián)想到Netty主要是一個NIO框架,于是覺得是因為對NIO的了解不夠而導(dǎo)致的.然后就查閱NIO的相關(guān)資料,發(fā)現(xiàn)還是不能理解其原理.

不得不說,Google了很多資料,包括英文的和中文的,大多數(shù)都是NIO的具體用法,而對于其核心組件,比如selector,他們的作用,實現(xiàn)原理,卻并沒有說明.看完網(wǎng)上的介紹之后,讓我更加懵懵噠了.

既然查詢不到結(jié)果,就想自己查看源碼了解其原理.于是查看了Oracle JDK1.8.0_91中和NIO相關(guān)的部分的源碼,以及openjdk1.7的部分源碼.因為Oracle JDK1.8.0_91中,對于一些類的實現(xiàn),并沒有給出,只是給出的.class文件.即使我們可以通過反編譯來獲得,但是終究還是太麻煩.所以這部分源碼,就從openjdk1.7來獲得.

Java NIO SelectorProvider

查看Oracle JDK1.8.0_91源碼時,我們可以看到Selector這個組件,是由SelectorProvider創(chuàng)建的.

我們看一下SelectorProvider.provider()方法的具體實現(xiàn):

查看loadProviderFromProperty()方法和loadProviderAsService()方法的源碼:

我們可以看到,SelectorProvider.provider()方法會在System Property中不存在java.nio.channels.spi.SelectorProvider屬性和不能找到SelectorProvider的實現(xiàn)類時,創(chuàng)建一個默認的sun.nio.ch.DefaultSelectorProvider來作為SelectorProvider.

我們從open jdk7中查看sun.nio.ch.DefaultSelectorProvider的源碼:

open jdk7的源碼中,提供了三個版本的sun.nio.ch.DefaultSelectorProvider的實現(xiàn):

我們這里選擇的是solaris版本的.

sun.nio.ch.DefaultSelectorProvider的源碼中,我們可以看到,如果是linux機器,并且其內(nèi)核版本大于2.6,創(chuàng)建的就是EPollSelectorProvider,否則的話,就創(chuàng)建PollSelectorProvider.

這就是我們今天要介紹的重點-IO多路復(fù)用.

IO多路復(fù)用

IO多路復(fù)用就是我們說的select,poll, epoll,接下來我們會逐個介紹.

Select

基本概念

IO多路復(fù)用是指內(nèi)核一旦發(fā)現(xiàn)進程指定的一個或者多個IO條件準(zhǔn)備讀取程剥,它就通知該進程。IO多路復(fù)用適用如下場合:

  • 當(dāng)客戶處理多個描述字時(一般是交互式輸入和網(wǎng)絡(luò)套接口),必須使用I/O復(fù)用。

  • 當(dāng)一個客戶同時處理多個套接口時,而這種情況是可能的,但很少出現(xiàn)殿遂。

  • 如果一個TCP服務(wù)器既要處理監(jiān)聽套接口诈铛,又要處理已連接套接口,一般也要用到I/O復(fù)用墨礁。

  • 如果一個服務(wù)器即要處理TCP幢竹,又要處理UDP,一般要使用I/O復(fù)用恩静。

  • 如果一個服務(wù)器要處理多個服務(wù)或多個協(xié)議焕毫,一般要使用I/O復(fù)用。

與多進程和多線程技術(shù)相比驶乾,I/O多路復(fù)用技術(shù)的最大優(yōu)勢是系統(tǒng)開銷小邑飒,系統(tǒng)不必創(chuàng)建進程/線程,也不必維護這些進程/線程级乐,從而大大減小了系統(tǒng)的開銷疙咸。

select函數(shù)

該函數(shù)準(zhǔn)許進程指示內(nèi)核等待多個事件中的任何一個發(fā)送,并只在有一個或多個事件發(fā)生或經(jīng)歷一段指定的時間后才喚醒唇牧。函數(shù)原型如下:

**int select(int maxfdp1,fd_set *readset,fd_set *writeset,fd_set exceptset,const struct timeval timeout)

函數(shù)參數(shù)介紹如下:

(1)第一個參數(shù)maxfdp1指定待測試的描述字個數(shù)罕扎,它的值是待測試的最大描述字加1(因此把該參數(shù)命名為maxfdp1)聚唐,描述字0丐重、1、2...maxfdp1-1均將被測試扮惦。因為文件描述符是從0開始的崖蜜。

(2)中間的三個參數(shù)readset豫领、writeset和exceptset指定我們要讓內(nèi)核測試讀舔琅、寫和異常條件的描述字备蚓。如果對某一個的條件不感興趣,就可以把它設(shè)為空指針二跋。struct fd_set可以理解為一個集合扎即,這個集合中存放的是文件描述符,可通過以下四個宏進行設(shè)置:

          void FD_ZERO(fd_set *fdset);           //清空集合

          void FD_SET(int fd, fd_set *fdset);   //將一個給定的文件描述符加入集合之中

          void FD_CLR(int fd, fd_set *fdset);   //將一個給定的文件描述符從集合中刪除

          int FD_ISSET(int fd, fd_set *fdset);   // 檢查集合中指定的文件描述符是否可以讀寫 

(3)timeout告知內(nèi)核等待所指定描述字中的任何一個就緒可花多少時間衫哥。其timeval結(jié)構(gòu)用于指定這段時間的秒數(shù)和微秒數(shù)撤逢。

         struct timeval{

                   long tv_sec;   //seconds

                   long tv_usec;  //microseconds

       };

這個參數(shù)有三種可能:

(1)永遠等待下去:僅在有一個描述字準(zhǔn)備好I/O時才返回蚊荣。為此互例,把該參數(shù)設(shè)置為空指針NULL媳叨。

(2)等待一段固定時間:在有一個描述字準(zhǔn)備好I/O時返回糊秆,但是不超過由該參數(shù)所指向的timeval結(jié)構(gòu)中指定的秒數(shù)和微秒數(shù)痘番。

(3)根本不等待:檢查描述字后立即返回平痰,這稱為輪詢宗雇。為此赔蒲,該參數(shù)必須指向一個timeval結(jié)構(gòu),而且其中的定時器值必須為0腻扇。

基本原理圖

poll

基本知識

poll的機制與select類似,與select在本質(zhì)上沒有多大差別舶沿,管理多個描述符也是進行輪詢,根據(jù)描述符的狀態(tài)進行處理括荡,但是poll沒有最大文件描述符數(shù)量的限制嫉髓。poll和select同樣存在一個缺點就是邑闲,包含大量文件描述符的數(shù)組被整體復(fù)制于用戶態(tài)和內(nèi)核的地址空間之間苫耸,而不論這些文件描述符是否就緒褪子,它的開銷隨著文件描述符數(shù)量的增加而線性增大。

poll函數(shù)

int poll ( struct pollfd * fds, unsigned int nfds, int timeout);

pollfd結(jié)構(gòu)體定義如下:

struct pollfd {

int fd;         /* 文件描述符 */
short events;         /* 等待的事件 */
short revents;       /* 實際發(fā)生了的事件 */
} ; 

每一個pollfd結(jié)構(gòu)體指定了一個被監(jiān)視的文件描述符呀枢,可以傳遞多個結(jié)構(gòu)體硫狞,指示poll()監(jiān)視多個文件描述符晃痴。每個結(jié)構(gòu)體的events域是監(jiān)視該文件描述符的事件掩碼,由用戶來設(shè)置這個域即彪。revents域是文件描述符的操作結(jié)果事件掩碼隶校,內(nèi)核在調(diào)用返回時設(shè)置這個域深胳。events域中請求的任何事件都可能在revents域中返回舞终。合法的事件如下:

  POLLIN         有數(shù)據(jù)可讀。

  POLLRDNORM       有普通數(shù)據(jù)可讀纷宇。

  POLLRDBAND      有優(yōu)先數(shù)據(jù)可讀像捶。

  POLLPRI         有緊迫數(shù)據(jù)可讀作岖。

  POLLOUT            寫數(shù)據(jù)不會導(dǎo)致阻塞痘儡。

  POLLWRNORM       寫普通數(shù)據(jù)不會導(dǎo)致阻塞沉删。

  POLLWRBAND        寫優(yōu)先數(shù)據(jù)不會導(dǎo)致阻塞矾瑰。

  POLLMSGSIGPOLL     消息可用殴穴。

  此外采幌,revents域中還可能返回下列事件:
  POLLER     指定的文件描述符發(fā)生錯誤休傍。

  POLLHUP   指定的文件描述符掛起事件。

  POLLNVAL  指定的文件描述符非法蹲姐。

這些事件在events域中無意義磨取,因為它們在合適的時候總是會從revents中返回。

使用poll()和select()不一樣柴墩,你不需要顯式地請求異常情況報告忙厌。

POLLIN | POLLPRI等價于select()的讀事件,POLLOUT |POLLWRBAND等價于select()的寫事件江咳。POLLIN等價于POLLRDNORM |POLLRDBAND逢净,而POLLOUT則等價于POLLWRNORM。例如,要同時監(jiān)視一個文件描述符是否可讀和可寫汹胃,我們可以設(shè)置 events為POLLIN |POLLOUT婶芭。在poll返回時,我們可以檢查revents中的標(biāo)志,對應(yīng)于文件描述符請求的events結(jié)構(gòu)體。如果POLLIN事件被設(shè)置,則文件描述符可以被讀取而不阻塞。如果POLLOUT被設(shè)置,則文件描述符可以寫入而不導(dǎo)致阻塞。這些標(biāo)志并不是互斥的:它們可能被同時設(shè)置,表示這個文件描述符的讀取和寫入操作都會正常返回而不阻塞。

timeout參數(shù)指定等待的毫秒數(shù)必逆,無論I/O是否準(zhǔn)備好损拢,poll都會返回或舞。timeout指定為負數(shù)值表示無限超時诈豌,使poll()一直掛起直到一個指定事件發(fā)生;timeout為0指示poll調(diào)用立即返回并列出準(zhǔn)備好I/O的文件描述符,但并不等待其它的事件。這種情況下,poll()就像它的名字那樣,一旦選舉出來,立即返回。

成功時,poll()返回結(jié)構(gòu)體中revents域不為0的文件描述符個數(shù);如果在超時前沒有任何事件發(fā)生阐污,poll()返回0隘膘;失敗時钦铁,poll()返回-1黎比,并設(shè)置errno為下列值之一:

  EBADF         一個或多個結(jié)構(gòu)體中指定的文件描述符無效不跟。

  EFAULTfds   指針指向的地址超出進程的地址空間颓帝。

  EINTR      請求的事件之前產(chǎn)生一個信號,調(diào)用可以重新發(fā)起窝革。

  EINVALnfds  參數(shù)超出PLIMIT_NOFILE值工猜。

  ENOMEM       可用內(nèi)存不足箭昵,無法完成請求鼻忠。

epoll

基本知識

epoll是在2.6內(nèi)核中提出的棘催,是之前的select和poll的增強版本。相對于select和poll來說耳标,epoll更加靈活醇坝,沒有描述符限制。epoll使用一個文件描述符管理多個描述符次坡,將用戶關(guān)系的文件描述符的事件存放到內(nèi)核的一個事件表中呼猪,這樣在用戶空間和內(nèi)核空間的copy只需一次。

epoll接口

epoll操作過程需要三個接口砸琅,分別如下:

#include <sys/epoll.h>
int epoll_create(int 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);

(1) int epoll_create(int size);
創(chuàng)建一個epoll的句柄宋距,size用來告訴內(nèi)核這個監(jiān)聽的數(shù)目一共有多大。這個參數(shù)不同于select()中的第一個參數(shù)症脂,給出最大監(jiān)聽的fd+1的值谚赎。需要注意的是淫僻,當(dāng)創(chuàng)建好epoll句柄后,它就是會占用一個fd值壶唤,在linux下如果查看/proc/進程id/fd/雳灵,是能夠看到這個fd的,所以在使用完epoll后闸盔,必須調(diào)用close()關(guān)閉悯辙,否則可能導(dǎo)致fd被耗盡。

(2)int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
epoll的事件注冊函數(shù)迎吵,它不同與select()是在監(jiān)聽事件時告訴內(nèi)核要監(jiān)聽什么類型的事件epoll的事件注冊函數(shù)躲撰,它不同與select()是在監(jiān)聽事件時告訴內(nèi)核要監(jiān)聽什么類型的事件,而是在這里先注冊要監(jiān)聽的事件類型击费。第一個參數(shù)是epoll_create()的返回值拢蛋,第二個參數(shù)表示動作,用三個宏來表示:

EPOLL_CTL_ADD:注冊新的fd到epfd中荡灾;
EPOLL_CTL_MOD:修改已經(jīng)注冊的fd的監(jiān)聽事件瓤狐;
EPOLL_CTL_DEL:從epfd中刪除一個fd;

第三個參數(shù)是需要監(jiān)聽的fd批幌,第四個參數(shù)是告訴內(nèi)核需要監(jiān)聽什么事础锐,struct epoll_event結(jié)構(gòu)如下:

struct epoll_event {
  __uint32_t events;  /* Epoll events */
  epoll_data_t data;  /* User data variable */
};

events可以是以下幾個宏的集合:

EPOLLIN :表示對應(yīng)的文件描述符可以讀(包括對端SOCKET正常關(guān)閉);
EPOLLOUT:表示對應(yīng)的文件描述符可以寫荧缘;
EPOLLPRI:表示對應(yīng)的文件描述符有緊急的數(shù)據(jù)可讀(這里應(yīng)該表示有帶外數(shù)據(jù)到來)皆警;
EPOLLERR:表示對應(yīng)的文件描述符發(fā)生錯誤;
EPOLLHUP:表示對應(yīng)的文件描述符被掛斷截粗;
EPOLLET: 將EPOLL設(shè)為邊緣觸發(fā)(Edge Triggered)模式信姓,這是相對于水平觸發(fā)(Level Triggered)來說的。
EPOLLONESHOT:只監(jiān)聽一次事件绸罗,當(dāng)監(jiān)聽完這次事件之后意推,如果還需要繼續(xù)監(jiān)聽這個socket的話,需要再次把這個socket加入到EPOLL隊列里

(3) int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
  等待事件的產(chǎn)生珊蟀,類似于select()調(diào)用菊值。參數(shù)events用來從內(nèi)核得到事件的集合,maxevents告之內(nèi)核這個events有多大育灸,這個maxevents的值不能大于創(chuàng)建epoll_create()時的size腻窒,參數(shù)timeout是超時時間(毫秒,0會立即返回磅崭,-1將不確定儿子,也有說法說是永久阻塞)。該函數(shù)返回需要處理的事件數(shù)目砸喻,如返回0表示已超時柔逼。

工作模式

epoll對文件描述符的操作有兩種模式:LT(level trigger)和ET(edge trigger)蒋譬。LT模式是默認模式,LT模式與ET模式的區(qū)別如下:

LT模式:當(dāng)epoll_wait檢測到描述符事件發(fā)生并將此事件通知應(yīng)用程序卒落,應(yīng)用程序可以不立即處理該事件羡铲。下次調(diào)用epoll_wait時蜂桶,會再次響應(yīng)應(yīng)用程序并通知此事件儡毕。

ET模式:當(dāng)epoll_wait檢測到描述符事件發(fā)生并將此事件通知應(yīng)用程序,應(yīng)用程序必須立即處理該事件扑媚。如果不處理腰湾,下次調(diào)用epoll_wait時,不會再次響應(yīng)應(yīng)用程序并通知此事件疆股。

ET模式在很大程度上減少了epoll事件被重復(fù)觸發(fā)的次數(shù)费坊,因此效率要比LT模式高。epoll工作在ET模式的時候旬痹,必須使用非阻塞套接口附井,以避免由于一個文件句柄的阻塞讀/阻塞寫操作把處理多個文件描述符的任務(wù)餓死。

參考資料

Linux IO模式及 select永毅、poll沼死、epoll詳解
IO多路復(fù)用之select總結(jié)
IO多路復(fù)用之poll總結(jié)
IO多路復(fù)用之epoll總結(jié)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末健芭,一起剝皮案震驚了整個濱河市慈迈,隨后出現(xiàn)的幾起案子兜看,更是在濱河造成了極大的恐慌细移,老刑警劉巖弧轧,帶你破解...
    沈念sama閱讀 216,496評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異旬牲,居然都是意外死亡,警方通過查閱死者的電腦和手機擂橘,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評論 3 392
  • 文/潘曉璐 我一進店門恼五,熙熙樓的掌柜王于貴愁眉苦臉地迎上來唤冈,“玉大人绘搞,你說我怎么就攤上這事夯辖≥锕樱” “怎么了?”我有些...
    開封第一講書人閱讀 162,632評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長诈嘿。 經(jīng)常有香客問我淳梦,道長爆袍,這世上最難降的妖魔是什么蛮瞄? 我笑而不...
    開封第一講書人閱讀 58,180評論 1 292
  • 正文 為了忘掉前任芹助,我火速辦了婚禮状土,結(jié)果婚禮上斥季,老公的妹妹穿的比我還像新娘。我一直安慰自己谤专,他們只是感情好映之,可當(dāng)我...
    茶點故事閱讀 67,198評論 6 388
  • 文/花漫 我一把揭開白布蠢甲。 她就那樣靜靜地躺著,像睡著了一般寞宫。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上拉鹃,一...
    開封第一講書人閱讀 51,165評論 1 299
  • 那天辈赋,我揣著相機與錄音,去河邊找鬼膏燕。 笑死钥屈,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的坝辫。 我是一名探鬼主播篷就,決...
    沈念sama閱讀 40,052評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼近忙!你這毒婦竟也來了竭业?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,910評論 0 274
  • 序言:老撾萬榮一對情侶失蹤及舍,失蹤者是張志新(化名)和其女友劉穎未辆,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體锯玛,經(jīng)...
    沈念sama閱讀 45,324評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡咐柜,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,542評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了攘残。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片拙友。...
    茶點故事閱讀 39,711評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖肯腕,靈堂內(nèi)的尸體忽然破棺而出献宫,到底是詐尸還是另有隱情,我是刑警寧澤实撒,帶...
    沈念sama閱讀 35,424評論 5 343
  • 正文 年R本政府宣布姊途,位于F島的核電站,受9級特大地震影響知态,放射性物質(zhì)發(fā)生泄漏捷兰。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,017評論 3 326
  • 文/蒙蒙 一负敏、第九天 我趴在偏房一處隱蔽的房頂上張望贡茅。 院中可真熱鬧,春花似錦、人聲如沸顶考。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽驹沿。三九已至艘策,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間渊季,已是汗流浹背朋蔫。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留却汉,地道東北人驯妄。 一個月前我還...
    沈念sama閱讀 47,722評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像合砂,于是被迫代替她去往敵國和親青扔。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,611評論 2 353

推薦閱讀更多精彩內(nèi)容

  • 作者: 一字馬胡 轉(zhuǎn)載標(biāo)志 【2017-11-24】 更新日志 一既穆、Java OIO Java OIO (Jav...
    一字馬胡閱讀 1,350評論 0 12
  • 本文摘抄自linux基礎(chǔ)編程 IO概念 Linux的內(nèi)核將所有外部設(shè)備都可以看做一個文件來操作赎懦。那么我們對與外部設(shè)...
    lintong閱讀 1,581評論 0 4
  • epoll概述 epoll是linux中IO多路復(fù)用的一種機制,I/O多路復(fù)用就是通過一種機制幻工,一個進程可以監(jiān)視多...
    發(fā)仔很忙閱讀 10,886評論 4 35
  • IO多路復(fù)用是指內(nèi)核一旦發(fā)現(xiàn)進程指定的一個或者多個IO條件準(zhǔn)備讀取傅瞻,它就通知該進程踢代。IO多路復(fù)用適用如下場合: 當(dāng)...
    七寸知架構(gòu)閱讀 94,085評論 16 272
  • 同步、異步嗅骄、阻塞胳挎、非阻塞 同步 & 異步 同步與異步是針對多個事件(線程/進程)來說的。 如果事件A需要等待事件B...
    rainybowe閱讀 2,893評論 0 9