Linux 网路编程 select


Linux 网路编程 select

文章插图
select(Linux 网路编程)【Linux 网路编程 select】select是一个计算机函式 , 位于头档案#include <sys/select.h>。该函式用于监视的档案描述符的变化情况——读写或是异常 。
基本介绍外文名:select
属性:Linux 网路编程
头档案:#include <sys/select.h>
返回值:>0、-1、0 
头档案#include <sys/select.h> /* 根据POSIX.1 - 2001 *//*根据早期的标準*/#include<sys/types.h>#include<sys/time.h>#include<unistd.h>原型int select (int maxfd + 1,fd_set *readset,fd_set *writeset,fd_set *exceptset,const struct timeval * timeout);参数参数一:最大的档案描述符加1 。参数二:用于检查可读性 , 参数三:用于检查可写性 , 参数四:用于检查带外数据 , 参数五:一个指向timeval结构的指针 , 用于决定select等待I/o的最长时间 。如果为空将一直等待 。timeval结构的定义:struct timeval{long tv_sec; // secondslong tv_usec; // microseconds}返回值>0:就绪描述字的正数目-1:出错0 :逾时注意readset writeset exceptset指定我们要让核心测试读、写和异常条件的描述字 。如果对某一个的条件不感兴趣 , 就可以把它设为NULL 。如果三个指针都为NULL , 我们就有了一个比sleep()函式更为精确的定时器(sleep()以毫秒为最小单位 , 这个以微秒为单位) 。select使用描述字集 , 典型地是一个整数数组 , 其中每个整数中的每一位对应一个描述字 。假设使用32位整数 , 那幺该数组的第一个元素对应于描述字0~31 , 第二个元素对应于描述字32~63 , 依此类推 。所有的实现细节都与应用程式无关 , 它们隐藏在名为fd_set的数据类型和以下四个宏中:void FD_ZERO (fd_set *fdset); // clear all bits in fdsetvoid FD_SET (int fd,fd_set *fdset); // turn on the bit for fd in fdsetvoid FD_CLR (int fd,fd_set *fdset); // turn off the bit for fd in fdsetintFD_ISSET(int fd,fd_set *fdset); // is the bit for fd on in fdset例子定义一个fd_set变数 , 然后打开描述字1.4.5对应的位:fd_set fdset;FD_ZERO (&fdset); // 如果不初始化 , 会导致不可预期的后果FD_SET (1,&fdset);FD_SET (4,&fdset);FD_SET (5,&fdset);int maxfdp1参数指定待测试的描述字个数 , 它的值是待测试的最大描述字加1 。套用Select在Socket编程中还是比较重要的 , 可是对于初学Socket的人来说都不太爱用Select写程式 , 他们只是习惯写诸如connect、accept、recv或recvfrom这样的阻塞程式(所谓阻塞方式block , 顾名思义 , 就是进程或是执行绪执行到这些函式时必须等待某个事件的发生 , 如果事件没有发生 , 进程或执行绪就被阻塞 , 函式不能立即返回) 。可是使用Select就可以完成非阻塞(所谓非阻塞方式non-block , 就是进程或执行绪执行此函式时不必非要等待事件的发生 , 一旦执行肯定返回 , 以返回值的不同来反映函式的执行情况 , 如果事件发生则与阻塞方式相同 , 若事件没有发生则返回一个代码来告知事件未发生 , 而进程或执行绪继续执行 , 所以效率较高)方式工作的程式 , 它能够监视我们需要监视的档案描述符的变化情况——读写或是异常 。详细介绍Select的函式格式(我所说的是Unix系统下的伯克利socket编程 , 和windows下的有区别 , 一会儿说明):int select(int maxfdp,fd_set *readfds,fd_set *writefds,fd_set *errorfds,struct timeval *timeout);先说明两个结构体:第一 , struct fd_set可以理解为一个集合 , 这个集合中存放的是档案描述符(filedescriptor) , 即档案句柄 , 这可以是我们所说的普通意义的档案 , 当然Unix下任何设备、管道、FIFO等都是档案形式 , 全部包括在内 , 所以毫无疑问一个socket就是一个档案 , socket句柄就是一个档案描述符 。fd_set集合可以通过一些宏由人为来操作 , 比如清空集合FD_ZERO(fd_set *);将一个给定的档案描述符加入集合之中FD_SET(int,fd_set*);将一个给定的档案描述符从集合中删除FD_CLR(int , fd_set*);检查集合中指定的档案描述符是否可以读写FD_ISSET(int,fd_set*) 。一会儿举例说明 。第二 , struct timeval是一个大家常用的结构 , 用来代表时间值 , 有两个成员 , 一个是秒数 , 另一个是微妙数 。具体解释select的参数:int maxfdp是一个整数值 , 是指集合中所有档案描述符的範围 , 即所有档案描述符的最大值加1 , 不能错!在Windows中这个参数的值无所谓 , 可以设定不正确 。fd_set*readfds是指向fd_set结构的指针 , 这个集合中应该包括档案描述符 , 我们是要监视这些档案描述符的读变化的 , 即我们关心是否可以从这些档案中读取数据了 , 如果这个集合中有一个档案可读 , select就会返回一个大于0的值 , 表示有档案可读 , 如果没有可读的档案 , 则根据timeout参数再判断是否逾时 , 若超出timeout的时间 , select返回0 , 若发生错误返回负值 。可以传入NULL值 , 表示不关心任何档案的读变化 。fd_set*writefds是指向fd_set结构的指针 , 这个集合中应该包括档案描述符 , 我们是要监视这些档案描述符的写变化的 , 即我们关心是否可以向这些档案中写入数据了 , 如果这个集合中有一个档案可写 , select就会返回一个大于0的值 , 表示有档案可写 , 如果没有可写的档案 , 则根据timeout参数再判断是否逾时 , 若超出timeout的时间 , select返回0 , 若发生错误返回负值 。可以传入NULL值 , 表示不关心任何档案的写变化 。fd_set *errorfds同上面两个参数的意图 , 用来监视档案错误异常 。struct timeval *timeout是select的逾时时间 , 这个参数至关重要 , 它可以使select处于三种状态 , 第一 , 若将NULL以形参传入 , 即不传入时间结构 , 就是将select置于阻塞状态 , 一定等到监视档案描述符集合中某个档案描述符发生变化为止;第二 , 若将时间值设为0秒0毫秒 , 就变成一个纯粹的非阻塞函式 , 不管档案描述符是否有变化 , 都立刻返回继续执行 , 档案无变化返回0 , 有变化返回一个正值;第三 , timeout的值大于0 , 这就是等待的逾时时间 , 即select在timeout时间内阻塞 , 逾时时间之内有事件到来就返回了 , 否则在逾时后不管怎样一定返回 , 返回值同上述 。返回值:负值:select错误正值:某些档案可读写或出错0:等待逾时 , 没有可读写或错误的档案在有了select后可以写出像样的网路程式来!举个简单的例子 , 就是从网路上接受数据写入一个档案中 。例子:main(){int sock; int fd;fd_set fds;struct timeval timeout={0,3}; //select等待3微秒 , 3微秒轮询 , 要非阻塞就置0char buffer[256]={0}; //256位元组的接收缓冲区/* 假定已经建立UDP连线 , 具体过程不写 , 简单 , 当然TCP也同理 , 主机ip和port都已经给定 , 要写的档案已经打开sock=socket(...);bind(...);fd=open(...); */while⑴{FD_ZERO(&fds); //每次循环都要清空集合 , 否则不能检测描述符变化FD_SET(sock,&fds); //添加描述符FD_SET(fd,&fds); //同上timeout.tv_sec=3;timeout.tv_usec=0;//select函式会不断修改timeout的值 , 所以每次循环都应该重新赋值[windows不受此影响]maxfdp=sock>fd?sock+1:fd+1; //描述符最大值加1switch(select(maxfdp,&fds,&fds,NULL,&timeout)) //select使用{case -1: exit(-1);break; //select错误 , 退出程式 case 0:break; //再次轮询default:if(FD_ISSET(sock,&fds)) //测试sock是否可读 , 即是否网路上有数据{recvfrom(sock,buffer,256,.....);//接受网路数据if(FD_ISSET(fd,&fds)) //测试档案是否可写write(fd,buffer...);//写入档案buffer清空;}// end if break;}// end switch}//end while}//end mainselect()函式与Linux驱动程式的关係当用户调用select系统调用时 , select系统调用会先调用poll_initwait(&table) , 然后调用驱动程式中 struct file_operations下的fop->poll函式 , 在这个函数里应该调用poll_wait() , 将current加到某个等待伫列(这里调用poll_wait()) , 并检查是否有效 , 如果无效就调用schedule_timeout();去睡眠 。事件发生后 , schedule_timeout()回来 , 调用fop->poll() , 检查到可以运行 , 就调用poll_freewait(&table);从而完成select系统调用 。重要的是fop->poll()里面要检查是否就绪 , 如果是 , 要返回相应标誌 。