好比說你從某寶下單買了幾個東西,這幾個東西分別由N個快遞員分別給你送過來。在某一時刻,你開始等快遞。
對於select/poll,就是你在睡覺的時候,收到一條短信「你有快遞到了,取一下」,但不知道發送方是誰(但必定是那N個快遞員中的某人/某幾我的給你發的),因此你必須挨個給那N個快遞員分別打個電話,問他們,是否是個人快遞已經到了。
至於select/poll的區別,相似於你和快遞員都分別有兩個手機號,一個移動,一個聯通,其區別就在於你用哪一個手機號給他們打的問題。
對於epoll,是你收到那條短信的時候,看到了發送方的電話號碼,你就能夠直接給他打電話,問他在哪兒,你好去去快遞。 html
做者:starsnow1982
連接:https://www.zhihu.com/question/21233763/answer/25314598
來源:知乎
著做權歸做者全部。商業轉載請聯繫做者得到受權,非商業轉載請註明出處。 linux
仍是收快遞,select/poll/epoll分別對應快遞公司A/B/C,映射關係以下: 編程
差異以下: windows
固然,100%映射生活例子是不可能的,好比上面的映射關係實際上沒有嚴格區分事件和文件描述符,這二者實際上在各個接口中的表現也不太同樣,只是不太好映射了. 服務器
做者:Ender
連接:https://www.zhihu.com/question/21233763/answer/359650461
來源:知乎
著做權歸做者全部。商業轉載請聯繫做者得到受權,非商業轉載請註明出處。 網絡
下面引用知乎一書焚城的回答再次鞏固一下IO模型數據結構
- 阻塞IO, 給女神發一條短信, 說我來找你了, 而後就默默的一直等着女神下樓, 這個期間除了等待你不會作其餘事情, 屬於備胎作法.
- 非阻塞IO, 給女神發短信, 若是不回, 接着再發, 一直髮到女神下樓, 這個期間你除了發短信等待不會作其餘事情, 屬於專注作法.
- IO多路複用, 是找一個宿管大媽來幫你監視下樓的女生, 這個期間你能夠些其餘的事情. 例如能夠順便看看其餘妹子,玩玩王者榮耀, 上個廁所等等. IO複用又包括 select, poll, epoll 模式. 那麼它們的區別是什麼?
3.1 select大媽 每個女生下樓, select大媽都不知道這個是否是你的女神, 她須要一個一個詢問, 而且select大媽能力還有限, 最多一次幫你監視1024個妹子
3.2 poll大媽不限制盯着女生的數量, 只要是通過宿舍樓門口的女生, 都會幫你去問是否是你女神
3.3 epoll大媽不限制盯着女生的數量, 而且也不須要一個一個去問. 那麼如何作呢? epoll大媽會爲每一個進宿舍樓的女生臉上貼上一個大字條,上面寫上女生本身的名字, 只要女生下樓了, epoll大媽就知道這個是否是你女神了, 而後大媽再通知你.
上面這些同步IO有一個共同點就是, 當女神走出宿舍門口的時候, 你已經站在宿舍門口等着女神的, 此時你屬於阻塞狀態併發
接下來是異步IO的狀況
你告訴女神我來了, 而後你就去王者榮耀了, 一直到女神下樓了, 發現找不見你了, 女神再給你打電話通知你, 說我下樓了, 你在哪呢? 這時候你纔來到宿舍門口. 此時屬於逆襲作法異步
首先引用levin的回答讓咱們理清楚五種IO模型socket
1.阻塞I/O模型
老李去火車站買票,排隊三天買到一張退票。
耗費:在車站吃喝拉撒睡 3天,其餘事一件沒幹。
2.非阻塞I/O模型
老李去火車站買票,隔12小時去火車站問有沒有退票,三天後買到一張票。耗費:往返車站6次,路上6小時,其餘時間作了好多事。
3.I/O複用模型
1.select/poll
老李去火車站買票,委託黃牛,而後每隔6小時電話黃牛詢問,黃牛三天內買到票,而後老李去火車站交錢領票。
耗費:往返車站2次,路上2小時,黃牛手續費100元,打電話17次
2.epoll
老李去火車站買票,委託黃牛,黃牛買到後即通知老李去領,而後老李去火車站交錢領票。
耗費:往返車站2次,路上2小時,黃牛手續費100元,無需打電話
4.信號驅動I/O模型
老李去火車站買票,給售票員留下電話,有票後,售票員電話通知老李,而後老李去火車5.異步I/O模型
老李去火車站買票,給售票員留下電話,有票後,售票員電話通知老李並快遞送票上門。
耗費:往返車站1次,路上1小時,免黃牛費100元,無需打電話站交錢領票。
耗費:往返車站2次,路上2小時,免黃牛費100元,無需打電話
1同2的區別是:本身輪詢
2同3的區別是:委託黃牛
3同4的區別是:電話代替黃牛
4同5的區別是:電話通知是自取仍是送票上門
阻塞(blocking)、非阻塞(non-blocking):能夠簡單理解爲須要作一件事能不能當即獲得返回應答,若是不能當即得到返回,須要等待,
那就阻塞了(進程或線程就阻塞在那了,不能作其它事情),不然就能夠理解爲非阻塞(在等待的過程當中能夠作其它事情)。
同步(synchronous)、異步(asynchronous): 你老是作完一件再去作另外一件,無論是否須要時間等待,這就是同步(就是在發出一個功能
調用時,在沒有獲得結果以前,該調用就不返回,即此時不能作下一件事情);異步則反之,你能夠同時作幾件事,並不是必定須要一件事作
完再作另外一件事(當一個異步過程調用發出後,調用者不能馬上獲得結果,此時能夠接着作其它事情)。同步簡單理解成一問一答同步進行,
異步能夠簡單理解爲沒必要等一個問題有了答案再去問另外一個問題,儘管問,有答了再通知你。
阻塞和同步:
有人會把阻塞調用和同步調用等同起來,實際上他是不一樣的。對於同步調用來講,不少時候當前線程仍是激活的,只是從邏輯上當前函數
沒有返回而已。 例如,咱們在socket中調用recv函數,若是緩衝區中沒有數據,這個函數就會一直等待,直到有數據才返回。而此時,當
前線程還會繼續處理各類各樣的消息。
針對網絡IO的操做,能夠分紅兩個階段,準備階段和操做階段。
1,準備階段是判斷是否可以操做(即等待數據是否可用),在內核進程完成的;
2,操做階段則執行實際的IO調用,數據從內核緩衝區拷貝到用戶進程緩衝區。
好比對於一個read操做發生時,它會經歷下面兩個階段:
A, 等待數據準備,數據是否拷貝到內核緩衝區;
B, 將數據從內核拷貝到用戶進程空間
上面兩點比較重要,注意理解。
《Unix網絡編程卷1:套接字聯網API》(即UNP)中第六章對unix 系統將IO模型分爲五類:阻塞IO,非阻塞IO,IO複用,信號驅動,異步IO。
一、阻塞IO:在準備階段即同步阻塞,應用進程調用I/O操做時阻塞,只有等待要操做的數據準備好,並複製到應用進程的緩衝區中才返回;
二、非阻塞IO:當應用進程要調用的I/O操做會致使該進程進入阻塞狀態時,該I/O調用返回一個錯誤,通常狀況下,應用進程須要利用輪詢的方式
來檢測某個操做是否就緒。數據就緒後,實際的I/O操做會等待數據複製到應用進程的緩衝區中之後才返回;
三、IO複用:多路IO共用一個同步阻塞接口,任意IO可操做均可激活IO操做,這是對阻塞IO的改進(主要是select和poll、epoll,關鍵是能實現同時對
多個IO端口進行監聽)。此時阻塞發生在select/poll的系統調用上,而不是阻塞在實際的I/O系統調用上。IO多路複用的高級之處在於:它能同時等
待多個文件描述符,而這些文件描述符(套接字描述符)其中的任意一個進入讀就緒狀態,select等函數就能夠返回。
四、信號驅動IO:註冊一個IO信號事件,在數據可操做時經過SIGIO信號通知線程,這應該算是一種異步機制;
以上四種模型在第一階段即判斷是否可操做階段各不相同,但一旦數據可操做,則切換到同步阻塞模式下執行IO操做,因此都算是同步IO。
五、異步IO: 應用進程通知內核開始一個異步I/O操做,並讓內核在整個操做(包含將數據從內核複製到應該進程的緩衝區)完成後通知應用進程。
根據上面所說的IO操做的兩個階段,能夠把上面的I/O模型進行以下歸類:
a,阻塞IO:在兩個階段上面都是阻塞的;
b,非阻塞IO:在第1階段,程序不斷的輪詢直到數據準備好,第2階段仍是阻塞的;
c,IO複用:在第1階段,當一個或者多個IO準備就緒時,通知程序,第2階段仍是阻塞的,在第1階段仍是輪詢實現的,只是全部的IO都集中在一個地方,這個地方進行輪詢;
d,信號IO:當數據準備完畢的時候,信號通知程序數據準備完畢,第2階段阻塞;
e,異步IO:1,2都不阻塞,異步IO模型 好比 windows之上的iocp,linux AIO,詳情點這裏;
結果以下圖(圖來自UNP)。
阻塞式I/O模型、非阻塞式I/O模型、I/O複用模型,這三種模型的區別在於第一階段(阻塞式I/O阻塞在I/O操做上,非阻塞式I/O輪詢,
I/O複用阻塞在select/poll/epoll上),第二階段都是同樣的,即這裏的阻塞不阻塞體如今第一階段,從這方面來講I/O複用類型也
能夠歸類到阻塞式I/O,它與阻塞式I/O的區別在於阻塞的系統調用不一樣。而異步I/O的兩個階段都不會阻塞進程。
其中POSIX將IO只分紅了同步IO、異步IO兩種模型。
同步I/O操做:實際的I/O操做將致使請求進程阻塞,直到I/O操做完成。
異步I/O操做:實際的I/O操做不致使請求進程阻塞。
由此,前面分類中:阻塞式I/O,非阻塞式I/O,I/O複用,信號驅動I/O模型都屬於同步I/O,由於第二階段的數據複製都是阻塞的。
而只有前面定義的異步I/O模型與這裏的異步I/O操做
同步或者異步I/O主要是指訪問數據的機制(即實際I/O操做的完成方式),同步通常指主動請求並等待I/O操做完畢的方式,I/O操做
未完成前,會致使應用進程掛起;而異步是指用戶進程觸發IO操做之後便開始作本身的事情,而當IO操做已經完成的時候會獲得IO
完成的通知(異步的特色就是通知),這可使進程在數據讀寫時也不阻塞。
阻塞或者非阻塞I/O主要是指I/O操做第一階段的完成方式(進程訪問的數據若是還沒有就緒),即數據還未準備好的時候,應用進程
的表現,若是這裏進程掛起,則爲阻塞I/O,不然爲非阻塞I/O。說白了就是阻塞和非阻塞是針對於進程在訪問數據的時候,根據IO
操做的就緒狀態來採起的不一樣方式,說白了是一種讀取或者寫入操做函數的實現方式,阻塞方式下讀取或者寫入函數將一直等待,
而非阻塞方式下,讀取或者寫入函數會當即返回一個狀態值。
若是一個I/O流進來,咱們就開啓一個進程處理這個I/O流。那麼假設如今有一百萬個I/O流進來,那咱們就須要開啓一百萬個進程一一對應處理這些I/O流(——這就是傳統意義下的多進程併發處理)。思考一下,一百萬個進程,你的CPU佔有率會多高,這個實現方式及其的不合理。因此人們提出了I/O多路複用這個模型,一個線程,經過記錄I/O流的狀態來同時管理多個I/O,能夠提升服務器的吞吐能力。
I/O multiplexing 也就是咱們所說的I/O多路複用,可是這個翻譯真的很不生動,因此我更喜歡將它拆開,變成 I/O multi plexing
multi意味着多,而plex意味着叢(叢:彙集,許多事物湊在一塊兒。),那麼字面上來看I/O multiplexing 就是將多個I/O湊在一塊兒。就像下面這張圖的前半部分同樣,中間的那條線就是咱們的單個線程,它經過記錄傳入的每個I/O流的狀態來同時管理多個IO。
multiplexing
I/O多路複用模型
咱們來分析一下上面這張圖
其實多路複用的實現有多種方式:select、poll、epoll
先理解一下select這個函數的形參都是什麼
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
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);
// 檢查集合中指定的文件描述符是否能夠讀寫
#define FILE "/dev/input/mouse0" int main(void) { int fd = -1; int sele_ret = -1; fd_set Fd_set; struct timeval time = {0}; char buf[10] = {0}; //打開設備文件 fd = open(FILE, O_RDONLY); if (-1 == fd) { perror("open error"); exit(-1); } //構建多路複用IO FD_ZERO(&Fd_set); //清除所有fd FD_SET(0, &Fd_set); //添加標準輸入 FD_SET(fd, &Fd_set); //添加鼠標 time.tv_sec = 10; //設置阻塞超時時間爲10秒鐘 time.tv_usec = 0; sele_ret = select(fd+1, &Fd_set, NULL, NULL, &time); if (0 > sele_ret) { perror("select error"); exit(-1); } else if (0 == sele_ret) { printf("無數據輸入,等待超時.\n"); } else { if (FD_ISSET(0, &Fd_set)) //監聽獲得獲得的結果如果鍵盤,則讓去讀取鍵盤的數據 { memset(buf, 0, sizeof(buf)); read(0, buf, sizeof(buf)/2); printf("讀取鍵盤的內容是: %s.\n", buf); } if (FD_ISSET(fd, &Fd_set)) //監聽獲得獲得的結果如果鼠標,則去讀取鼠標的數據 { memset(buf, 0, sizeof(buf)); read(fd, buf, sizeof(buf)/2); printf("讀取鼠標的內容是: %s.\n", buf); } } //關閉鼠標設備文件 close(fd); return 0; }
先理解一下poll這個函數的形參是什麼
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
struct pollfd { int fd; //文件描述符 short events; //請求的事件(請求哪一種操做) short revents; //返回的事件 };
後兩個參數都與select的第一和最後一個參數概念同樣,就不細講了
#define FILE "/dev/input/mouse0" int main(void) { int fd = -1; int poll_ret = 0; struct pollfd poll_fd[2] = {0}; char buf[100] = {0}; //打開設備文件 fd = open(FILE, O_RDONLY); if (-1 == fd) { perror("open error"); exit(-1); } //構建多路複用IO poll_fd[0].fd = 0; //鍵盤 poll_fd[0].events = POLLIN; //定義請求的事件爲讀數據 poll_fd[1].fd = fd; //鼠標 poll_fd[1].events = POLLIN; //定義請求的事件爲讀數據 int time = 10000; //定義超時時間爲10秒鐘 poll_ret = poll(poll_fd, fd+1, time); if (0 > poll_ret) { perror("poll error"); exit(-1); } else if (0 == poll_ret) { printf("阻塞超時.\n"); } else { if (poll_fd[0].revents == poll_fd[0].events) //監聽獲得獲得的結果如果鍵盤,則讓去讀取鍵盤的數據 { memset(buf, 0, sizeof(buf)); read(0, buf, sizeof(buf)/2); printf("讀取鍵盤的內容是: %s.\n", buf); } if (poll_fd[1].revents == poll_fd[1].events) //監聽獲得獲得的結果如果鼠標,則去讀取鼠標的數據 { memset(buf, 0, sizeof(buf)); read(fd, buf, sizeof(buf)/2); printf("讀取鼠標的內容是: %s.\n", buf); } } //關閉文件 close(fd); return 0; }
epoll操做過程當中會用到的重要函數
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);
EPOLL_CTL_ADD:註冊新的fd到epfd中;
EPOLL_CTL_MOD:修改已經註冊的fd的監聽事件;
EPOLL_CTL_DEL:從epfd中刪除一個fd;
第三參數爲監聽的fd,第四個參數是告訴內核要監聽什麼事
a. 從用戶空間將fd_set拷貝到內核空間
b. 註冊回調函數
c. 調用其對應的poll方法
d. poll方法會返回一個描述讀寫是否就緒的mask掩碼,根據這個mask掩碼給fd_set賦值。
e. 若是遍歷完全部的fd都沒有返回一個可讀寫的mask掩碼,就會讓select的進程進入休眠模式,直到發現可讀寫的資源後,從新喚醒等待隊列上休眠的進程。若是在規定時間內都沒有喚醒休眠進程,那麼進程會被喚醒從新得到CPU,再去遍歷一次fd。
f. 將fd_set從內核空間拷貝到用戶空間
缺點:兩次拷貝耗時、輪詢全部fd耗時,支持的文件描述符過小
優勢:跨平臺支持
優勢:鏈接數(也就是文件描述符)沒有限制(鏈表存儲)
缺點:大量拷貝,水平觸發(當報告了fd沒有被處理,會重複報告,很耗性能)
LT:延遲處理,當檢測到描述符事件通知應用程序,應用程序不當即處理該事件。那麼下次會再次通知應用程序此事件。
ET:當即處理,當檢測到描述符事件通知應用程序,應用程序會當即處理。
ET模式減小了epoll被重複觸發的次數,效率比LT高。咱們在使用ET的時候,必須採用非阻塞套接口,避免某文件句柄在阻塞讀或阻塞寫的時候將其餘文件描述符的任務餓死
a. 當調用epoll_wait函數的時候,系統會建立一個epoll對象,每一個對象有一個evenpoll類型的結構體與之對應,結構體成員結構以下。
rbn,表明將要經過epoll_ctl向epll對象中添加的事件。這些事情都是掛載在紅黑樹中。
rdlist,裏面存放的是將要發生的事件
b. 文件的fd狀態發生改變,就會觸發fd上的回調函數
c. 回調函數將相應的fd加入到rdlist,致使rdlist不空,進程被喚醒,epoll_wait繼續執行。
d. 有一個事件轉移函數——ep_events_transfer,它會將rdlist的數據拷貝到txlist上,並將rdlist的數據清空。
e. ep_send_events函數,它掃描txlist的每一個數據,調用關聯fd對應的poll方法去取fd中較新的事件,將取得的事件和對應的fd發送到用戶空間。若是fd是LT模式的話,會被txlist的該數據從新放回rdlist,等待下一次繼續觸發調用。
只有存在大量的空閒鏈接和不活躍的鏈接的時候,使用epoll的效率纔會比select/poll高