Linux的I/O多路复用机制之--selectpoll
1. Linux下的五種I/O模型
1)阻塞I/O(blocking I/O)
2)非阻塞I/O?(nonblocking I/O)
3) I/O復用(select 和poll)?(I/O multiplexing)
4)信號驅(qū)動I/O?(signal driven I/O (SIGIO))
5)異步I/O?(asynchronous I/O (the POSIX aio_functions))
(前四種都是同步,只有最后一種才是異步IO。)
五種I/O模型的比較:
2.多路復用--select
系統(tǒng)提供select函數(shù)來實現(xiàn)多路復用輸入/輸出模型。select系統(tǒng)調(diào)用是用來讓我們的程序監(jiān)視多個文件句柄的狀態(tài)變化的。程序會停在select這里等待,直到被監(jiān)視的文件句柄有一個或多個發(fā)生了狀態(tài)改變。關(guān)于文件句柄,其實就是一個整數(shù),我們最熟悉的句柄是0、1、2三個,0是標準輸入,1是標準輸出,2是標準錯誤輸出。0、1、2是整數(shù)表示的,對應(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ù)說明:
參數(shù)maxfd是需要監(jiān)視的最大的文件描述符值+1;rdset,wrset,exset分別對應(yīng)于需要檢測的可讀文件描述符的集合,可寫文件描述符的集合及異常文件描述符的集合。struct timeval結(jié)構(gòu)用于描述一段時間長度,如果在這個時間內(nèi),需要監(jiān)視的描述符沒有事件發(fā)生則函數(shù)返回,返回值為0。
下面的宏提供了處理這三種描述詞組的方式:
FD_CLR(inr fd,fd_set* set);用來清除描述詞組set中相關(guān)fd 的位
FD_ISSET(int fd,fd_set *set);用來測試描述詞組set中相關(guān)fd 的位是否為真
FD_SET(int fd,fd_set*set);用來設(shè)置描述詞組set中相關(guān)fd的位
FD_ZERO(fd_set *set);用來清除描述詞組set的全部位
參數(shù)timeout為結(jié)構(gòu)timeval,用來設(shè)置select()的等待時間,其結(jié)構(gòu)定義如下:
struct?timeval?? {??time_t?tv_sec;//second??time_t?tv_usec;//minisecond?? };如果參數(shù)timeout設(shè)為:
NULL,則表示select()沒有timeout,select將一直被阻塞,直到某個文件描述符上發(fā)生了事件。
0:僅檢測描述符集合的狀態(tài),然后立即返回,并不等待外部事件的發(fā)生。
特定的時間值:如果在指定的時間段里沒有事件發(fā)生,select將超時返回。
函數(shù)返回值:
執(zhí)行成功則返回文件描述詞狀態(tài)已改變的個數(shù),如果返回0代表在描述詞狀態(tài)改變前已超過timeout時間,沒有返回;當有錯誤發(fā)生時則返回-1,錯誤原因存于errno,此時參數(shù)readfds,writefds,exceptfds和timeout的值變成不可預測。
理解select模型:
理解select模型的關(guān)鍵在于理解fd_set,為說明方便,取fd_set長度為1字節(jié),fd_set中的每一bit可以對應(yīng)一個文件描述符fd。則1字節(jié)長的fd_set最大可以對應(yīng)8個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返回,此時set變?yōu)?000,0011。注意:沒有事件發(fā)生的fd=5被清空。
基于上面的討論,可以輕松得出select模型的特點:
可監(jiān)控的文件描述符個數(shù)取決與sizeof(fd_set)的值。
將fd加入select監(jiān)控集的同時,還要再使用一個數(shù)據(jù)結(jié)構(gòu)array保存放到select監(jiān)控集中的fd,一是用于再select 返回后,array作為源數(shù)據(jù)和fd_set進行FD_ISSET判斷。二是select返回后會把以前加入的但并無事件發(fā)生的fd清空,則每次開始 select前都要重新從array取得fd逐一加入(FD_ZERO最先),掃描array的同時取得fd最大值maxfd,用于select的第一個 參數(shù)。
可見select模型必須在select前循環(huán)array(加fd,取maxfd),select返回后循環(huán)array(FD_ISSET判斷是否有時間發(fā)生)。
select缺點:
(1)每次調(diào)用select,都需要把fd集合從用戶態(tài)拷貝到內(nèi)核態(tài),這個開銷在fd很多時會很大
(2)同時每次調(diào)用select都需要在內(nèi)核遍歷傳遞進來的所有fd,這個開銷在fd很多時也很大
(3)select支持的文件描述符數(shù)量太小,默認是1024
3.多路復用--poll
poll與select非常相似,不同之處在于,select使用三個位圖來表示三種不同的事件,而poll使用一個 pollfd的指針實現(xiàn)。
poll函數(shù)
#include?<poll.h> int?poll?(struct?pollfd?*fds,?unsigned?int?nfds,?int?timeout);參數(shù)說明:
fds:是一個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)用于存放需要檢測其狀態(tài)的Socket描述符;每當調(diào)用這個函數(shù)之后,系統(tǒng)不會清空這個數(shù)組,操作起來比較方便;特別是對于 socket連接比較多的情況下,在一定程度上可以提高處理的效率;這一點與select()函數(shù)不同,調(diào)用select()函數(shù)之后,select() 函數(shù)會清空它所檢測的socket描述符集合,導致每次調(diào)用select()之前都必須把socket描述符重新加入到待檢測的集合中;因 此,select()函數(shù)適合于只檢測一個socket描述符的情況,而poll()函數(shù)適合于大量socket描述符的情況;
nfds:nfds_t類型的參數(shù),用于標記數(shù)組fds中的結(jié)構(gòu)體元素的總數(shù)量;
timeout:是poll函數(shù)調(diào)用阻塞的時間,單位:毫秒;如果timeout>0那么poll()函數(shù)會阻塞timeout所指定的毫秒時間長度之后返回。如果timeout==0,那么 poll() 函數(shù)立即返回而不阻塞,如果timeout==INFTIM,那么poll() 函數(shù)會一直阻塞下去,直到所檢測的socket描述符上的感興趣的事件發(fā) 生是才返回,如果感興趣的事件永遠不發(fā)生,那么poll()就會永遠阻塞下去;
返回值:
>0:數(shù)組fds中準備好讀、寫或出錯狀態(tài)的那些socket描述符的總數(shù)量;
==0:數(shù)組fds中沒有任何socket描述符準備好讀、寫,或出錯;此時poll超時,超時時間是timeout毫秒;換句話說,如果所檢測的 socket描述符上沒有任何事件發(fā)生的話,
-1: ?poll函數(shù)調(diào)用失敗,同時會自動設(shè)置全局變量errno;
poll() 函數(shù)的功能和返回值的含義與 select() 函數(shù)的功能和返回值的含義是完全一樣的,兩者之間的差別就是內(nèi)部實現(xiàn)方式不一樣。
4.select實例之網(wǎng)絡(luò)服務(wù)器(poll實現(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測試
使用客戶端測試
使用瀏覽器測試
轉(zhuǎn)載于:https://blog.51cto.com/11418774/1836323
總結(jié)
以上是生活随笔為你收集整理的Linux的I/O多路复用机制之--selectpoll的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Vertica集群扩容实验过程记录
- 下一篇: 第46条:for-each循环优先于传统