select函数

转载自:https://blog.csdn.net/silent123go/article/details/52577160

一、函数原型及参数说明

    int select(int maxfdp, fd_set *readfds, fd_set *writefds, fd_set *errorfds, struct timeval *timeout);
    
    返回值: 负值:select错误,正值:某些文件可读写或出错,0:等待超时,没有可读写或错误的文件。
    
maxfdp :是一个整数值,是指集合中所有文件描述符的范围,即所有文件描述符的最大值加1,不能错!在Windows中这个参数的值无所谓,可以设置不正确。

readfds:readfds参数标识等待可读性检查的套接口。如果该套接口正处于监听listen()状态,则若有连接请求到达,该套接口便被标识为可读,这样一个accept()调用保证可以无阻塞完成,对其他套接口而言,可读性意味着有排队数据供读取。或者对于SOCK_STREAM类型套接口来说,相对于该套接口的虚套接口已关闭,于是recv()recvfrom()操作均能无阻塞完成。

它是指向fd_set结构的指针,这个集合中应该包括文件描述符,我们是要监视这些文件描述符的读变化的,即我们关心是否可以从这些文件中读取数据了,如果这个集合中有一个文件可读,select就会返回一个大于0的值,表示有文件可读,如果没有可读的文件,则根据timeout参数再判断是否超时,若超出timeout的时间,select返回0,若发生错误返回负值。可以传入NULL值,表示不关心任何文件的读变化。

writefds:writefds参数标识等待可写性检查的套接口。如果一个套接口正在connect()连接(非阻塞),可写性意味着连接顺利建立。如果套接口并未处于connect()调用中,可写性意味着send()sendto()调用将无阻塞完成。〔但并未指出这个保证在多长时间内有效,特别是在多线程环境中〕

它是指向fd_set结构的指针,这个集合中应该包括文件描述符,我们是要监视这些文件描述符的写变化的,即我们关心是否可以向这些文件中写入数据了,如果这个集合中有一个文件可写,select就会返回一个大于0的值,表示有文件可写,如果没有可写的文件,则根据timeout参数再判断是否超时,若超出timeout的时间,select返回0,若发生错误返回负值。可以传入NULL值,表示不关心任何文件的写变化。
               
errorfds : 同上面两个参数的意图,用来监视文件是否发生错误异常。该参数标识等待带外数据存在性或意味错误条件检查的套接口,请注意如果设置了SO_OOBINLINE选项为假FALSE,则只能用这种方法来检查带外数据的存在与否,对于SO_STREAM类型套接口,远端造成的连接中止和KEEPALIVE错误都将被作为意味出错。如果套接口正在进行连接connect()(非阻塞方式),则连接试图的失败将会表现在exceptfds参数中。
    
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原理及驱动实现》。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值