• linux select 函数使用


    I/O多路复用之select()系统调用
    为什么要使用I/O多路复用

    应用程序常常需要在多于一个文件描述符上阻塞:例如响应键盘输入(stdin)、进程间通信以及同时操作多个文件。

    在不使用线程(怎么理解线程的存在呢?我么可以举一个例子。当我们运行qq这个进程的时候,是可以执行不同的任务的。例如,我们可以在使用qq发送消息的同时来收发文件。而这两个不同的任务就是利用线程来完成的),尤其是独立处理每一个文件的情况下,进程无法在多个文件描述符上同时阻塞。如果文件都处于准备好被读写的状态,同时操作多个文件描述符是没有问题的。但是,一旦在该过程中出现一个未准备好的文件描述符(就是说,如果一个read()被调用,但没有读入数据),则这个进程将会阻塞,不能再操作其他文件。可能阻塞只有几秒钟,但是应用无响应也会造成不好的用户体验。然而,如果文件描述符始终没有任何可用数据,就可能一直阻塞下去。

    如果使用非阻塞I/O,应用可以发起I/O请求并返回一个特别的错误,从而避免阻塞。但是,从两个方面来讲,这种方法效率较差。首先,进程需要以某种不确定的方式不断发起I/O操作,直到某个打开的文件描述符准备好进行I/O。其次,如果程序可以睡眠的话将更加有效,可以让处理器进行其他工作,直到一个或更多文件描述符可以进行I/O时再唤醒。

     
    三种I/O多路复用方案

    I/O多路复用允许应用在多个文件描述符上同时阻塞,并在其中某个可以读写时收到通知。这时I/O多路复用就成了应用的关键所在。

    I/O多路复用的设计遵循一下原则:

    1、I/O多路复用:当任何文件描述符准备好I/O时告诉我

    2、在一个或更多文件描述符就绪前始终处于睡眠状态

    3、唤醒:哪个准备好了?

    4、在不阻塞的情况下处理所有I/O就绪的文件描述符

    5、返回第一步,重新开始

    Linux提供了三种I/O多路复用方案:select、poll、epoll。
     

    select函数使用

    1. /* According to POSIX.1-2001 */
    2. #include <sys/select.h>
    3. /* According to earlier standards */
    4. #include <sys/time.h>
    5. #include <sys/types.h>
    6. #include <unistd.h>
    7. int select(int nfds, fd_set *readfds, fd_set *writefds,
    8. fd_set *exceptfds, struct timeval *timeout);
    9. /*
    10. nfds: 监控的文件描述符集里最大文件描述符加1,因为此参数会告诉内核检测前多少个文件描述符的状态
    11. readfds: 监控有读数据到达文件描述符集合,传入传出参数
    12. writefds: 监控写数据到达文件描述符集合,传入传出参数
    13. exceptfds: 监控异常发生达文件描述符集合,如带外数据到达异常,传入传出参数
    14. timeout: 定时阻塞监控时间,3种情况
    15. 1.NULL,永远等下去
    16. 2.设置timeval,等待固定时间
    17. 3.设置timeval里时间均为0,检查描述字后立即返回,轮询
    18. struct timeval {
    19. long tv_sec; // seconds
    20. long tv_usec; // microseconds
    21. };
    22. int类型返回值:超时返回0;失败返回-1;成功返回大于0的整数,这个整数表示就绪描述符的数目。
    23. */
    24. void FD_CLR(int fd, fd_set *set); // 把文件描述符集合里fd0
    25. int FD_ISSET(int fd, fd_set *set); // 测试文件描述符集合里fd是否置1
    26. void FD_SET(int fd, fd_set *set); // 把文件描述符集合里fd位置1
    27. void FD_ZERO(fd_set *set); //把文件描述符集合里所有位清0

    其中fd_set是select机制中提供的一种数据结构,实际上是一long类型的数组,每一个数组元素都能与一打开的文件句柄(不仅是socket句柄,还是其他文件或命名管道或设备句柄)建立联系,建立联系的工作由程序员完成,当调用select()时,由内核根据IO状态修改fd_set的内容,由此来通知执行了select()的进程哪一个socket或文件发生了可读或可写事件

    监测的文件描述符可以分为三类,分别等待不同的事件。监测readfds集合中的文件描述符,确认其中是否有可读数据(也就是说,确认好了的文件描述符的读操作可以无阻塞的完成)。监测writefds集合中的文件描述符,确认其中是否有一个写操作可以不阻塞地完成。监测exceptfds中的文件描述符,确认其中是否有出现异常发生或者出现带外数据(这种情况只适用于套接字)。指定的集合可能为空(NULL)。相应的,select()则不对此类事件进行监测。

    成功返回时,每个集合只包含对应类型的I/O就绪的文件描述符。举个例子,readfds集合中有两个文件描述符:7和9.当调用返回时,如果7还在集合中,该文件描述符就准备好进行无阻塞I/O了。如果9已不在集合中,它可能在被读取时会发生阻塞。出现错误返回-1。

    第一个参数n,等于所有集合中文件描述符的最大值加1。这样,select()的调用者需要找到最大的文件描述符值,并将其加1后传给第一个参数。
     

    timeout参数是一个指向timeval结构体的指针,定义如下:

    1. #include <sys/time.h>
    2. struct timeval {
    3. long tv_sec; /* seconds */
    4. long tv_usec; /* microseconds */
    5. };

    如果这个参数不是NULL,即使此时没有文件描述符处于I/O就绪状态,select()调用也将在tv_sec秒、tv_usec微秒后返回。即 select在timeout时间内阻塞,超时时间之内有事件到来就返回了,否则在超时后不管怎样一定返回。

    如果时限中的两个值都是0,调用会立即返回,并报告调用时所有事件对应的文件描述符均不可用,且不等待任何后续事件。

    若将NULL以形参传入,即不传入时间结构,就是将select置于阻塞状态,一定等到监视文件描述符集合中某个文件描述符发生变化为止

    fd_set结构体

      fd_set其实这是一个数组的宏定义,实际上是一long类型的数组,每一个数组元素都能与一打开的文件句柄(socket、文件、管道、设备等)建立联系,建立联系的工作由程序员完成,当调用select()时,由内核根据IO状态修改fd_set的内容,由此来通知执行了select()的进程哪个句柄可读。
     

    1. #include <WINSOCK2.H>
    2. int main()
    3. {
    4. fd_set fdset;
    5. FD_ZERO(&fdset);
    6. FD_SET(1, &fdset);
    7. FD_SET(2, &fdset);
    8. FD_SET(3, &fdset);
    9. FD_SET(7, &fdset);
    10. int isset = FD_ISSET(3, &fdset);
    11. printf("isset = %d\n", isset);
    12. FD_CLR(3, &fdset);
    13. isset = FD_ISSET(3, &fdset);
    14. printf("isset = %d\n", isset);
    15. return 0;
    16. }

     然后经过FD_CLR以后,fd_array[2]就被清除了,数组后面的数据依次往前提,即7被放到了fd_array[2]
           所以isset前后两次打印的值分别为1和0

    select模型

    理解select模型的关键在于理解fd_set,为说明方便,取fd_set长度为1字节,fd_set中的每一bit可以对应一个文件描述符fd。则1字节长的fd_set最大可以对应8个fd。

    (1)执行fd_set set; FD_ZERO(&set); 则set用位表示是0000,0000。

    (2)若fd=5,执行FD_SET(fd,&set);后set变为0001,0000(第5位置为1)

    (3)若再加入fd=2,fd=1,则set变为0001,0011

    (4)执行select(6,&set,0,0,0)阻塞等待

    (5)若fd=1,fd=2上都发生可读事件,则select返回,此时set变为0000,0011。注意:没有事件发生的fd=5被清空。

    基于上面的讨论,可以轻松得出select模型的特点:

    (1)可监控的文件描述符个数取决与sizeof(fd_set)的值。我这边服务器上sizeof(fd_set)=512,每bit表示一个文件描述符,则我服务器上支持的最大文件描述符是512*8=4096。据说可调,另有说虽然可调,但调整上限受于编译内核时的变量值。

    (2)将fd加入select监控集的同时,还要再使用一个数据结构array保存放到select监控集中的fd,一是用于再select返回后,array作为源数据和fd_set进行FD_ISSET判断。二是select返回后会把以前加入的但并无事件发生的fd清空,则每次开始select前都要重新从array取得fd逐一加入(FD_ZERO最先),扫描array的同时取得fd最大值maxfd,用于select的第一个参数。

    (3)可见select模型必须在select前循环加fd,取maxfd,select返回后利用FD_ISSET判断是否有事件发生。

     

  • 相关阅读:
    SpringBoot 4 SpringBoot 整合 Mybatis
    前端的数据标记协议
    【informix】解决启动报错大全,以及解决办法
    计算机视觉与深度学习-卷积神经网络-卷积&图像去噪&边缘提取-卷积-[北邮鲁鹏]
    postgresql-自增字段
    C/C++ 基础知识总结
    照身帖、密钥,看古代人做实名认证有哪些招数?
    【C++】深拷贝和浅拷贝 ④ ( 深拷贝示例 )
    时间序列论文-聚类和异常检测(一)
    Web 前端汇总
  • 原文地址:https://blog.csdn.net/zhengshifeng123/article/details/126867392