低谷时刻,
你需要一颗强心针,让你迅速回血,拥抱每天清晨的太阳吧!
目录:
1、五种IO模型
2、多路转接IO
3、IO多路转接
- select模型
- poll模型
- epoll模型
>>五种IO模型
五种典型的模型:阻塞/非阻塞/信号驱动/异步/多路转接IO
这几种IO的发展过程:效率以及对资源的利用率越来越高,但是占用的资源越来越多,并且流程控制越来越复杂
阻塞:为了完成某个功能,发起系统调用,当前若不具备完成条件,则等待条件具备,完成功能后返回
非阻塞:为了完成某个功能,发起系统调用,当前若不具备完成条件,则立即报错返回
阻塞和非阻塞和区别:发起调用后,调用是否立即返回。
同步:为了完成某个功能,发起系统调用,当前不具备成条件,则进程等待,等待功能完成。
异步:为了完成某个功能,发起系统调用,功能有其他进程完成。
同步阻塞:
异步非阻塞:
>>多路转接IO
对大量IO事件是否就绪的阻塞监控并告诉用户当前哪一个IO是就绪的;
事件是否就绪:当前的描述符是否可读/可写/异常;
优点:进程可以针对就绪的描述符进行操作,(避免没有就绪的描述符进行无用操作);tcp服务器同一时间只能于一个客户端通信一次,对没有就绪的描述符进行操作之后或导致程序阻塞;
socket可读事件就绪:socket接受缓冲区中的数据大小大于低水位标记(通常默认为1个字节)
socket可写事件就绪:socket发送缓冲区中的剩余空间大小大于低水位标记。
>>IO多路转接
IO多路转接模型:实现多路转接IO功能的方法
1、select模型:
#include <sys/select.h>
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
nfds: maxfd + 1---最大文件描述符+1,避免空遍历判断,提高性能
readfds/writefds/exceptfds:可读/可写/异常事件集合
timeout:struct timeval{...} 做一个阻塞监控,一个超时返回 NULL:永久阻塞 0:立即返回
返回值:>0 就绪的描述符个数 ==0 等待超时 <0 监控出错;
void FD_CLR(int fd, fd_set *set); // 用来清除描述词组set中相关fd 的位
int FD_ISSET(int fd, fd_set *set); // 用来测试描述词组set中相关fd 的位是否为真
void FD_SET(int fd, fd_set *set); // 用来设置描述词组set中相关fd的位
void FD_ZERO(fd_set *set); // 用来清除描述词组set的全部位
select优缺点:
1、select所监控的描述符数量有最大上限----1024(_FD_SETSIZE)
2、select每次需要将描述符集合拷贝到内核进行监控(内核态和用户态之间的数据拷贝)
3、select在内核中对所有的描述符进行轮询遍历判断是否就绪(性能随着描述符增多而下降)、
4、select就绪后会移除集合中非就绪的描述符,修改集合;每次监控需要重新添加描述符(编码复杂)
5、select返回给用户就绪的描EPOLL_CTL_MOD述符集合,但还不会直接告诉用户哪些描述符处于就绪状态(需要用户进行轮询遍历一遍判断哪些描述符在集合中,进而找出就绪的描述符对其操作)(性能随着描述符增多而下降)
优点:
1、select遵循posix,可以跨平台
2、select监控超时等待时间,可以精确到微秒
2、poll模型 :
#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
功能:对大量描述符进行事件监控
int fd: 用户所要监控的描述符
uint32_t events: 用户对监控的描述符所关心的事件 POLLIN |POLLOUT
uint32_t revents: 描述符实际就绪事件
1、用户为每一个关心的描述符定义事件结构(文件描述符/用户所关心的事件)
2、将描述符事件结构体数组拷贝到内核中进行监控
3、轮询遍历事件数组中的描述符,判断描述符是否就绪了某个事件
若没有描述符就绪用户关心的事件,则每隔一会遍历判断一次
若有描述符就绪用户关心的事件,则会将就绪的事件放到事件结构体的revents中,并调用返回
4、调用返回
poll优缺点:
1、poll每次监控都需要将所有的事件结构信息拷贝到内核
2、在内核poll进行就绪判断同样使用轮询遍历判断(性能随着描述符增多而下降)
3、描述符就绪后,poll修改描述符事件结构中的revents信息为当前就绪的事件;但是不会直接告诉用户哪些描述符处于就绪状态
(需要用户进行轮询遍历一遍判断哪些描述符在集合中,进而找出就绪的描述符对其操作)(poll相较于select实际上对大量描述符进行监控的原理并没有发上改变,性能并没有多大提高)
4、poll不能跨平台
优点:
1、poll采用事件结构的方式对描述符进行监控,简化了多个描述符集合的监控编码流程
2、poll没有描述符数量的上限限制(取决于硬件资源)
3、epoll模型:<Linux下最优秀的多路转接模型>
#include <sys/epoll.h>
int epoll_create(int size);------>在内核中创建eventpoll结构,并且返回文件描述符作为操作句柄
struct epoll_event{
uint32_t event; //用户对于描述符所要监控的事件
union epoll_data_t data(int fd,void* ptr) // fd :用户所要监控的描述符,ptr:所要发生就绪的事件
}
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
epfd: 创建epoll中的epoll的描述符
op-->三种操作-->EPOLL_CTL_ADD / EPOLL_CTL_MOD / EPOLL_CTL_DEL 添加/修改/移除一个内核中的监控节点
fd:用户所要监控的描述符
返回值:>0 就绪的描述符个数 ==0 等待超时 <0 监控出错
epoll监控原理:
通过epoll_create创建eventpoll结构,返回操作句柄;
通过操作句柄向内核中添加描述符的监控事件epoll_ctl;
epoll开始监控之后,告诉操作系统开始替进程监控内核指定的eventpoll结构体中的红黑树中的所有事件节点;
操作系统为每一个描述符定义了一个事件回调(当描述符指定事件就绪的时候,则将这个描述符对应的事件结构信息节点添加到eventpoll结构体中的双向链表中);
epoll的事件触发方式有两种:
水平触发(event=EPOLLLT)--->默认的触发方式
边缘触发(event=EPOLLET)
水平触发只要缓冲区中有数据,就会发生触发事件,进而对描述符进行读取数据;
边缘触发每一条数据只触发一次,处理过程就需要用户将所有的数据读取完毕,因为事件不会被触发第二次,等待下一条数据到来的时候才会触发下一次事件;
解决方案:
将IO操作设置为非阻塞:recv(fd,buf,MSG_DONTWAIT)
将描述符设置为非阻塞:fcntl(fd,F_SETFL,flag | O_NONBLOCK) 设置描述符属性为增加一个非阻塞属性
!!!这里的以及上面的优缺点是需要重点记住的地方
epoll优缺点:
1、监控的描述符数量无最大上限
2、采用事件结构对描述符进行监控,简化了多个描述符集合的监控编码流程
3、epoll的事件信息,每条信息只需要向内核拷贝一次
4、epoll是一个异步阻塞操作,操作系统对描述符事件进行监控-----采用回调的方式,不需要精心轮询遍历描述符进行判断(性能不会随着描述符增多而下降)
5、当描述符就绪后将描述信息添加到双向链表中,epoll_wait只需要每隔一会看一下链表是否为空,就可以判断出是否有描述符事件就绪
6、当epoll_wait返回时,直接将就绪的事件信息拷贝到用户给与的事件结构数组中;相当于直接告诉了用户那些描述符是就绪的,用户可以直接对描述符进行操作(避免遍历非就绪描述符,遍历的都是就绪事件)
优点:
1、epoll不能跨平台
2、监控超时等待时间只能精确到毫秒
多路转接模型的使用场景:多路转接模型实现高并发相较于多线程/多进程消耗资源更少,但是并非适用所有的场景!!它适用于有大量描述符需要监控,但是同一时间只有少量活跃,并每个描述符的处理时间不能太长的情况下。
~bye~