select使用案例

select使用案例

本节内容:

  • 使用fd_set的接口,来实现检测标准输入输出。
  • 使用select编写网络服务器。
检测标准输入输出:
 stdin.c 代码:

  1 #include <stdio.h>                                                                                                               
  2 #include <stdlib.h>                                                                                                              
  3 #include <unistd.h>                                                                                                              
  4 #include <sys/select.h>                                                                                                          
  5                                                                                                                                  
  6 /////////////////////////////////////////                                                                                        
  7 // 使用 select 完成对标准输入读状态就绪的等待                                                                                    
  8 ////////////////////////////////////////////                                                                                     
  9                                                                                                                                  
 10 /////////////////////////////////////////////                                                                                    
 11 // IO多路复用的本质:                                                                                                            
 12 // 本来 read 既能完成等,又能完成拷贝                                                                                            
 13 // 但是 read 有一个重要的缺陷:一次只能等一个文件描述符,                                                                        
 14 // 如果要想同时等待多个,就得配合多个线程。                                                                                      
 15 //                                                                                                                               
 16 // 但是线程如果太多,开销也很大,于是就想办法让一个线程就能                                                                      
 17 // 等待多个文件描述符。                                                                                                          
 18 //                                                                                                                               
 19 // 于是就有了 IO多路复用                                                                                                         
 20 // 因为 select 一个函数能等一堆文件描述符。                                                                                      
 21 ////////////////////////////////////////////////////////////// 
  23 int main()                                                                                                                       
 24 {                                                                                                                                
 25   while(1)                                                                                                                       
 26   {                                                                                                                              
 27     fd_set readfds;                                                                                                              
 28     FD_ZERO(&readfds); // 将该文件描述符集清空                                                                                   
 29     FD_SET(0, &readfds); // 将该文件描述符集的第0位设置成1                                                                       
 30     // 等待的过程交给 select ,让它去等一堆文件描述符                                                                            
 31     int ret = select(1, &readfds, NULL, NULL, NULL);                                                                             
 32     //最后一个参数设为NULL,表示按阻塞的行为去执行                                                                               
 33     if(ret <  0)                                                                                                                 
 34     {                                                                                                                            
 35       perror("select");                                                                                                          
 36       return 1;                                                                                                                  
 37     }                                                                                                                            
 38     if(!FD_ISSET(0, &readfds)) //判断第0位是否就绪                                                                               
 39     {                                                                                                                            
 40       printf("readfds error\n");                                                                                                 
 41       return 1;                                                                                                                  
 42     }             
 43                                                                                                                                  
 44     // 从文件描述符中读数据                                                                                                      
 45     char buf[1024]={0};                                                                                                          
 46     // 拷贝的过程交给 read                                                                                                       
 47     ssize_t read_size = read(0, buf,sizeof(buf)-1);                                                                              
 48     if(read_size < 0)                                                                                                            
 49     {                                                                                                                            
 50       perror("read");                                                                                                            
 51       return 1;                                                                                                                  
 52     }                                                                                                                            
 53     if(read_size == 0)                                                                                                           
 54     {                                                                                                                            
 55       printf("read done!\n");                                                                                                    
 56       return 0;                                                                                                                  
 57     }                                                                                                                            
 58     buf[read_size]='\0';                                                                                                         
 59     printf("buf = %s\n",buf);                                                                                                    
 60   }                                                                                                                              
 61   return 0;                                                                                                                      
 62 }         
使用select编写TCP版本的服务器:

特点:
- 实现多个连接
- 只能有一个线程来完成

  select_server_xiugai.c:

  1 #include <stdio.h>                                                                                                               
  2 #include <string.h>                                                                                                              
  3 #include <stdlib.h>                                                                                                              
  4 #include <unistd.h>                                                                                                              
  5 #include <sys/select.h>                                                                                                          
  6 #include <sys/socket.h>                                                                                                          
  7 #include <netinet/in.h>                                                                                                          
  8 #include <arpa/inet.h>                                                                                                           
  9 typedef struct sockaddr sockaddr;                                                                                                
 10 typedef struct sockaddr_in sockaddr_in;                                                                                          
 11                                                                                                                                  
 12 //////////////////////////////////////////////                                                                                   
 13 // 基于 fd_set 进行简单的封装。最重要的目的是为了                                                                                
 14 // 能够随时获取到文件描述符集中最大的文件描述符是多少                                                                            
 15 ///////////////////////////////////////////////////                                                                              
 16                                                                                                                                  
 17 typedef struct FdSet                                                                                                             
 18 {                                                                                                                                
 19   fd_set set; // 文件描述符集                                                                                                    
 20   int max_fd; // set 中最大的文件描述符的数值                                                                                    
 21 }FdSet;        
  22                                                                                                                                  
 23 void FdSetInit(FdSet* fds)                                                                                                       
 24 {                                                                                                                                
 25   fds->max_fd = -1;  // 0本身是一个合法的文件描述符                                                                              
 26   FD_ZERO(&fds->set); // 清空文件描述符集                                                                                        
 27 }                                                                                                                                
 28                                                                                                                                  
 29 void FdSetAdd(FdSet* fds, int fd)                                                                                                
 30 {                                                                                                                                
 31   FD_SET(fd, &fds->set); // 将fd加入到文件描述符集set中                                                                          
 32   if(fd > fds->max_fd)                                                                                                           
 33   {                                                                                                                              
 34     fds->max_fd = fd;                                                                                                            
 35   }                                                                                                                              
 36 }                                                                                                                                
 37                                                                                                                                  
 38 void FdSetDel(FdSet* fds, int fd)                                                                                                
 39 {                                                                                                                                
 40   FD_CLR(fd, &fds->set); // 将fd从文件描述符集中删掉                                                                             
 41   // 在文件描述符集中删除一个文件描述符后,就要考虑                                                                              
 42   // 删除的这个文件描述符是否为最大的,这时,就要更新  
 43   // fds->max_fd 。                                                                                                              
 44   int max_fd = -1;                                                                                                               
 45   int i=0;                                                                                                                       
 46   // 从前往后遍历                                                                                                                
 47   for(; i<= fds->max_fd; ++i)                                                                                                    
 48   {                                                                                                                              
 49     // 先判断文件描述符集中的哪些位是有效的                                                                                      
 50     if(!FD_ISSET(i, &fds->set))                                                                                                  
 51     {                                                                                                                            
 52       continue;                                                                                                                  
 53     }                                                                                                                            
 54     // 将该有序位上的文件描述符的值和最大的文件描述符的值进行比较,                                                              
 55     // 选出最大的文件描述符值。                                                                                                  
 56     if(i > max_fd)                                                                                                               
 57     {                                                                                                                            
 58       max_fd = i;                                                                                                                
 59     }                                                                                                                            
 60   }                                                                                                                              
 61   // 此时 max_fd 值就已经是新的文件描述符集中最大的值了。                                                                        
 62   fds->max_fd = max_fd;                                                                                                          
 63 }  
 64                                                                                                                                  
 65 //////////////////////////////////////////////////////////////////                                                               
 66 // 以下代码为 select server                                                                                                      
 67 // 个                                                                                                                            
 68 //////////////////////////////////////////////////////////////                                                                   
 69                                                                                                                                  
 70 int ServerInit(const char* ip, short port)                                                                                       
 71 {                                                                                                                                
 72  // 创建 socket                                                                                                                  
 73  int listen_sock =socket(AF_INET, SOCK_STREAM, 0);                                                                               
 74  if(listen_sock < 0)                                                                                                             
 75  {                                                                                                                               
 76    perror("socket");// 文件描述符创建失败:可能是因为文件描述符表满了                                                            
 77    return -1;                                                                                                                    
 78  }                                                                                                                               
 79                                                                                                                                  
 80  // 绑定端口号                                                                                                                   
 81  sockaddr_in addr;                                                                                                               
 82  addr.sin_family = AF_INET;                                                                                                      
 83  addr.sin_addr.s_addr = inet_addr(ip);                                                                                           
 84  addr.sin_port = htons(port);        
 85  int ret = bind(listen_sock, (sockaddr*)&addr, sizeof(addr));                                                                    
 86  if(ret < 0)                                                                                                                     
 87  {                                                                                                                               
 88    perror("bind");                                                                                                               
 89    return -1;                                                                                                                    
 90  }                                                                                                                               
 91                                                                                                                                  
 92  // 监听                                                                                                                         
 93  ret = listen(listen_sock, 5);                                                                                                   
 94  if(ret < 0)                                                                                                                     
 95  {                                                                                                                               
 96    perror("listen");                                                                                                             
 97    return -1;                                                                                                                    
 98  }                                                                                                                               
 99  return listen_sock;                                                                                                             
100 }                                                                                                                                
101                                                                                                                                  
102 int ProcessRequest(int new_sock)                                                                                                 
103 {                                                                                                                                
104   char buf[1024]={0};                                                                                                            
105   ssize_t read_size = read(new_sock, buf, sizeof(buf)-1);   
106   if(read_size < 0)                                                                                                              
107   {                                                                                                                              
108     perror("read");                                                                                                              
109     return -1;                                                                                                                   
110   }                                                                                                                              
111   if(read_size == 0)                                                                                                             
112   {                                                                                                                              
113     printf("[client %d] disconnect\n", new_sock);                                                                                
114     close(new_sock);                                                                                                             
115     return 0;                                                                                                                    
116   }                                                                                                                              
117   buf[read_size] = '\0';                                                                                                         
118   printf("[client %d] %s\n", new_sock, buf);                                                                                     
119   write(new_sock, buf, strlen(buf));                                                                                             
120   return 1;                                                                                                                      
121 }                                                                                                                                
122                                                                                                                                  
123 int main(int argc, char* argv[])                                                                                                 
124 {                                                                                                                                
125   if(argc != 3)                                                                                                                  
126   {    
127     printf("Usage ./select_server.c [ip] [port]\n");                                                                             
128     return 1;                                                                                                                    
129   }                                                                                                                              
130                                                                                                                                  
131   int listen_sock = ServerInit(argv[1], atoi(argv[2]));                                                                          
132   if(listen_sock < 0)                                                                                                            
133   {                                                                                                                              
134     printf("ServerInit failed!\n");                                                                                              
135     return 1;                                                                                                                    
136   }                                                                                                                              
137   printf("ServerInit OK!\n");                                                                                                    
138                                                                                                                                  
139   FdSet fds;                                                                                                                     
140   FdSetInit(&fds);                                                                                                               
141   FdSetAdd(&fds, listen_sock);                                                                                                   
142   while(1)                                                                                                                       
143   {                                                                                                                              
144     ///////////////////////////////////////////////////////                                                                      
145     // 此时我们要使用 select 完成所有文件描述符就绪的等待                                                                        
146     // 按照上面代码来实现,其实还有一个致命缺陷。                                                                                
147     // 例如:调用 select 的时候 fds.set 包含了100个文件描述符, 
148     // 说明需要关注100个文件描述符的就绪状态。                                                                                   
149     // 当 select 返回的时候,假设此时只有1个文件描述符就绪,                                                                     
150     // fds.set 也就只包含1位为1了。                                                                                              
151     //                                                                                                                           
152     // 此时,如果循环结束,第二次在调用 select ,fds.set 里面                                                                    
153     // 就只有1个文件描述符了,而我们关注的是100个文件描述符                                                                      
154     // 的就绪状态,即将99个文件描述符搞没了。                                                                                    
155     ///////////////////////////////////////////////////////                                                                      
156     FdSet output_fds = fds; // 这里的备份,就是为了解决上面描述的问题                                                            
157     int ret = select(fds.max_fd+1, &output_fds.set, NULL, NULL, NULL);                                                           
158     if(ret < 0)                                                                                                                  
159     {                                                                                                                            
160       perror("select");                                                                                                          
161       continue;                                                                                                                  
162     }                                                                                                                            
163     if(FD_ISSET(listen_sock, &output_fds.set))                                                                                   
164     {                                                                                                                            
165       // 就绪的文件描述符是 listen_sock ,此时意味着有                                                                            
166       // 新的客户端连上了服务器,就应该调用 accept 把连接获取到                                                                  
167       sockaddr_in peer;                                                                                                          
168       socklen_t len = sizeof(peer);    
169       int new_sock = accept(listen_sock, (sockaddr*)&peer, &len);                                                                
170       if(new_sock < 0)                                                                                                           
171       {                                                                                                                          
172         perror("accept");                                                                                                        
173         continue;                                                                                                                
174       }                                                                                                                          
175       FdSetAdd(&fds, new_sock); // 将new_sock 加到文件描述符集中                                                                 
176       printf("[client %d] connect!\n", new_sock);                                                                                
177     }                                                                                                                            
178     else                                                                                                                         
179     {                                                                                                                            
180       // i 用于遍历文件描述符集。i 的含义也就是表示一个文件描述符                                                                
181       int i = 0;                                                                                                                 
182       for(; i <= output_fds.max_fd; ++i)                                                                                         
183       {                                                                                                                          
184         if(!FD_ISSET(i, &output_fds.set))                                                                                        
185         {                                                                                                                        
186           // 当前的文件描述符不在返回的文件描述符集之中(没就绪)。                                                              
187           continue;                                                                                                              
188         }                                                                                                                        
189         // 此时某个 new_sock 读就绪了。       
190         // 从当前的 new_sock 中读取一次请求,进行计算,并进行响应。                                                              
191         int ret = ProcessRequest(i);                                                                                             
192         // 此处需要判定 ret ,如果是0,表示对方断开连接。                                                                        
193         // 此时就应该把对应的 socket 从 select 的位图上进行删除。                                                                
194         if(ret == 0)                                                                                                             
195         {                                                                                                                        
196           FdSetDel(&fds, i);                                                                                                     
197         } // end if(ret == 0)                                                                                                    
198       } // end for(; i <= fds.max_fd; ++i)                                                                                       
199     } // end if(FD_ISSET(listen_sock, &fds.set))                                                                                 
200   } // end while(1)                                                                                                              
201   return 0;                                                                                                                      
202 }             

猜你喜欢

转载自blog.csdn.net/bit666888/article/details/81108558