IO复用
IO多路复用实际就是select/poll/epoll这些多路选择器,使用一个线程同时监听多个文件描述符(fd_set), I/O事件,阻塞等待并且在某个文件描述符可读写时收到通知。linux在处理网络IO连接时的优化,复用的不是I/O连接,而是复用的是线程,让一个线程处理多个连接。
Select
函数原型
int select(int maxfdp,fd_set *readfds,fd_set *writefds,fd_set *errorfds,
struct timeval *timeout);
参数解释
nfds:
委托内核检测的这三个集合中最大的文件描述符 + 1
内核需要线性遍历这些集合中的文件描述符,这个值是循环结束的条件。
readfds:
文件描述符的集合,内核只检测这个集合中文件描述符对应的读缓冲区
传入传出参数,读集合一般情况下都是需要检测的,这样才知道通过哪个文件描述符接收数据
writefds:
文件描述符的集合,内核只检测这个集合中文件描述符对应的写缓冲区
传入传出参数,如果不需要使用这个参数可以指定为 NULL
exceptfds:
文件描述符的集合,内核检测集合中文件描述符是否有异常状态
传入传出参数,如果不需要使用这个参数可以指定为 NULL
timeout:
超时时长,用来强制解除 select () 函数的阻塞的
将timeout设置为NULL:函数检测不到就绪的文件描述符会一直阻塞。
等待固定时长(秒):函数检测不到就绪的文件描述符,在指定时长之后强制解除阻塞,函数返回 0不等待:函数不会阻塞,直接将该参数对应的结构体初始化为 0 即可。
返回值:
负值:select错误
0:等待超时,没有可读写或错误的文件
正值:有文件可读,或可写,或可读可写
fd_set集合
就是一个类似于数组,有1024位,如果设置文件描述符该位就为1。
编程流程
伪代码如下
fd_set se;
FD_ZERO(&se);//将se这个集合全部置为零
//设fd为用于监听的文件描述符
//将fd加入读集合中
FD_SET(fd,&se);
//设置select检测时间5秒
struct timeval timeout={5,0};
//while循环检测select中就绪的文件描述符
while(1){
int num=select(1024,&se,NULL,NULL,);//我们只关心读事件,将写事件和异常事件集合置为空
if(num<0){
printf("select error\n");//select返回值小于0,执行函数失败
}
else if(num==0){
printf("time out\n");
}
else{
/*
select返回值大于0表示有num个文件描述符就绪,我们需要
遍历se找到用于监听的文件描述符fd和用于通信的文件描述符c。
*/
for(int i=0;i<1024;++i){
//找到用于监听的文件描述符fd我们需要accept接受
if(FD_ISSET(fd,&se))
{int c=accept(fd,struct sockarr*)&caddr,&len);
//将用于通信的文件描述符c加入到se集合中等待下一轮select检测。
FD_SET(c,&se);}
//找到用于通信的文件描述符我们recv接受它
if(FD_ISSET(i,&se)&&!FD_ISSET(fd,&se))
{int len=recv(c,buff,127,0);
if(len==0)//说明对方关闭,需要把通信文件描述符c从集合中取出
FD_CLR(i,&se);
close(i);}
}
}
}