I/O多路复用之select
select的功能可以用一句话来描述: 实现基于I/O多路复用的异步并发编程。 在具体讲解select之前我们先看看常规的阻塞socket编程方式,以服务端为例:
![](https://pic002.cnblogs.com/images/2012/428519/2012071721172679.jpg)
对于这种方式,最大的问题在哪里呢?accept和recev的阻塞调用!下面以两种场景为例,来说明相比这种情况,select是如何做到异步I/O多路复用的高效性。
第一种场景: server除了要对外响应client的服务外,还要能够接受标准输入的命令来进行管理。
假如使用上述阻塞方式,在单线程中,accept调用和read调用必定有先后顺序,而它们都是阻塞的。比如先调用accept,后调用 read,那么如果没有客户请求时,服务器会一直阻塞在accept,没有机会调用read,也就不能响应标准输入的命令。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 int fd_stdin = open(...); 2 int fd_socket = socket(...); 3 bind(...); 4 listen(...); 5 while(1){ 6 accept(...); 7 read(...); 8 }
而如果使用select,先注册分别由socket和open创建的文件描述符,然后进入select调用。当其中任何一个文件描述符的状态发生改变时,就可以进行相应的处理。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
int fd_stdin = open(...); int fd_socket = socket(...); bind(...); listen(...); fd_set fs; while(1){ FD_ZERO(fs...); FD_SET(fd_stdin, fs); FD_SET(fd_socket, fs); select(); if(FD_ISSET(fd_socket...)) accept(...); if(FD_ISSET(fd_stdin...)) read(...); }
第二种场景: server要对外提供大量的client请求服务。
假如使用阻塞方式,在单线程中,由于accept和recev都是阻塞式的,那么当一个client被服务器accept后,它可能在send发送消息时阻塞,因此服务器就会阻塞在recev调用。即时此时有其他的client进行connect,也无法进行响应。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 int fd_socket = socket(...); 2 bind(...); 3 listen(...); 4 while(1){ 5 accept(...); 6 revev(...); 7 }
而如果使用select,在服务器端先注册由socket创建的文件描述符,然后进入select调用。只有当由socket创建的文件描述符的状态发生改变时,才执行accept操作,并把得到的client的文件描述符进行注册,再次进入select调用。当select检查到有文件描述符的状态改变时,如果是server的socket创建的文件描述符,则执行accept操作,否则执行recev操作。当请求的client数目比较多时, select明显能够提高并发性。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 int fd_socket = socket(...); 2 bind(...); 3 listen(...); 4 fd_set fs; 5 while(1){ 6 FD_ZERO(fs...); 7 FD_SET(fd_socket, fs); 8 set fs with the file descriptor fd_accept got from accept; 9 select(); 10 if(FD_ISSET(fd_socket...)){ 11 accept(...); 12 record the file descriptor; 13 } 14 15 if(FD_ISSET(fd_accept...)) 16 recv(...); 17 }
说完了select相比阻塞调用的好处,我们也简单说说它的 限制和不足。
(1)select在查找状态改变的文件描述符时,是对描述符链表进行遍历操作,因此对效率有较大影响。
(2)select在默认情况下,支持的最大文件描述符个数为1024。当然,可以通过修改linux的socket内核进行修改。