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

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 运维知识 > linux >内容正文

linux

Linux的I/O多路复用机制之--selectpoll

發(fā)布時(shí)間:2025/7/14 linux 27 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Linux的I/O多路复用机制之--selectpoll 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

1. Linux下的五種I/O模型

1)阻塞I/O(blocking I/O)
2)非阻塞I/O?(nonblocking I/O)
3) I/O復(fù)用(select 和poll)?(I/O multiplexing)
4)信號(hào)驅(qū)動(dòng)I/O?(signal driven I/O (SIGIO))
5)異步I/O?(asynchronous I/O (the POSIX aio_functions))

前四種都是同步,只有最后一種才是異步IO。

五種I/O模型的比較:

2.多路復(fù)用--select

系統(tǒng)提供select函數(shù)來(lái)實(shí)現(xiàn)多路復(fù)用輸入/輸出模型。select系統(tǒng)調(diào)用是用來(lái)讓我們的程序監(jiān)視多個(gè)文件句柄的狀態(tài)變化的。程序會(huì)停在select這里等待,直到被監(jiān)視的文件句柄有一個(gè)或多個(gè)發(fā)生了狀態(tài)改變。關(guān)于文件句柄,其實(shí)就是一個(gè)整數(shù),我們最熟悉的句柄是0、1、2三個(gè),0是標(biāo)準(zhǔn)輸入,1是標(biāo)準(zhǔn)輸出,2是標(biāo)準(zhǔn)錯(cuò)誤輸出。0、1、2是整數(shù)表示的,對(duì)應(yīng)的FILE *結(jié)構(gòu)的表示就是stdin、stdout、stderr。

select函數(shù)

int?select(int?maxfd,fd_set?*rdset,fd_set?*wrset,?\??fd_set?*exset,struct?timeval?*timeout);

參數(shù)說(shuō)明:

參數(shù)maxfd是需要監(jiān)視的最大的文件描述符值+1;rdset,wrset,exset分別對(duì)應(yīng)于需要檢測(cè)的可讀文件描述符的集合,可寫文件描述符的集合及異常文件描述符的集合。struct timeval結(jié)構(gòu)用于描述一段時(shí)間長(zhǎng)度,如果在這個(gè)時(shí)間內(nèi),需要監(jiān)視的描述符沒有事件發(fā)生則函數(shù)返回,返回值為0。

下面的宏提供了處理這三種描述詞組的方式:
FD_CLR(inr fd,fd_set* set);用來(lái)清除描述詞組set中相關(guān)fd 的位
FD_ISSET(int fd,fd_set *set);用來(lái)測(cè)試描述詞組set中相關(guān)fd 的位是否為真
FD_SET(int fd,fd_set*set);用來(lái)設(shè)置描述詞組set中相關(guān)fd的位
FD_ZERO(fd_set *set);用來(lái)清除描述詞組set的全部位

參數(shù)timeout為結(jié)構(gòu)timeval,用來(lái)設(shè)置select()的等待時(shí)間,其結(jié)構(gòu)定義如下:

struct?timeval?? {??time_t?tv_sec;//second??time_t?tv_usec;//minisecond?? };

如果參數(shù)timeout設(shè)為:

NULL,則表示select()沒有timeout,select將一直被阻塞,直到某個(gè)文件描述符上發(fā)生了事件。

0:僅檢測(cè)描述符集合的狀態(tài),然后立即返回,并不等待外部事件的發(fā)生。

特定的時(shí)間值:如果在指定的時(shí)間段里沒有事件發(fā)生,select將超時(shí)返回。

函數(shù)返回值:

執(zhí)行成功則返回文件描述詞狀態(tài)已改變的個(gè)數(shù),如果返回0代表在描述詞狀態(tài)改變前已超過timeout時(shí)間,沒有返回;當(dāng)有錯(cuò)誤發(fā)生時(shí)則返回-1,錯(cuò)誤原因存于errno,此時(shí)參數(shù)readfds,writefds,exceptfds和timeout的值變成不可預(yù)測(cè)。

理解select模型:

理解select模型的關(guān)鍵在于理解fd_set,為說(shuō)明方便,取fd_set長(zhǎng)度為1字節(jié),fd_set中的每一bit可以對(duì)應(yīng)一個(gè)文件描述符fd。則1字節(jié)長(zhǎng)的fd_set最大可以對(duì)應(yīng)8個(gè)fd。

(1)執(zhí)行fd_set set; FD_ZERO(&set);則set用位表示是0000,0000。

(2)若fd=5,執(zhí)行FD_SET(fd,&set);后set變?yōu)?001,0000(第5位置為1)

(3)若再加入fd=2,fd=1,則set變?yōu)?001,0011

(4)執(zhí)行select(6,&set,0,0,0)阻塞等待

(5)若fd=1,fd=2上都發(fā)生可讀事件,則select返回,此時(shí)set變?yōu)?000,0011。注意:沒有事件發(fā)生的fd=5被清空。

基于上面的討論,可以輕松得出select模型的特點(diǎn):

  • 可監(jiān)控的文件描述符個(gè)數(shù)取決與sizeof(fd_set)的值。

  • 將fd加入select監(jiān)控集的同時(shí),還要再使用一個(gè)數(shù)據(jù)結(jié)構(gòu)array保存放到select監(jiān)控集中的fd,一是用于再select 返回后,array作為源數(shù)據(jù)和fd_set進(jìn)行FD_ISSET判斷。二是select返回后會(huì)把以前加入的但并無(wú)事件發(fā)生的fd清空,則每次開始 select前都要重新從array取得fd逐一加入(FD_ZERO最先),掃描array的同時(shí)取得fd最大值maxfd,用于select的第一個(gè) 參數(shù)。

  • 可見select模型必須在select前循環(huán)array(加fd,取maxfd),select返回后循環(huán)array(FD_ISSET判斷是否有時(shí)間發(fā)生)。


select缺點(diǎn):

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

(2)同時(shí)每次調(diào)用select都需要在內(nèi)核遍歷傳遞進(jìn)來(lái)的所有fd,這個(gè)開銷在fd很多時(shí)也很大

(3)select支持的文件描述符數(shù)量太小,默認(rèn)是1024


3.多路復(fù)用--poll

poll與select非常相似,不同之處在于,select使用三個(gè)位圖來(lái)表示三種不同的事件,而poll使用一個(gè) pollfd的指針實(shí)現(xiàn)。

poll函數(shù)

#include?<poll.h> int?poll?(struct?pollfd?*fds,?unsigned?int?nfds,?int?timeout);

參數(shù)說(shuō)明:

fds:是一個(gè)struct pollfd結(jié)構(gòu)類型的數(shù)組,其結(jié)構(gòu)如下:

struct?pollfd?{????int?fd;?/*?file?descriptor?*/short?events;?/*?requested?events?to?watch?*/short?revents;?/*?returned?events?witnessed?*/};

該結(jié)構(gòu)用于存放需要檢測(cè)其狀態(tài)的Socket描述符;每當(dāng)調(diào)用這個(gè)函數(shù)之后,系統(tǒng)不會(huì)清空這個(gè)數(shù)組,操作起來(lái)比較方便;特別是對(duì)于 socket連接比較多的情況下,在一定程度上可以提高處理的效率;這一點(diǎn)與select()函數(shù)不同,調(diào)用select()函數(shù)之后,select() 函數(shù)會(huì)清空它所檢測(cè)的socket描述符集合,導(dǎo)致每次調(diào)用select()之前都必須把socket描述符重新加入到待檢測(cè)的集合中;因 此,select()函數(shù)適合于只檢測(cè)一個(gè)socket描述符的情況,而poll()函數(shù)適合于大量socket描述符的情況;

nfds:nfds_t類型的參數(shù),用于標(biāo)記數(shù)組fds中的結(jié)構(gòu)體元素的總數(shù)量;

timeout:是poll函數(shù)調(diào)用阻塞的時(shí)間,單位:毫秒;如果timeout>0那么poll()函數(shù)會(huì)阻塞timeout所指定的毫秒時(shí)間長(zhǎng)度之后返回。如果timeout==0,那么 poll() 函數(shù)立即返回而不阻塞,如果timeout==INFTIM,那么poll() 函數(shù)會(huì)一直阻塞下去,直到所檢測(cè)的socket描述符上的感興趣的事件發(fā) 生是才返回,如果感興趣的事件永遠(yuǎn)不發(fā)生,那么poll()就會(huì)永遠(yuǎn)阻塞下去;

返回值:

>0:數(shù)組fds中準(zhǔn)備好讀、寫或出錯(cuò)狀態(tài)的那些socket描述符的總數(shù)量;

==0:數(shù)組fds中沒有任何socket描述符準(zhǔn)備好讀、寫,或出錯(cuò);此時(shí)poll超時(shí),超時(shí)時(shí)間是timeout毫秒;換句話說(shuō),如果所檢測(cè)的 socket描述符上沒有任何事件發(fā)生的話,

-1: ?poll函數(shù)調(diào)用失敗,同時(shí)會(huì)自動(dòng)設(shè)置全局變量errno;


poll() 函數(shù)的功能和返回值的含義與 select() 函數(shù)的功能和返回值的含義是完全一樣的,兩者之間的差別就是內(nèi)部實(shí)現(xiàn)方式不一樣。


4.select實(shí)例之網(wǎng)絡(luò)服務(wù)器(poll實(shí)現(xiàn)類似)

服務(wù)器端

#include?<stdio.h> #include?<stdlib.h> #include?<sys/types.h> #include?<sys/socket.h> #include?<netinet/in.h> #include?<arpa/inet.h> #include?<sys/select.h> #include?<string.h>#define?_MAX_LISTEN_?5 #define?_MAX_SIZE_?10 #define?_BUF_SIZE_?1024int?fd_arr[_MAX_SIZE_]; int?max_fd?=?-1;static?void?init_fd_arr() {int?i?=?0;for(;?i?<?_MAX_SIZE_;?++i){fd_arr[i]?=?-1;} }static?int?add_fd_arr(int?fd) {int?i?=?0;for(;?i?<?_MAX_SIZE_;?++i){if(fd_arr[i]?==?-1){fd_arr[i]?=?fd;return?0;}}return?1; }static?void?remove_fd_arr(int?fd) {int?i?=?0;for(;?i?<?_MAX_SIZE_;?++i){if(fd_arr[i]?==?fd){fd_arr[i]?=?-1;break;}} }static?void?reload_fd_arr(fd_set*?pset) {int?i?=?0;max_fd?=?-1;for(;?i?<?_MAX_SIZE_;?++i){if(fd_arr[i]?!=?-1){FD_SET(fd_arr[i],?pset);if(fd_arr[i]?>?max_fd)max_fd?=?fd_arr[i];}} }static?printf_msg(int?i,?const?char*?msg) {printf("client?%d?#?%s\n",?fd_arr[i],?msg); }void?Usage(const?char*?proc) {printf("%s?usage:?[ip]?[port]\n",?proc); }int?startup(const?char*?_ip,?const?char*?_port) {int?sock?=?socket(AF_INET,?SOCK_STREAM,?0);if(sock?<?0){perror("socket");exit(1);}int?opt?=?1;if?(setsockopt(sock,?SOL_SOCKET,?SO_REUSEADDR,?&opt,?sizeof(opt))?<?0)?{perror("setsockopt");exit(2);}??struct?sockaddr_in?local;local.sin_family?=?AF_INET;local.sin_port?=?htons(atoi(_port));local.sin_addr.s_addr?=?inet_addr(_ip);if(bind(sock,?(struct?sockaddr*)&local,?sizeof(local))?<?0){perror("bind");exit(3);}if(listen(sock,?_MAX_LISTEN_)?<?0){perror("listen");exit(4);}return?sock; }int?main(int?argc,?char*?argv[]) {if(argc?!=?3){Usage(argv[0]);return?1;}int?listen_sock?=?startup(argv[1],?argv[2]);init_fd_arr();add_fd_arr(listen_sock);fd_set?rfds;FD_ZERO(&rfds);struct?timeval?tv?=?{5,?0};while(1){reload_fd_arr(&rfds);int?fds?=?select(max_fd+1,?&rfds,?NULL,?NULL,?NULL);switch(fds){case?-1:perror("select");exit(5);break;case?0:printf("time?out\n");break;default:{int?index?=?0;for(;?index?<?_MAX_SIZE_;?++index){if(fd_arr[index]?==?listen_sock?&&?FD_ISSET(fd_arr[index],?&rfds))?//new?accept{struct?sockaddr_in?peer;socklen_t?len?=?sizeof(peer);int?new_fd?=?accept(listen_sock,?(struct?sockaddr*?)&peer,?&len);if(new_fd?<?0){perror("accept");exit(6);}printf("get?a?new?client?%d?->?ip:?%s?port:?%d\n",?new_fd,?inet_ntoa(peer.sin_addr),?ntohs(peer.sin_port));if(1?==?add_fd_arr(new_fd)){perror("fd_arr?is?full\n");close(new_fd);exit(7);}continue;}if(fd_arr[index]?!=?-1?&&?FD_ISSET(fd_arr[index],?&rfds))?//new?read?fd{char?buf[_BUF_SIZE_];memset(buf,?'\0',?sizeof(buf));ssize_t?_s?=?read(fd_arr[index],?buf,?sizeof(buf)-1);if(_s?>?0){buf[_s]?=?'\0';printf_msg(index,?buf);}else?if(_s?==?0)?//client?closed{printf("client?%d?is?closed...\n",?fd_arr[index]);FD_CLR(fd_arr[index],?&rfds);close(fd_arr[index]);?//?must?before?remove!!!remove_fd_arr(fd_arr[index]);}else{}}}}break;}}return?0; }

客戶端

#include?<stdio.h> #include?<sys/types.h> #include?<sys/socket.h> #include?<netinet/in.h> #include?<arpa/inet.h> #include?<unistd.h> #include?<string.h> #include?<stdlib.h>void?Usage(const?char*?proc) {printf("usage:?%s?[ip]?[port]\n",?proc); }int?main(int?argc,?char*?argv[]) {if(argc?!=?3){Usage(argv[0]);exit(1);}int?conn_sock?=?socket(AF_INET,?SOCK_STREAM,?0);struct?sockaddr_in?conn;conn.sin_family?=?AF_INET;conn.sin_port?=?htons(atoi(argv[2]));conn.sin_addr.s_addr?=?inet_addr(argv[1]);if(connect(conn_sock,?(const?struct?sockaddr*)&conn,?sizeof(conn))?<?0){perror("connect");exit(2);}char?buf[1024];memset(buf,?'\0',?sizeof(buf));while(1){printf("please?enter?#?");fflush(stdout);ssize_t?_s?=?read(0,?buf,?sizeof(buf)-1);if(_s?>?0){buf[_s-1]?=?'\0';write(conn_sock,?buf,?strlen(buf));}}return?0; }

程序演示

使用Telnet測(cè)試

使用客戶端測(cè)試

使用瀏覽器測(cè)試


轉(zhuǎn)載于:https://blog.51cto.com/11418774/1836323

總結(jié)

以上是生活随笔為你收集整理的Linux的I/O多路复用机制之--selectpoll的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。

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