单线程或单进程能够同时监测若干个文件描述符是否可以执行IO操作的能力
IO模型
1.阻塞IO
2.非阻塞IO EAGAIN忙等待errno
3.信号驱动IO SIGIO(用的相对较少)
4.并行模型 进程,线程
5.IO多路复用 select、poll、epoll
非阻塞IO
int fcntl(int fd,int cmd, ...);
功能:修改文件的属性信息
参数:
fd:要调整的文件描述符
cmd:要调整的文件属性宏名称
返回值:
成功 不一定(看cmd)
失败返回-1
int flag;
flag = fcntl(fd, F_GETFL, 0); //获取fd文件默认属性到flag变量中
flag = flag | O_NONBLOCK; //将变量调整并添加非阻塞属性
fcntl(fd, F_SETFL,flag); //将新属性flag设置到fd对应的文件生效
信号驱动IO
文件描述符需要追加O_ASYNC标志
设备有IO事件可以执行时,内核发送SIGIO信号
1.追加标志
int flag;
flag = fcntl(fd, F_GETFL, 0);
fcntl(fd, F_SETFL,flag | O_ASYNC);
2.设置信号接收者
fcntl(fd,F_SETOWN,getpid());
3.对信号进行捕获
signal(SIGIO,myhandle);
IO多路复用(系统开销小)
select循环服务器===》用select函数来动态检测有数据流动的文件描述符
int select(int nfds,fd_set *readfds,fd_set *writefds,fd_set *exceptfds,struct timeval *timeout);
功能:完成指定描述符集合中有效描述符的动态监测
该函数具有阻塞等待功能,在函数执行完毕后目标测试集合中将只保留最后有数据的文件描述符
参数:
nfds:描述符的上限值,一般是链接后文件描述符的最大值+1
readfds:只读描述符集
writefds:只写描述符集
exceptfds:异常描述符集
time out 检测超时,如果是NULL表示一直检测不超时
返回值:
超时0
失败-1
成功>0
为了配合select函数执行,有如下宏函数
void FD_CLR(int fd,fd_set *set);
功能:将指定的set集合中编号为fd的文件描述符号删除
int FD_ISSET(interesting反对,fd_set*set);
功能:判断值为fd的文件描述符是否在set集合中
如果在则返回真,否则返回假
void FD_SET(int fd,fd_set *set);
功能:将指定的fd文件描述符,添加到set集合中
void FD_ZERO(fd_set *set);
功能:将指定的set集合中所有文件描述符删除
注意事项:
1.redfds等是指针结果参数,会被函数修改,所以一般会另外定义一个allread_fdest,
保持全部要监听读的句柄,将它的拷贝传递给select函数,返回可读的句柄集合,类型fdest支持赋值运算符;
2.要注意计算nfds,当新增监听句柄时比较容易修改,当减少监听句柄时比较麻烦,如果要精确修改需要遍历或者采用最大堆等数据结构维护这个句柄集,以方便找到第二大的句柄,或者在减少监听句柄时不管nfds
3.timeout如果为NULL表示阻塞等待,如果timeout指向的时间为0,表示非阻塞,否则表示select的超时时间
4.select返回-1表示错误,返回0表示超时时间到没有监听到的时间发生,返回正数表示监听到的所有事件数,通常在处理事件时会利用这个返回值来提高效率,避免不必要的事件触发检查
5.Linux的实现中select返回会将timeout修改为剩余时间,所以重复使用timeout需要注意
缺点:
1.由于描述符集合set的限制,每个set最多只能监听FD_SETSIZE(Linux上是1024)个句柄
2.返回的可读集合是个fdset类型,需要对所有监听读句柄一一进行FD_ISSET的测试来判断是否可读
3.nfds,的存在就是为了解决select的效率问题,但是解决不彻底,比如只监听0和1000两个句柄,select需要遍历1001个句柄来检查事件
epoll
int epoll_create(int size);
功能:创建一个句柄
int epoll_ctl(int epfd,int op,int fd,struct epoll_event *event);
功能:控制事件的添加/修改/删除
参数:
epfd:epoll实例的文件描述符
op:操作类型
EPOLL_CTL_ADD 添加新的文件描述符
EPOLL_CTL_MOD 修改已经存在的文件描述符的监听事件
EPOLL_CTL_DEL 删除一个文件描述符
fd:要操作的文件描述符
event:指定事件类型和相关数据
int epoll_wait(int epfd,struct epoll_event *events,int maxevents,int timeout);
功能:等待事件就绪
参数:
epfd:epoll实例的文件描述符
events:用于存储就绪事件的数组
maxevents:表示最多处理的事件数
timeout:指定等待的超时时间,单位是毫秒。传递-1表示一直等待,传递0表示立即返回,传递正整数表示等待时间
epoll_event结构体
struct epoll_event{
uint32_t events; //表示事件类型,EPOLLIN(可读)、EPOLLOUT(可写)
epoll_data_t data; //用户数据,可以是文件描述符或指针
};
epoll解决了select和poll的几个性能的缺陷:
1.不限制监听的描述符个数,只受进程打开描述符总数的限制;
2.监听性能不随着监听描述符的增加而增加,不再是轮询描述符来探测事件,而是由描述符主动上报事件
3.使用共享内存的方式,不在用户和内核之间反复传递监听的描述符信息
4.返回参数中就是触发事件的列表,不再遍历输入时间表来查询各个事件是否被触发
epoll显著提高性能的前期是:监听大量的文件描述符,并且每次触发的描述符文件非常少
epoll的另外区别是:
1.epoll创建了描述符,记得close
2.支持水平触发和边沿触发