select函数使用浅析

一、函数原型及参数说明

    int select(int maxfdp, fd_set *readfds, fd_set *writefds, fd_set *errorfds, struct timeval *timeout);
    
    返回值  : 负值:select错误,正值:某些文件可读写或出错,0:等待超时,没有可读写或错误的文件。
    
    maxfdp :是一个整数值,是指集合中所有文件描述符的范围,即所有文件描述符的最大值加1,不能错!在Windows中这个参数的值无所谓,可以设置不正确。
    
    readfds : 是指向fd_set结构的指针,这个集合中应该包括文件描述符,我们是要监视这些文件描述符的读变化的,即我们关心是否可以从这些文件中读取数据了,如果这个集合中有一个文件可读,select就会返回一个大于0的值,表示有文件可读,如果没有可读的文件,则根据timeout参数再判断是否超时,若超出timeout的时间,select返回0,若发生错误返回负值。可以传入NULL值,表示不关心任何文件的读变化。

    writefds :是指向fd_set结构的指针,这个集合中应该包括文件描述符,我们是要监视这些文件描述符的写变化的,即我们关心是否可以向这些文件中写入数据了,如果这个集合中有一个文件可写,select就会返回一个大于0的值,表示有文件可写,如果没有可写的文件,则根据timeout参数再判断是否超时,若超出timeout的时间,select返回0,若发生错误返回负值。可以传入NULL值,表示不关心任何文件的写变化。
               
    errorfds : 同上面两个参数的意图,用来监视文件是否发生错误异常。
    
    timeout : 是select的超时时间,这个参数至关重要。它可以使select处于三种状态,第一,若将NULL以形参传入,即不传入时间结构,就是将select置于永久阻塞状态,一定等到监视文件描述符集合中某个文件描述符发生变化为止;第二,若将时间值设为0秒0毫秒,就变成一个纯粹的非阻塞函数,不管文件描述符是否有变化,都立刻返回继续执行,文件无变化返回0,有变化返回一个正值;第三,timeout的值大于0,这就是等待的超时时间,即select在timeout时间内阻塞,超时时间之内有事件到来返回正值,超时返回0。                  


    fd_set结构体定义
    
    #define __FD_SETSIZE    1024
    
    typedef __kernel_fd_set     fd_set;
    
    typedef struct {
        unsigned long fds_bits[__FD_SETSIZE / (8 * sizeof(long))];
    } __kernel_fd_set;

    可以看出,fd_set结构体里面是一个无符号长整型的数组,总共有1024/(8 * 4) = 32个元素,然而这并不是说select最多只能监控32个文件的变化。过去,描述符集被作为一个整数位屏蔽码得到实现,但是这种实现对于多于32个的文件描述符将无法工作。描述符集现在通常用整数数组中的位域表示,数组元素的每一位对应一个文件描述符。例如,一个整数占32位,那么整数数组的第一个元素代表文件描述符0到31,数组的第二个元素代表文件描述符32到63,以此类推。宏FD_SET设置整数数组中对应于fd文件描述符的位为1,宏FD_CLR设置整数数组中对应于fd文件描述符的位为0,宏FD_ZERO设置整数数组中的所有位都为0。fd_set可以理解为一个集合,这个集合中存放的是文件描述符(file descriptor),即文件句柄。
    
    struct timeval结构体定义
    
    struct timeval {
        __kernel_time_t        tv_sec;        /* seconds */
        __kernel_suseconds_t    tv_usec;    /* microseconds */
    };

    tv_sec  单位是秒
    tv_usec 单位是微秒,不是毫秒!毫秒的英文单词是millisecond。


二、操作描述字集的四个宏


    FD_ZERO(&set);      /* 将set清零 */
    FD_SET(fd, &set);   /* 将fd加入set */
    FD_CLR(fd, &set);   /* 将fd从set中清除 */
    FD_ISSET(fd, &set); /* 如果fd在set中则真 */
    
    关于FD_ISSET多说一句,select返回时会将没有准备就绪的文件描述符从set中清除,所以FD_ISSET(fd, &set)判断fd是否在set中,如果在说明他没有被清除,该描述符的状态发生了变化(可读、可写或者异常)。


三、一个简单的例子

int main(int argc, char **argv)
{

    int sock;
    int ret;
    FILE *fp;
    struct fd_set fds;
    struct timeval timeout={3,0}; //select等待3秒,3秒轮询,要非阻塞就置0。
    char buffer[256]={0}; //256字节的接收缓冲区

    while(1)
    {
        FD_ZERO(&fds);      //每次循环都要清空集合,否则不能检测描述符变化
        FD_SET(sock,&fds);  //添加描述符
        FD_SET(fp,&fds);    //添加描述符

        maxfdp=sock>fp?sock+1:fp+1; //描述符最大值加1

        ret = select(maxfdp, &fds, &fds, NULL, &timeout);
        
        if(ret < 0) {
            break;    //select错误,退出程序
        } else if(ret == 0) {
            continue; //select超时,再次轮询
        } else {
            if(FD_ISSET(sock,&fds)) //测试sock是否可读,即是否网络上有数据
            {
                recvfrom(sock,buffer,256,.....);           //接受网络数据

                if(FD_ISSET(fp, &fds)) {                   //测试文件是否可写
                    fwrite(buffer, 1, sizeof(buffer), fp); //写入文件
                }

                memset(buffer, 0, sizeof(buffer));
             }
        }
    }
    
    return 0;
}

四、什么情况下使用select()

    read()函数和write()函数本身就有阻塞的功能,那么为什么还要用select呢?个人觉得用select主要有以下两个原因
    1、select可以监控多个文件描述符的状态,等相应的描述符有变化了再去读写。这时如果不用select的话,每个描述符你都要开一个进程去等待,太浪费资源了。
    2、使用select可以进行非阻塞开发。如果不想在网络包还没来之前一直阻塞在recv(),这时候就可以设置select参数timeout的值来处理了。
    
五、select函数的驱动实现

    select为什么会阻塞?我想自己开发一个驱动程序,给上层提供select的功能,我的驱动该怎么做?欲知后事如何,请看我的另一篇博文《select原理及驱动实现》。


你可能感兴趣的:(linux驱动开发,函数,select)