日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

【面试】彻底理解 IO多路复用

發(fā)布時間:2025/3/16 编程问答 41 豆豆
生活随笔 收集整理的這篇文章主要介紹了 【面试】彻底理解 IO多路复用 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

目錄

1、什么是IO多路復(fù)用?
2、為什么出現(xiàn)IO多路復(fù)用機(jī)制?
3、IO多路復(fù)用的三種實(shí)現(xiàn)方式
4、select函數(shù)接口
5、select使用示例
6、select缺點(diǎn)
7、poll函數(shù)接口
8、poll使用示例
9、poll缺點(diǎn)
10、epoll函數(shù)接口
11、epoll使用示例
12、epoll缺點(diǎn)
13、epoll LT 與 ET模式的區(qū)別
14、epoll應(yīng)用
15、select/poll/epoll之間的區(qū)別
16、IO多路復(fù)用完整代碼實(shí)現(xiàn)
17、高頻面試題

1、什么是IO多路復(fù)用

「定義」

  • IO多路復(fù)用是一種同步IO模型,實(shí)現(xiàn)一個線程可以監(jiān)視多個文件句柄;一旦某個文件句柄就緒,就能夠通知應(yīng)用程序進(jìn)行相應(yīng)的讀寫操作;沒有文件句柄就緒時會阻塞應(yīng)用程序,交出cpu。多路是指網(wǎng)絡(luò)連接,復(fù)用指的是同一個線程

2、為什么有IO多路復(fù)用機(jī)制?

沒有IO多路復(fù)用機(jī)制時,有BIO、NIO兩種實(shí)現(xiàn)方式,但有一些問題

同步阻塞(BIO)

  • 服務(wù)端采用單線程,當(dāng)accept一個請求后,在recv或send調(diào)用阻塞時,將無法accept其他請求(必須等上一個請求處recv或send完),無法處理并發(fā)

//?偽代碼描述 while(1)?{//?accept阻塞client_fd?=?accept(listen_fd)fds.append(client_fd)for?(fd?in?fds)?{//?recv阻塞(會影響上面的accept)if?(recv(fd))?{//?logic}}?? }
  • 服務(wù)器端采用多線程,當(dāng)accept一個請求后,開啟線程進(jìn)行recv,可以完成并發(fā)處理,但隨著請求數(shù)增加需要增加系統(tǒng)線程,大量的線程占用很大的內(nèi)存空間,并且線程切換會帶來很大的開銷,10000個線程真正發(fā)生讀寫事件的線程數(shù)不會超過20%,每次accept都開一個線程也是一種資源浪費(fèi)

//?偽代碼描述 while(1)?{//?accept阻塞client_fd?=?accept(listen_fd)//?開啟線程read數(shù)據(jù)(fd增多導(dǎo)致線程數(shù)增多)new?Thread?func()?{//?recv阻塞(多線程不影響上面的accept)if?(recv(fd))?{//?logic}}?? }

同步非阻塞(NIO)

  • 服務(wù)器端當(dāng)accept一個請求后,加入fds集合,每次輪詢一遍fds集合recv(非阻塞)數(shù)據(jù),沒有數(shù)據(jù)則立即返回錯誤,每次輪詢所有fd(包括沒有發(fā)生讀寫事件的fd)會很浪費(fèi)cpu

setNonblocking(listen_fd) //?偽代碼描述 while(1)?{//?accept非阻塞(cpu一直忙輪詢)client_fd?=?accept(listen_fd)if?(client_fd?!=?null)?{//?有人連接fds.append(client_fd)}?else?{//?無人連接}??for?(fd?in?fds)?{//?recv非阻塞setNonblocking(client_fd)//?recv?為非阻塞命令if?(len?=?recv(fd)?&&?len?>?0)?{//?有讀寫數(shù)據(jù)//?logic}?else?{無讀寫數(shù)據(jù)}}?? }

IO多路復(fù)用(現(xiàn)在的做法)

  • 服務(wù)器端采用單線程通過select/epoll等系統(tǒng)調(diào)用獲取fd列表,遍歷有事件的fd進(jìn)行accept/recv/send,使其能支持更多的并發(fā)連接請求

fds?=?[listen_fd] //?偽代碼描述 while(1)?{//?通過內(nèi)核獲取有讀寫事件發(fā)生的fd,只要有一個則返回,無則阻塞//?整個過程只在調(diào)用select、poll、epoll這些調(diào)用的時候才會阻塞,accept/recv是不會阻塞for?(fd?in?select(fds))?{if?(fd?==?listen_fd)?{client_fd?=?accept(listen_fd)fds.append(client_fd)}?elseif?(len?=?recv(fd)?&&?len?!=?-1)?{?//?logic}}?? }

3、IO多路復(fù)用的三種實(shí)現(xiàn)方式

  • select

  • poll

  • epoll

4、select函數(shù)接口

#include?<sys/select.h> #include?<sys/time.h>#define?FD_SETSIZE?1024 #define?NFDBITS?(8?*?sizeof(unsigned?long)) #define?__FDSET_LONGS?(FD_SETSIZE/NFDBITS)//?數(shù)據(jù)結(jié)構(gòu)?(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 )??????????????????????????????//?返回值就緒描述符的數(shù)目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()?{/**?這里進(jìn)行一些初始化的設(shè)置,*?包括socket建立,地址的設(shè)置等,*/fd_set?read_fs,?write_fs;struct?timeval?timeout;int?max?=?0;??//?用于記錄最大的fd,在輪詢中時刻更新即可//?初始化比特位FD_ZERO(&read_fs);FD_ZERO(&write_fs);int?nfds?=?0;?//?記錄就緒的事件,可以減少遍歷的次數(shù)while?(1)?{//?阻塞獲取//?每次需要把fd從用戶態(tài)拷貝到內(nèi)核態(tài)nfds?=?select(max?+?1,?&read_fd,?&write_fd,?NULL,?&timeout);//?每次需要遍歷所有fd,判斷有無讀寫事件發(fā)生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缺點(diǎn)

  • 單個進(jìn)程所打開的FD是有限制的,通過FD_SETSIZE設(shè)置,默認(rèn)1024

  • 每次調(diào)用select,都需要把fd集合從用戶態(tài)拷貝到內(nèi)核態(tài),這個開銷在fd很多時會很大

  • 對socket掃描時是線性掃描,采用輪詢的方法,效率較低(高并發(fā)時)

7、poll函數(shù)接口

poll與select相比,只是沒有fd的限制,其它基本一樣

#include?<poll.h> //?數(shù)據(jù)結(jié)構(gòu) struct?pollfd?{int?fd;?????????????????????????//?需要監(jiān)視的文件描述符short?events;???????????????????//?需要內(nèi)核監(jiān)視的事件short?revents;??????????????????//?實(shí)際發(fā)生的事件 };//?API int?poll(struct?pollfd?fds[],?nfds_t?nfds,?int?timeout);

8、poll使用示例

//?先宏定義長度 #define?MAX_POLLFD_LEN?4096??int?main()?{/**?在這里進(jìn)行一些初始化的操作,*?比如初始化數(shù)據(jù)和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;??//?隊列的實(shí)際長度,是一個隨時更新的,也可以自定義其他的int?timeout?=?0;int?current_size?=?max;while?(1)?{//?阻塞獲取//?每次需要把fd從用戶態(tài)拷貝到內(nèi)核態(tài)nfds?=?poll(fds,?max+1,?timeout);if?(fds[0].revents?&?POLLRDNORM)?{//?這里處理accept事件connfd?=?accept(listenfd);//將新的描述符添加到讀描述符集合中}//?每次需要遍歷所有fd,判斷有無讀寫事件發(fā)生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缺點(diǎn)

  • 每次調(diào)用poll,都需要把fd集合從用戶態(tài)拷貝到內(nèi)核態(tài),這個開銷在fd很多時會很大

  • 對socket掃描時是線性掃描,采用輪詢的方法,效率較低(高并發(fā)時)

10、epoll函數(shù)接口

#include?<sys/epoll.h>//?數(shù)據(jù)結(jié)構(gòu) //?每一個epoll對象都有一個獨(dú)立的eventpoll結(jié)構(gòu)體 //?用于存放通過epoll_ctl方法向epoll對象中添加進(jìn)來的事件 //?epoll_wait檢查是否有事件發(fā)生時,只需要檢查eventpoll對象中的rdlist雙鏈表中是否有epitem元素即可 struct?eventpoll?{/*紅黑樹的根節(jié)點(diǎn),這顆樹中存儲著所有添加到epoll中的需要監(jiān)控的事件*/struct?rb_root??rbr;/*雙鏈表中則存放著將要通過epoll_wait返回給用戶的滿足條件的事件*/struct?list_head?rdlist; };//?APIint?epoll_create(int?size);?//?內(nèi)核中間加一個?ep?對象,把所有需要監(jiān)聽的?socket?都放到?ep?對象中 int?epoll_ctl(int?epfd,?int?op,?int?fd,?struct?epoll_event?*event);?//?epoll_ctl?負(fù)責(zé)把?socket?增加、刪除到內(nèi)核紅黑樹 int?epoll_wait(int?epfd,?struct?epoll_event?*?events,?int?maxevents,?int?timeout);//?epoll_wait?負(fù)責(zé)檢測可讀隊列,沒有可讀?socket?則阻塞進(jìn)程

11、epoll使用示例

int?main(int?argc,?char*?argv[]) {/**?在這里進(jìn)行一些初始化的操作,*?比如初始化數(shù)據(jù)和socket等。*///?內(nèi)核中創(chuàng)建ep對象epfd=epoll_create(256);//?需要監(jiān)聽的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);//?接收新連接寫到內(nèi)核對象中epoll_ctl(epfd,EPOLL_CTL_ADD,connfd,&ev);}?else?if?(events[i].events&EPOLLIN)?{//?這里處理read事件read(sockfd,?BUF,?MAXLINE);//讀完后準(zhǔn)備寫epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev);}?else?if(events[i].events&EPOLLOUT)?{//?這里處理write事件write(sockfd,?BUF,?n);//寫完后準(zhǔn)備讀epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev);}}}return?0; }

12、epoll缺點(diǎn)

  • epoll只能工作在linux下

13、epoll LT 與 ET模式的區(qū)別

  • epoll有EPOLLLT和EPOLLET兩種觸發(fā)模式,LT是默認(rèn)的模式,ET是“高速”模式。

  • LT模式下,只要這個fd還有數(shù)據(jù)可讀,每次 epoll_wait都會返回它的事件,提醒用戶程序去操作

  • ET模式下,它只會提示一次,直到下次再有數(shù)據(jù)流入之前都不會再提示了,無論fd中是否還有數(shù)據(jù)可讀。所以在ET模式下,read一個fd的時候一定要把它的buffer讀完,或者遇到EAGAIN錯誤

14、epoll應(yīng)用

  • redis

  • nginx

15、select/poll/epoll之間的區(qū)別

16、完整代碼示例

https://github.com/caijinlin/learning-pratice/tree/master/linux/io

17、高頻面試題

  • 什么是IO多路復(fù)用?

  • nginx/redis 所使用的IO模型是什么?

  • select、poll、epoll之間的區(qū)別

  • epoll 水平觸發(fā)(LT)與 邊緣觸發(fā)(ET)的區(qū)別?

有道無術(shù),術(shù)可成;有術(shù)無道,止于術(shù)

歡迎大家關(guān)注Java之道公眾號

好文章,我在看??

新人創(chuàng)作打卡挑戰(zhàn)賽發(fā)博客就能抽獎!定制產(chǎn)品紅包拿不停!

總結(jié)

以上是生活随笔為你收集整理的【面试】彻底理解 IO多路复用的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。