您好,登錄后才能下訂單哦!
多路復用之select
之前在套接字編程中我們用了多線程和多進程的方法來編寫,用它們編寫的好處自然是穩定,而卻非常耗資源,在前面的高級I/O博客中說到了另外一種方式那便是高效的多路復用方式了,它會一次等待多個滿足條件的文件描述符,這里先介紹第一種多路復用方式select后面還會介紹比它更好的epoll和最好的多路復用方式epoll。
select的多路復用方式是這樣的:它需要一個buf來存放需要監聽的文件描述符,先要把所關心的文件描述符放入到所對應的讀事件集合或者寫事件集合中,一旦其中發生變化會以參數反應出來。
先來看看select的函數
/* According to POSIX.1-2001 */ #include <sys/select.h> /* According to earlier standards */ #include <sys/time.h> #include <sys/types.h> #include <unistd.h> int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout); void FD_CLR(int fd, fd_set *set); int FD_ISSET(int fd, fd_set *set); void FD_SET(int fd, fd_set *set); void FD_ZERO(fd_set *set); struct timeval { long tv_sec; /* seconds */ long tv_usec; /* microseconds */ };
nfds:是所要監控的文件描述符的個數加一,注意,一定要加一!!!
fd_set *readfds:表示監控的readfds集,這個fd_set是有上限的,我的機子上是128,那么表示最多能監控128*8=1024個。readfds是輸入輸出型的參數,它的每個bit為會對應監控一個文件描述符,select是阻塞式的等待事件的發生的,當某個文件描述符就緒則把它對應的bit為置1,而其它的都要清空,所以每次在select之前都要先執行emty(下面會說到),和set(下面會說到)。
writeds:寫事件文件描述符集
exceptfds:錯誤事件文件描述符集
timeout:設置等待事件,等待事件到了可以做其他的事情,比如提示。。
FD_CLR清空所設置關心的文件描述符集
FD_ISSET:用于檢查該文件描述符是否在對應集合中
FD_SET:用于將所關心的文件描述符添加入對應集合中
FD_ZERO:將對應文件描述符集中的所有bit位置0
下面是我對select的工作流程理解
下面是一個使用select實現的TCPserver的簡單代碼
1 #include<stdio.h> 2 #include<sys/types.h> 3 #include<sys/socket.h> 4 #include<sys/select.h> 5 #include<arpa/inet.h> 6 #include<netinet/in.h> 7 #include<string.h> 8 #include<unistd.h> 9 10 11 int fd[64]; //select需要創建一個用于存放socket文件描述符的buf 12 void usage(char *proc) 13 { 14 printf("%s+[ip]+[port]\n",proc); 15 } 16 17 int listen_sock(const char*ip,int port) 18 //創建listen套接字前面的socket博客中已經詳細說明了 19 { 20 int sock=socket(AF_INET,SOCK_STREAM,0); 21 if(sock<0) 22 { 23 perror("socket"); 24 } 25 struct sockaddr_in server; 26 server.sin_family=AF_INET; 27 server.sin_addr.s_addr=inet_addr(ip); 28 server.sin_port=htons(port); 29 if(bind(sock,(struct sockaddr*)&server,sizeof(server))<0) 30 { 31 32 perror("bind"); 33 } 34 if(listen(sock,10)<0) 35 { 36 perror("listen"); 37 } 38 return sock; 39 } 40 41 void select_server(int sock) 42 { 43 int i=0; 44 for(;i<64;i++) //初始化為無效的文件描述符 45 { 46 fd[i]=-1; 47 } 48 fd_set reads; //定義讀文件描述符集 49 printf("%d\n",sizeof(fd_set)); 50 fd_set writes; //定義寫文件描述符集 51 int max_fd; //定義select所需要的參數,比最大的文件描述符大1 52 int newsock=-1;//定義所需要的accept到的文件描述符 53 int fd_len=sizeof(fd)/sizeof(fd[0]);//select參數 54 55 struct sockaddr_in routom; 56 socklen_t routom_len=sizeof(routom); 57 58 struct timeval timeout;//設置最大等待時間用于提醒 59 fd[0]=sock; //將buff中的第一個設置為監聽套接字用于監聽 60 max_fd=sock; //目前sock是最大的文件描述符 61 62 char buf[1024]; 63 while(1) 64 { 65 timeout.tv_sec=5; //單位為秒 66 timeout.tv_usec=0; //單位為毫秒 67 FD_ZERO(&reads); //初始化 68 FD_ZERO(&writes); 69 FD_SET(sock,&reads); 70 //將sock添加到reads讀文件描述符集中因為每次我們都需要這個文件描述符 71 int i=0; 72 for(;i<fd_len;i++) 73 { 74 //尋找有效的文件描述符并添加到reads集中,注意(因為我們所要實現的是 75 //客戶端發消息,服務器顯示,所以我們不關心寫文件描述符,只有兩種我們 76 //需要關心的文件描述符1.accept到的需要鏈接服務器的文件描述符.2.有數據 77 //需要去讀的問家描述符。 78 if(fd[i]>0) 79 { 80 FD_SET(fd[i],&reads); 81 if(fd[i]>max_fd) 82 { 83 max_fd=fd[i]; //將最大的文件描述符給max_fd 84 } 85 } 86 } 87 switch(select(max_fd+1,&reads,&writes,NULL,&timeout)){ 88 //正式進入select函數 89 case -1: 90 perror("select"); 91 case 0 : //返回值為0代表timeout了 92 printf("timeout...\n"); 93 break; 94 default: 95 { 96 i=0; 97 for(;i<fd_len;i++) 98 { 99 100 if(fd[i]==sock&&FD_ISSET(fd[i],&reads)) 101 //每次我們都需要監聽一次 102 { 103 104 newsock=accept(sock,(struct sockaddr*)\ 105 &routom,&routom_len); 106 printf("%d\n",newsock); 107 if(newsock<0) 108 { 109 perror("accept"); 110 continue; 111 } 112 for(i=0;i<fd_len;i++) 113 //監聽到了就把這個套接字給一個有效的buf空間 114 { 115 if(fd[i]==-1) 116 { 117 fd[i]=newsock; 118 printf("[ip]:%s has comming...\n",\ 119 inet_ntoa(routom.sin_addr)); 120 break; 121 } 122 } 123 if(i==max_fd) 124 //如果滿了就先關閉他 125 { 126 close(newsock); 127 } 128 } 129 else if(fd[i]>0&&FD_ISSET(fd[i],&reads)) 130 //滿足有數據要讀的文件描述符 131 { 132 memset(buf,'\0',sizeof(buf)); 133 ssize_t size=read(fd[i],buf,sizeof(buf)-1); 134 if(size>0) 135 { 136 buf[size]='\0'; 137 printf("[%s]::%s\n",inet_ntoa\ 138 (routom.sin_addr),buf); 139 }else if(size==0){ 140 //size小于零時,則說明遠端已經關閉 141 printf("[ip]%s:is shutdown\n",\ 142 inet_ntoa(routom.sin_addr)); 143 close(fd[i]); 144 fd[i]=-1; 145 }else{ 146 ; 147 } 148 } 149 } 150 } 151 } 152 } 153 } 154 155 int main(int argc,char *argv[]) 156 { 157 if(argc!=3) 158 { 159 usage(argv[0]); 160 } 161 char *ip=argv[1]; 162 int port=atoi(argv[2]); 163 int sock=listen_sock(ip,port); 164 165 select_server(sock); 166 close(sock); 167 return 0; 168 }
總結:select可以一次監聽多個事件,但是它的缺點是很明顯的。
1、它是有監聽上限的,我的電腦上限是1024個。
2、它是通過輸出型參數來返回的,這樣我們就不得不每次監聽到都要遍歷 一次整個存放文件描述符的buf。
3、它會在設置的時候在用戶態和內核態中來回copy這樣開銷很大。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。