I/O多路轉接之select(只負責等)網絡
系統提供select函數來實現多路複用輸入/輸出模型。
數據結構
傳向select的參數告訴內核:多線程
1)咱們所關心的文件描述符。socket
參數nfds是須要監視的最大的文件描述符值+1;
ide
2)對每一個描述符,咱們所關心的狀態。函數
rdset,wrset,exset分別對應於須要檢測的可讀文件描述符的集合,可寫文件描述符的集合及異常文件描述符的集合。測試
監視的文件描述符分爲三類set,每一種對應等待不一樣的事件。readfds中列出的文件描述符被監視是否有數據可供讀取(若是讀取操做完成則不會 阻 塞)。writefds中列出的文件描述符則被監視是否寫入操做完成而不阻塞後,exceptfds中列出的文件描述符則被監視是否發生異常,或者無 法控制的數據是否可用(這些狀態僅僅應用於套接字)。這三類set能夠是NULL,這種狀況下select()不監視這一類事件。spa
下面的宏提供了處理這三種描述詞組的方式:線程
FD_ZERO移除指定set中的全部文件描述符。每一次調用select()以前都應該先調用它;指針
FD_CLR則從指定的set中移除一個文件描述符;
FD_SET添加一個文件描述符到指定的set中;
FD_ISSET測試一個文件描述符是否指定set的一部分。若是文件描述符在set中則返回一個非0整數,不在則返0,FD_ISSET在調用select() 返回以後使用,測試指定的文件描述符是否準備好相關動做。
3)咱們要等待多長時間。(咱們能夠等待無限長的時間,等待固定的一段時間,或者根本就不等待)
timeout參數是一個指向timeval結構體的指針,timeval定義以下:
struct timeval結構用於描述一段時間長度,若是在這個時間內,須要監視的描述符沒有事件發生則函數返回,返回值爲0。
從 select函數返回後,內核告訴咱們如下信息:
1)已經作好準備的描述符的個數。
2)對於三種條件哪些描述符已經作好準備。(讀,寫,異常)
執成功則返回件描述詞狀態已改變的個數;若是返回0表明在描述詞狀態改變前已超過timeout時間,沒有返回;
當有錯誤發時則返回-1,錯誤緣由存於errno,此時參數readfds,writefds,exceptfds和timeout的值變成不可預測。錯誤值可能爲:EBADF 件描述詞爲效的或該件已關閉 ,EINTR 此調被信號所中斷 EINVAL 參數n 爲負值,ENOMEM 核內存不。
#include<stdio.h> #include<stdlib.h> #include<string.h> #include<errno.h> #include<sys/socket.h> #include<sys/types.h> #include<netinet/in.h> #include<arpa/inet.h> int fds[64]; const fds_nums=sizeof(fds)/sizeof(fds[0]); static int startup(const char* _ip,int _port) { int sock=socket(AF_INET,SOCK_STREAM,0); if(sock<0) { perror("socket"); exit(2); } int opt=1; setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt)); struct sockaddr_in local; local.sin_family=AF_INET; local.sin_port=htons(_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,5)<0) { perror("listen"); exit(4); } return sock; } static 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 i=0; for(i=0;i<fds_nums;++i) { fds[i]=-1; } int listen_sock=startup(argv[1],atoi(argv[2])); fd_set rset; FD_ZERO(&rset); FD_SET(listen_sock,&rset); fds[0]=listen_sock; int done=0; while(!done) { int max_fd=-1; for(i=0;i<fds_nums;++i) { if(fds[i]>0) { FD_SET(fds[i],&rset); max_fd=max_fd<fds[i]?fds[i]:max_fd; } } struct timeval timeout={0,0}; switch(select(max_fd+1,&rset,NULL,NULL,NULL/*&timeout*/)) { case 0: printf("timeout..\n"); break; case 1: perror("select"); break; default: for(i=0;i<fds_nums;++i) { if(i=0&& FD_ISSET(listen_sock,&rset)) { struct sockaddr_in peer; socklen_t len=sizeof(peer); int new_fd=accept(listen_sock,(struct sockaddr*)&peer,&len); if(new_fd>0) { printf("get a new client:socket->%s:%d\n",inet_ntoa(peer.sin_addr),ntohs(peer.sin_port)); int j=0; for(j=0;j<fds_nums;j++) { if(fds[i]==-1) { fds[j]=new_fd; break; } } if(j==fds_nums) { close(new_fd); } } else { if(FD_ISSET(fds[i],&rset)) { char buf[1024]; memset(buf,'\0',sizeof(buf)); ssize_t _s=read(fds[i],buf,sizeof(buf)-1); if(_s>0) { printf("client# %s\n",buf); } else if(_s==0) { printf("client close...\n"); close(fds[i]); } else { perror("read"); } } } } } break; } } return 0; }
select優勢:
(1)相較於以前多線程的方法,使用select不用建立線程,更方便
(2)select目前幾乎在全部的平臺上都支持,其良好跨平臺支持也是它的一個優勢
select缺點:
(1)每次調用select,都須要把fd集合從用戶態拷貝到內核態,這個開銷在fd不少時會很大
(2)同時每次調用select都須要在內核遍歷傳遞進來的全部fd,這個開銷在fd不少時也很大
(3)可以監視的文件描述符的數量存在最大限制,在Linux上通常爲1024,由於它依賴於文件系統
(4)select()所維護的文件描述符的數據結構,隨着文件描述符數量的增大,其複製的開銷也線性增加。
(5)因爲網絡響應時間的延遲使得大量TCP鏈接處於非活躍狀態,但調用select()會對全部socket進行一次線性掃描,這也會有一些開銷