Linux的五种IO模型
- 阻塞IO
进程或线程等待某个条件,如果不满足则一直等待,进入阻塞
- 非阻塞IO
如果不满足条件则直接返回,然后轮询全部的请求,如果满足条件则读取
- 信号驱动IO模型
由于轮询的效率还是不够高,那么则采用信号机制,也就是说采用报警器,如果满足条件则内核让进程去读取
- IO复用模型
添加select函数去监听所有的请求,如果哪个成功则直接进行IO读取
- 异步IO模型
IO读取由内核自己完成,如果读取完则直接告诉进程读取完成
Java中的IO
- BIO
- NIO
- 原理图
- channel
数据通过channel到达buffer,之后从buffer再读取到线程 - buffer
作为nio的重要实现,数据不需要像javaIO阻塞读取,而是可以一直被读取,而不影响线程的其它操作 - 总结分析
NIO是同步非阻塞IO,由于buffer的实现,那么假如有数据需要读取到线程中,那么可以先读取到channel,再读取到buffer,但是如果数据没有完全读取完,线程在buffer还在不停的被读入数据的同时做其它的操作
- channel
- selector
进一步的非阻塞,线程不会只是一个channel写入数据,单个线程可以利用selector控制多个channel写入数据
- 原理图
- AIO
NIO的进化版本,同步的io需要线程本身的资源去读取,而非阻塞异步io,数据的读取过程由内核自己完成,当成功读取到线程中后,会有操作系统来提示线程数据读取完成。
IO多路复用
select,poll,epoll都是IO多路复用的机制。I/O多路复用就是通过一种机制,一个进程可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。
- select
- 维护了三个状态数组,分别是
- fd_set* writefds
- fd_set* readfds
- fd_set* exceptfds
- 通过遍历这三个数组则可以获取到状态
- 问题: 单个进程可以监控的文件描述符有限,linux上是1024个
- 维护了三个状态数组,分别是
- poll
- select的进化,三个数组变成一个pollfd链表
struct pollfd { int fd; /* file descriptor */ short events; /* requested events to watch */ short revents; /* returned events witnessed */ };
- 问题: 虽然说取消了1024的限制,但是如果变长,效率还是很差
- select的进化,三个数组变成一个pollfd链表
- epoll
-
将改进转移到内核中,将用户关系的文件描述符的事件存放到内核的一个事件表中
-
epoll的操作过程
- int epoll_create(int size);、
初始化内核中事件表的大小,size参数则是指定的大小,但是并不做限制,只是建议 - int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
- epfd 是 epoll_create()的返回值
- op 是 add,mod,del (增删改查的操作)
- fd 监听的文件描述符
- epoll_event 表示监听的具体什么事件的数组
struct epoll_event { __uint32_t events; /* Epoll events */ epoll_data_t data; /* User data variable */ }; //events可以是以下几个宏的集合: EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭); EPOLLOUT:表示对应的文件描述符可以写; EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来); EPOLLERR:表示对应的文件描述符发生错误; EPOLLHUP:表示对应的文件描述符被挂断; EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。 EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里
- int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
epoll_create()返回的内核处理的事件结果- epfd 是 epoll_wait的结果
- events 是内核上处理的事件的集合
- maxevent就是告诉内核这些事件有多大,小于创建时的size
- 事件的超时事件
- int epoll_create(int size);、
-
epoll的工作方式
- LT : 可以暂时不处理,等下次再来处理
- ET :需要立刻处理
-
epoll的全流程总结
epoll首先需要利用epoll_create()去创建一个句柄,方便直接联系到内核
之后利用epoll_ctl()去为epoll_create()在内核中空间创建的时间表中,添加文件描述符,或为文件描述符添加,删除或者是修改事件
注意一个文件描述符可以添加很多的描述的事件
之后,由于内核中存在事件表,如果事件表发生某种变化,可以利用一种callback的方式回调到epoll中
这个时候如果调用epoll_wait则可以获取这些状态 (减少了轮询,而是直接通知到epoll) -