【面试】彻底理解 IO多路复用
目錄
1、什么是IO多路復用?
2、為什么出現IO多路復用機制?
3、IO多路復用的三種實現方式
4、select函數接口
5、select使用示例
6、select缺點
7、poll函數接口
8、poll使用示例
9、poll缺點
10、epoll函數接口
11、epoll使用示例
12、epoll缺點
13、epoll LT 與 ET模式的區別
14、epoll應用
15、select/poll/epoll之間的區別
16、IO多路復用完整代碼實現
17、高頻面試題
1、什么是IO多路復用
「定義」
IO多路復用是一種同步IO模型,實現一個線程可以監視多個文件句柄;一旦某個文件句柄就緒,就能夠通知應用程序進行相應的讀寫操作;沒有文件句柄就緒時會阻塞應用程序,交出cpu。多路是指網絡連接,復用指的是同一個線程
2、為什么有IO多路復用機制?
沒有IO多路復用機制時,有BIO、NIO兩種實現方式,但有一些問題
同步阻塞(BIO)
服務端采用單線程,當accept一個請求后,在recv或send調用阻塞時,將無法accept其他請求(必須等上一個請求處recv或send完),無法處理并發
服務器端采用多線程,當accept一個請求后,開啟線程進行recv,可以完成并發處理,但隨著請求數增加需要增加系統線程,大量的線程占用很大的內存空間,并且線程切換會帶來很大的開銷,10000個線程真正發生讀寫事件的線程數不會超過20%,每次accept都開一個線程也是一種資源浪費
同步非阻塞(NIO)
服務器端當accept一個請求后,加入fds集合,每次輪詢一遍fds集合recv(非阻塞)數據,沒有數據則立即返回錯誤,每次輪詢所有fd(包括沒有發生讀寫事件的fd)會很浪費cpu
IO多路復用(現在的做法)
服務器端采用單線程通過select/epoll等系統調用獲取fd列表,遍歷有事件的fd進行accept/recv/send,使其能支持更多的并發連接請求
3、IO多路復用的三種實現方式
select
poll
epoll
4、select函數接口
#include?<sys/select.h> #include?<sys/time.h>#define?FD_SETSIZE?1024 #define?NFDBITS?(8?*?sizeof(unsigned?long)) #define?__FDSET_LONGS?(FD_SETSIZE/NFDBITS)//?數據結構?(bitmap) typedef?struct?{unsigned?long?fds_bits[__FDSET_LONGS]; }?fd_set;//?API int?select(int?max_fd,?fd_set?*readset,?fd_set?*writeset,?fd_set?*exceptset,?struct?timeval?*timeout )??????????????????????????????//?返回值就緒描述符的數目FD_ZERO(int?fd,?fd_set*?fds)???//?清空集合 FD_SET(int?fd,?fd_set*?fds)????//?將給定的描述符加入集合 FD_ISSET(int?fd,?fd_set*?fds)??//?判斷指定描述符是否在集合中? FD_CLR(int?fd,?fd_set*?fds)????//?將給定的描述符從文件中刪除??5、select使用示例
int?main()?{/**?這里進行一些初始化的設置,*?包括socket建立,地址的設置等,*/fd_set?read_fs,?write_fs;struct?timeval?timeout;int?max?=?0;??//?用于記錄最大的fd,在輪詢中時刻更新即可//?初始化比特位FD_ZERO(&read_fs);FD_ZERO(&write_fs);int?nfds?=?0;?//?記錄就緒的事件,可以減少遍歷的次數while?(1)?{//?阻塞獲取//?每次需要把fd從用戶態拷貝到內核態nfds?=?select(max?+?1,?&read_fd,?&write_fd,?NULL,?&timeout);//?每次需要遍歷所有fd,判斷有無讀寫事件發生for?(int?i?=?0;?i?<=?max?&&?nfds;?++i)?{if?(i?==?listenfd)?{--nfds;//?這里處理accept事件FD_SET(i,?&read_fd);//將客戶端socket加入到集合中}if?(FD_ISSET(i,?&read_fd))?{--nfds;//?這里處理read事件}if?(FD_ISSET(i,?&write_fd))?{--nfds;//?這里處理write事件}}}6、select缺點
單個進程所打開的FD是有限制的,通過FD_SETSIZE設置,默認1024
每次調用select,都需要把fd集合從用戶態拷貝到內核態,這個開銷在fd很多時會很大
對socket掃描時是線性掃描,采用輪詢的方法,效率較低(高并發時)
7、poll函數接口
poll與select相比,只是沒有fd的限制,其它基本一樣
#include?<poll.h> //?數據結構 struct?pollfd?{int?fd;?????????????????????????//?需要監視的文件描述符short?events;???????????????????//?需要內核監視的事件short?revents;??????????????????//?實際發生的事件 };//?API int?poll(struct?pollfd?fds[],?nfds_t?nfds,?int?timeout);8、poll使用示例
//?先宏定義長度 #define?MAX_POLLFD_LEN?4096??int?main()?{/**?在這里進行一些初始化的操作,*?比如初始化數據和socket等。*/int?nfds?=?0;pollfd?fds[MAX_POLLFD_LEN];memset(fds,?0,?sizeof(fds));fds[0].fd?=?listenfd;fds[0].events?=?POLLRDNORM;int?max??=?0;??//?隊列的實際長度,是一個隨時更新的,也可以自定義其他的int?timeout?=?0;int?current_size?=?max;while?(1)?{//?阻塞獲取//?每次需要把fd從用戶態拷貝到內核態nfds?=?poll(fds,?max+1,?timeout);if?(fds[0].revents?&?POLLRDNORM)?{//?這里處理accept事件connfd?=?accept(listenfd);//將新的描述符添加到讀描述符集合中}//?每次需要遍歷所有fd,判斷有無讀寫事件發生for?(int?i?=?1;?i?<?max;?++i)?{?????if?(fds[i].revents?&?POLLRDNORM)?{?sockfd?=?fds[i].fdif?((n?=?read(sockfd,?buf,?MAXLINE))?<=?0)?{//?這里處理read事件if?(n?==?0)?{close(sockfd);fds[i].fd?=?-1;}}?else?{//?這里處理write事件?????}if?(--nfds?<=?0)?{break;???????}???}}}9、poll缺點
每次調用poll,都需要把fd集合從用戶態拷貝到內核態,這個開銷在fd很多時會很大
對socket掃描時是線性掃描,采用輪詢的方法,效率較低(高并發時)
10、epoll函數接口
#include?<sys/epoll.h>//?數據結構 //?每一個epoll對象都有一個獨立的eventpoll結構體 //?用于存放通過epoll_ctl方法向epoll對象中添加進來的事件 //?epoll_wait檢查是否有事件發生時,只需要檢查eventpoll對象中的rdlist雙鏈表中是否有epitem元素即可 struct?eventpoll?{/*紅黑樹的根節點,這顆樹中存儲著所有添加到epoll中的需要監控的事件*/struct?rb_root??rbr;/*雙鏈表中則存放著將要通過epoll_wait返回給用戶的滿足條件的事件*/struct?list_head?rdlist; };//?APIint?epoll_create(int?size);?//?內核中間加一個?ep?對象,把所有需要監聽的?socket?都放到?ep?對象中 int?epoll_ctl(int?epfd,?int?op,?int?fd,?struct?epoll_event?*event);?//?epoll_ctl?負責把?socket?增加、刪除到內核紅黑樹 int?epoll_wait(int?epfd,?struct?epoll_event?*?events,?int?maxevents,?int?timeout);//?epoll_wait?負責檢測可讀隊列,沒有可讀?socket?則阻塞進程11、epoll使用示例
int?main(int?argc,?char*?argv[]) {/**?在這里進行一些初始化的操作,*?比如初始化數據和socket等。*///?內核中創建ep對象epfd=epoll_create(256);//?需要監聽的socket放到ep中epoll_ctl(epfd,EPOLL_CTL_ADD,listenfd,&ev);while(1)?{//?阻塞獲取nfds?=?epoll_wait(epfd,events,20,0);for(i=0;i<nfds;++i)?{if(events[i].data.fd==listenfd)?{//?這里處理accept事件connfd?=?accept(listenfd);//?接收新連接寫到內核對象中epoll_ctl(epfd,EPOLL_CTL_ADD,connfd,&ev);}?else?if?(events[i].events&EPOLLIN)?{//?這里處理read事件read(sockfd,?BUF,?MAXLINE);//讀完后準備寫epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev);}?else?if(events[i].events&EPOLLOUT)?{//?這里處理write事件write(sockfd,?BUF,?n);//寫完后準備讀epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev);}}}return?0; }12、epoll缺點
epoll只能工作在linux下
13、epoll LT 與 ET模式的區別
epoll有EPOLLLT和EPOLLET兩種觸發模式,LT是默認的模式,ET是“高速”模式。
LT模式下,只要這個fd還有數據可讀,每次 epoll_wait都會返回它的事件,提醒用戶程序去操作
ET模式下,它只會提示一次,直到下次再有數據流入之前都不會再提示了,無論fd中是否還有數據可讀。所以在ET模式下,read一個fd的時候一定要把它的buffer讀完,或者遇到EAGAIN錯誤
14、epoll應用
redis
nginx
15、select/poll/epoll之間的區別
16、完整代碼示例
https://github.com/caijinlin/learning-pratice/tree/master/linux/io
17、高頻面試題
什么是IO多路復用?
nginx/redis 所使用的IO模型是什么?
select、poll、epoll之間的區別
epoll 水平觸發(LT)與 邊緣觸發(ET)的區別?
有道無術,術可成;有術無道,止于術
歡迎大家關注Java之道公眾號
好文章,我在看??
新人創作打卡挑戰賽發博客就能抽獎!定制產品紅包拿不停!總結
以上是生活随笔為你收集整理的【面试】彻底理解 IO多路复用的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: VitrualBox、vagrant、h
- 下一篇: 从一线技术人员到阿里合伙人,主导了去“I