对大量的描述符进行I/O事件监控—可以告诉进程现在有哪些描述符就绪了,然后进行就可以只针对就绪了的描述符进行响应操作,避免对没有就绪的I/O操作所导致的效率降低和流程阻塞。
系统提供select函数来实现多路复用输入/输出模型.
1.程序员定义某个事件的描述符集合(可读事件的描述符集合/可写事件的描述符集合/异常事件的描述符集合),初始化清空集合
对哪个描述符关心什么事件,就把这个描述符添加到相应时间的描述符集合中
2.发起监控调用,将集合拷贝到内核中进行监控,监控的原理原理是轮询遍历判断
可读事件的就绪:接收缓冲区中数据的大小低于水位标记(量化标准–通常默认为1个字节) 可写事件的就绪:发送缓冲区中剩余空间的大小大于低水位标记(量化标准—通常默认为1个字节) 异常事件的就绪:描述符是否产生了某个异常
3.监控的调用返回,表示监控出错/有描述符就绪/监控等待超时了
并且调用返回的时候,将事件监控的描述符集合中的未就绪描述符从集合中移除了----(集合中仅仅保留就绪的描述符) 因为返回的时候修改了集合,因此下次监控的时候,就需要重新向集合中添加描述符
4.程序员轮询判断那个描述符仍然在哪个集合中,就确定这个描述符是否就绪了某个事件,然后进行对应事件的操作即可
select并不会直接返回给用户就绪的描述符,而是返回了就绪的描述符集合,因此需要程序员进行判断
1.定义集合—struct fd_set
,成员只有一个数组(当做二进制位图使用)添加描述符就是将描述符对应的比特位置1
因此select能够监控的描述符数量,取决于二进制比特位多少,而比特位多少取决于宏 ,FD_SETSIZE,默认等于1024
void FD_ZERO(fd_set* set);//初始化清空集合
void FD_SET(int fd,fd_set* set);//将fd描述符添加到set集合中
int select(int nfds,fd_set* readfds,
fd_set* writefds, fd_set *exceptfds,
struct timeval* timeout);
struct timeval{tv_sec;tv_usec};
时间结构体,通过这个时间决定select阻塞/非阻塞/限制超时的阻塞若timeout为NULL,则表示阻塞监控,直到描述符就绪/出错才会返回 若timeout中的成员数据为0,则表示阻塞,监控的时候若没有描述符就绪,则立即超时返回 若timeout中成员数据不为0,则在指定的时间内,没有就绪则超时返回
返回值:>0表示就绪的描述符个数 ==0表示没有描述符就绪,超时返回 <0表示是监控出错
3.调用返回,返回给程序员,就绪的描述符集合,程序员遍历判断哪个描述符还在哪个集合中,就是就绪了那个事件
int FD_ISSET(int fd,fd_set *set);
//判断fd描述符是否在集合中
注意:因为select返回时会修改集合,因此每次监控的时候都要重新添加描述符
4.若对描述符不想进行监控了,则从集合中移除描述符
void FD_CLR(int fd,fd_set* set);
//从描述符中删除描述符fd
遵循POSIX标准,跨平台移植性好
#include<sys/select.h>
#include<stdio.h>
#include<sys/types.h>
#include<sys/time.h>
#include<unistd.h>
#include<cstdio>
#include<time.h>
int main(int argc,char* argv[]){
fd_set rfds;
FD_ZERO(&rfds);//初始化描述符集合
FD_SET(0,&rfds);//将描述符fd添加到描述符集合
while(1){
printf(">");
fflush(stdout);
int ret=select(1,&rfds,NULL,NULL,NULL);//监控
if(ret<0){//监控错误
perror("select error");
continue;
}
if(FD_ISSET(0,&rfds)){//描述符已就绪
char buf[1024];
read(0,buf,sizeof(buf)-1);
printf("buf-->%s\n",buf);
}else{//描述符未就绪
perror("error! invaild fd\n");
continue;
}
FD_ZERO(&rfds);//将描述符置为0
FD_SET(0,&rfds);//将标准输入描述符添加到集合中
}
return 0;
}
运行结果: