对于这种方式,最大的问题在哪里呢?accept和recev的阻塞调用!下面以两种场景为例,来说明相比这种情况,select是如何做到异步I/O多路复用的高效性。
第一种场景:
server除了要对外响应client的服务外,还要能够接受标准输入的命令来进行管理。
假如使用上述阻塞方式,在单线程中,accept调用和read调用必定有先后顺序,而它们都是阻塞的。比如先调用accept,后调用 read,那么如果没有客户请求时,服务器会一直阻塞在accept,没有机会调用read,也就不能响应标准输入的命令。
int fd_stdin = open(...);
int fd_socket = socket(...);
bind(...);
listen(...);
while(1){
accept(...);
read(...);
}
而如果使用select,先注册分别由socket和open创建的文件描述符,然后进入select调用。当其中任何一个文件描述符的状态发生改变时,就可以进行相应的处理。
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,也无法进行响应。
int fd_socket = socket(...);
bind(...);
listen(...);
while(1){
accept(...);
revev(...);
}
而如果使用select,在服务器端先注册由socket创建的文件描述符,然后进入select调用。只有当由socket创建的文件描述符的状态发生改变时,才执行accept操作,并把得到的client的文件描述符进行注册,再次进入select调用。当select检查到有文件描述符的状态改变时,如果是server的socket创建的文件描述符,则执行accept操作,否则执行recev操作。当请求的client数目比较多时, select明显能够提高并发性。
int fd_socket = socket(...);
bind(...);
listen(...);
fd_set fs;
while(1){
FD_ZERO(fs...);
FD_SET(fd_socket, fs);
set fs with the file descriptor fd_accept got from accept;
select();
if(FD_ISSET(fd_socket...)){
accept(...);
record the file descriptor;
}
if(FD_ISSET(fd_accept...))
recv(...);
}
说完了select相比阻塞调用的好处,我们也简单说说它的
限制和不足。
(1)select在查找状态改变的文件描述符时,是对描述符链表进行遍历操作,因此对效率有较大影响。
(2)select在默认情况下,支持的最大文件描述符个数为1024。当然,可以通过修改linux的socket内核进行修改。
PS:本博客的主要用途是记录在准备找工作过程中对以前所学知识的复习笔记。由于时间关系,大部分文章都没有进入深入的细节讲解。忘海涵!