高級I/O---多路複用---select

多路複用之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的工作流程理解

wKioL1dNjSyyqiIoAADP_SMQT0I554.png

下面是一個使用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這樣開銷很大。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章