当前位置: 首页 > 工具软件 > selecto > 使用案例 >

select与阻塞/非阻塞IO(深入理解select函数)

盖晋
2023-12-01

转载from:select与阻塞/非阻塞IO_yxtxiaotian的专栏-CSDN博客_select阻塞

目录

1、引言(select应用场景)

2、select意义

3、select 说明

4、 select使用例子

5、浅谈select阻塞机制

1、引言(select应用场景)
很多人把【阻塞/非阻塞socket】和select联系在一起,需要指明select函数与【阻塞/非阻塞socket】没有半毛钱的关系。select函数本身是阻塞的(与socket是否阻塞并没有关系), 直到:

有监测事件发生(返回  > 0) 
超时(返回0)
select函数错误 (返回-1) 
一个套接字socket阻塞或者不阻塞,select就在那里,它可以针对这2种套接字使用,对任何一种套接字的轮询检测,超时时间都是有效的,区别就在于:当select完毕,认为该套接字可读时:

阻塞的套接字,会让read阻塞,直到读到所需要的所有字节;
非阻塞的套接字,会让read读完fd中的数据后就返回,但如果原本你要求读10个数据,这时只读了8个数据,如果你不再次使用select来判断它是否可读,而是直接read,很可能返回EAGAIN或=EWOULDBLOCK(BSD风格) ,此错误由在非阻塞套接字上不能立即完成的操作返回,例如,当套接字上没有排队数据可读时调用了recv()函数。此错误不是严重错误,相应操作应该稍后重试。对于在非阻塞   SOCK_STREAM套接字上调用connect()函数来说,报告EWOULDBLOCK是正常的,因为建立一个连接必须花费一些时间。
EWOULDBLOCK的意思是如果你不把socket设成非阻塞(即阻塞)模式时,这个读操作将阻塞,也就是说数据还未准备好(但系统知道数据来了,所以select告诉你那个socket可读)。使用非阻塞模式做I/O操作的细心的人会检查errno是不是EAGAIN、EWOULDBLOCK、EINTR,如果是就应该重读,一般是用循环。如果你不是一定要用非阻塞就不要设成这样,这就是为什么系统的默认模式是阻塞。

2、select意义
当进程调用一个阻塞的系统函数时,该进程被置于睡眠(Sleep)状态,这时内核调度其它进程运行,直到该进程等待的事件发生了(比如网络上接收到数据包,或者调用sleep 指定的睡眠时间到了)它才有可能继续运行。与睡眠状态相对的是运行(Running)状态,在Linux内核中处于运行状态的进程分为两种情况:  正在被调度执行和就绪状态。假设一个进程同时监视多个设备,如果read(设备1)是阻塞的,那么只要设备1没有数据到达就会一直阻塞在设备1的read 调用上,即使设备2有数据到达也不能处理,使用非阻塞I/O就可以避免设备2得不到及时处理。
       在open 一个设备时指定了O_NONBLOCK 标志,read / write 就不会阻塞。以read 为例,如果设备暂时没有数据可读就返回-1,同时置errno 为EWOULDBLOCK(或者EAGAIN,这两个宏定义的值相同),表示本来应该阻塞在这里,那么调用者不是阻塞在这里死等,这样可以同时监视多个设备。
      非阻塞I/O有一个缺点,如果所有设备都一直没有数据到达,调用者需要反复查询做无用功,如果阻塞在那里操作系统可以调度别的进程执行,就不会做无用功了。select 函数可以阻塞地同时监视多个设备,还可以设定阻塞等待的超时时间,从而圆满地解决了这个问题。

3、select 说明
select系统调用是用来让我们的程序监视多个文件句柄的状态变化的。程序会停在select这里等待,直到被监视的文件句柄有一个或多个发生了状态改变。关于文件句柄,其实就是一个整数,我们最熟悉的句柄是0、1、2三个,0是标准输入,1是标准输出,2是标准错误输出。0、1、2是整数表示的,对应的FILE *结构的表示就是stdin、stdout、stderr。

select函数原型是:

int select(int n,fd_set * readfds,fd_set * writefds,fd_set * exceptfds,struct timeval * timeout);
参数n代表文件描述词加1;参数readfds、writefds 和exceptfds 称为描述词组,是用来回传该描述词的读,写或例外的状况。

下面的宏提供了处理这三种描述词组的方式:

FD_CLR(inr fd,fd_set* set); //用来清除描述词组set中相关fd 的位
FD_ISSET(int fd,fd_set *set); //用来测试描述词组set中相关fd 的位是否为真
FD_SET(int fd,fd_set*set); //用来设置描述词组set中相关fd的位
FD_ZERO(fd_set *set); //用来清除描述词组set的全部位
参数timeout为结构timeval,用来设置select()的等待时间,其结构定义如下:

struct timeval
{
    time_t tv_sec;
    time_t tv_usec;
};
如果参数timeout设为NULL,则表示select()没有timeout。

select函数执行结果:

执行成功则返回文件描述词状态已改变的个数;
如果返回0代表在描述词状态改变前已超过timeout时间,没有返回;
当有错误发生时则返回-1,错误原因存于errno,此时参数readfds,writefds,exceptfds和timeout的值变成不可预测。
错误值可能为:

EBADF  //文件描述词为无效的或该文件已关闭
EINTR  // 此调用被信号所中断
EINVAL  // 参数n 为负值。
ENOMEM  // 核心内存不足

4、 select使用例子

#include<sys/time.h>  
#include<sys/types.h>  
#include<unistd.h>  
#include<string.h>  
#include<stdlib.h>  
#include<stdio.h>  
int main()  
{  
        char buf[10]="";  
        fd_set rdfds;  
        struct timeval tv;  
        int ret;  
        FD_ZERO(&rdfds);  
        FD_SET(0,&rdfds);   //文件描述符0表示stdin键盘输入  
         tv.tv_sec = 3;  
        tv.tv_usec = 500;  
        ret = select(1,&rdfds,NULL,NULL,&tv);      //第一个参数是监控句柄号+1  
        if(ret<0)  
              printf("selcet error\r\n");  
        else if(ret == 0)  
              printf("timeout \r\n");  
        else  
              printf("ret = %d \r\n",ret);  
  
        if(FD_ISSET(0,&rdfds)){         //监控输入的确是已经发生了改变  
              printf(" reading");  
              read(0,buf,9);                 //从键盘读取输入  
         }  
         write(1,buf,strlen(buf));          //在终端中回显  
         printf(" %d \r\n",strlen(buf));  
         return 0;  
}  


5、浅谈select阻塞机制
Tip:参见博文 深入浅出Linux 设备驱动编程 第5节 5.设备的阻塞与非阻塞操作

         扩展阅读参见博文 Linux下5种IO模型以及阻塞/非阻塞/同步/异步区别 

 
————————————————
版权声明:本文为CSDN博主「yxtxiaotian」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/yxtxiaotian/article/details/84062446

 类似资料: