什么是多路转接IO
对大量的描述符进行I/O事件监控—可以告诉进程现在有哪些描述符就绪了,然后进行就可以只针对就绪了的描述符进行响应操作,避免对没有就绪的I/O操作所导致的效率降低和流程阻塞。
- IO事件:可读事件/可写事件/异常事件
I/O多路转接模型之select
select介绍
系统提供select函数来实现多路复用输入/输出模型.
- select系统调用是用来让我们的程序监视多个文件描述符的状态变化的;
- 程序会停在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集合中
- select开始调用监控
int select(int nfds,fd_set* readfds,
fd_set* writefds, fd_set *exceptfds,
struct timeval* timeout);
- nfds:当前监控的集合中最大的描述符+1,减少遍历次数
- readfds/writefds/exceptfds:可读/可写/异常三种事件的描述符集合
- 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
select就绪条件
读就绪
- socket内核中, 接收缓冲区中的字节数, 大于等于低水位标记SO_RCVLOWAT. 此时可以无阻塞的读该文件 描述符, 并且返回值大于0;
- socket TCP通信中, 对端关闭连接, 此时对该socket读, 则返回0;
- 监听的socket上有新的连接请求;
- socket上有未处理的错误;
写就绪
- socket内核中, 发送缓冲区中的可用字节数(发送缓冲区的空闲位置大小), 大于等于低水位标记 SO_SNDLOWAT, 此时可以无阻塞的写, 并且返回值大于0;
- socket的写操作被关闭(close或者shutdown). 对一个写操作被关闭的socket进行写操作, 会触发SIGPIPE 信号;
- socket使用非阻塞connect连接成功或失败之后;
- socket上有未读取的错误;
异常就绪
- socket上收到带外数据. 关于带外数据, 和TCP紧急模式相关(TCP协议头中, 有一个紧急指针的字段).
select优缺点分析
缺点
- select对描述符进行监控有最大数量上限,上限取决于宏-FD_SETSIZE,默认大小是1024
- 在内核中进行监控,是通过轮询遍历判断实现的,性能会随着描述符的增多而下降
- 只能返回就绪的集合,需要进程进行轮询遍历判断才能得知那个描述符就绪了哪个事件
- 每次监控都需要重新添加描述符到集合中,每次监控都需要将集合重新拷贝到内核中
优点:
遵循POSIX标准,跨平台移植性好
使用select简单检测标准输入
#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;
}
运行结果:
注意:本文归作者所有,未经作者允许,不得转载