原型:
#include<sys/time.h> #include<unistd.h> int select(int maxfd, fd_set *rdset, fd_set *wrest, fd_set *exset, struct timeval *timeout);
参数:
- maxfd:描述需要监视最大文件描述符+1
- rdset:监视的可读文件描述符的集合
- wrset:监视的可写文件描述符的集合
- exset:监视的异常文件描述符的集合
- struct timeval:描述一段时间长度,如果在这个时间内,需要监视的描述符没有事件发生,返回0
返回值:
- 超时返回0
- 失败返回-1
- 成功返回大于0的整数,这个整数表示就绪描述符的数目
int FD_ZERO(fd_set *fdset):将指定的文件描述符集情况,在对文件描述符集合进行设置之前,必须对其进行初始化。如果不请空,由于在系统分配内存后,通常不做清空处理,所以结果时不可知的。 int FD_SET(int fd, fd_set *fdset); 用于在文件描述符集合中添加一个新的文件描述符 int FD_CLR(int fd, fd_set *fdset); 用于在文件描述符集合中删除一个新的文件描述符 int FD_ISSET(int fd, fd_set *fdset); 用于测试指定的文件描述符是否在该集合中。
注意:fd_set通常是一个整数数组,其中每个整数中的每一位对应一个描述符(矢量),例如,使用一个32位整数,那么该数组的第一个元素对应于描述符0~31,第二个元素对应于描述符32~63,一次类推。
select 系统调用用途在于在一段指定的时间内,监听用户感兴趣的文件描述符上可读可写和异常事件。
select使用范例:
当声明了一个文件描述符集之后,必须用FD_ZERo将所有位置置0,然后再将我们所感兴趣的描述符所对应的位置位:
- fd_set rset;
- int fd;
- FD_ZERO(&rset);
- FD_SET(fd,&rset);
- FD_SET(stdin,&rset);
然后调用select函数,阻塞等待文件描述符事件的到来如果超过设定的事件,则不再等待,继续往下执行
select(fd+1,&rset,NULL,NULL,NULL);
select返回后,用FD_ISSET测试给定位是否置位。
if(FD_ISSET(fd, &rset)) { … //do something }
深入理解select模型:
理解selecr模型的关键是在于理解fd_set为了说明方便,取fd_set长度为1字节,fd_set的每一位bit可以对应一个文件描述符,则1字节长的fd_set最大可以对应8个fd。
- 执行fd_set set; FD_ZERO(&set); 则set用位表示为 0000,0000
- 若fd = 5,执行FD_SET(fd,&set); 后set变为0001,0000 (第5位置1)
- 若再加入fd = 2,fd = 1,则set变为0001,0011
- 执行select(6,&set,0,0,0)阻塞等待
- 若fd=1,fd=2上有事件发生,则select返回,此时set变为0000,0001。注意:没有事件发生的fd=5被清空。
select模型特点:
- 可监控的文件描述符个数取决于sizeof(fd_set)的值。每个bit可以表示一个文件描述符。
- 将fd加入到select监控集的同事,还要再使用一个数据结构array保存放到select监控集中的fd,一是用于在select返回后,array作为元数据和fd_set进行FD_ISSET判断。二是select返回后会把以前加入的但无事件发生的fd清空,则每次开始select前都要重新从array取得fd逐一加入人(FD_ZERO最先),扫描array的同时取得fd的最大值maxfd,用于select的第一个参数
可见select模型必须在select前循环加上fd,取maxfd,select 返回后在利用FD_ISSET判断是否有事件发生。
select优势:
用户可以在一个线程内同时处理多个socket的IO请求,在网络编程中,当涉及到多客户访问服务器的情况,除了使用fork多个进程来处理每个客户的连接,还可以使用select来处理。
select缺点:
select本质是通过设置或者检查存放fd标志位的数据结构来进行下一步处理,这样带来的缺点:
- l 单个进程可监视的fd数量被限制,即能监听端口的大小有限。一般来说这个数目和系统内存关系很大,具体数目可以cat /proc/sys/fs/file-max查看。
- l 对socket进行扫描时是线性扫描,即采用轮询的方法,效率较低,当套接字比较多的时候,每次select(0都要通过遍历FD_SETSIZE个socket来完成调度,不管哪个socket是活跃的,都需要遍历一遍。这就很浪费CPU事件,如果能给套接字注册某个回调函数,当他们活跃时,自动完成相关操作,这就避免了轮询,这正是epoll与kqueue做的
- l 需要维护一个用来存放大量fd的数据结构,这样会使得用户空间和内核空间在传递该结构体时赋值开销大。