IO模型
计算机知识思维导图:IO模型。网页展示前三层结构,可在线查看完整脑图并下载 GoMind 文件。
2026-08-25
## IO模型 ### IO模型 #### BIO 同步堵塞IO ##### 1. 发起recvfrom系统调用,等待数据到达,堵塞 ##### 2. 数据准备好后将数据从内核复制到用户进程,堵塞 #### NIO 同步非堵塞IO ##### 1. 发起recvfrom系统调用后立即返回,之后通过轮询的方式查看数据是否已准备就绪,非堵塞 ##### 2. 数据准备好后将数据从内核复制到用户进程,堵塞 #### IO多路复用 ##### 实现 #### signal driven IO 信号驱动IO #### asynchronous IO 异步非堵塞IO > 仅展示前三层结构;请在线查看完整脑图或下载 GoMind 文件。
IO模型
IO模型
1. 发起recvfrom系统调用,等待数据到达,堵塞
2. 数据准备好后将数据从内核复制到用户进程,堵塞
BIO 同步堵塞IO
1. 发起recvfrom系统调用后立即返回,之后通过轮询的方式查看数据是否已准备就绪,非堵塞
2. 数据准备好后将数据从内核复制到用户进程,堵塞
NIO 同步非堵塞IO
也就是说非阻塞的recvform系统调用调用之后,进程并没有被阻塞,内核马上返回给进程,如果数据还没准备好,此时会返回一个error。进程在返回之后,可以干点别的事情,然后再发起recvform系统调用。重复上面的过程,循环往复的进行recvform系统调用。这个过程通常被称之为轮询。轮询检查内核数据,直到数据准备好,再拷贝数据到进程,进行数据处理。需要注意,拷贝数据整个过程,进程仍然是属于阻塞的状态。
select 会修改传入的参数数组,这个对于一个需要调用很多次的函数,是非常不友好的
每次任何一个socket准备好数据后都需要遍历一遍链表, 随着监视的描述符数量的增长,其效率也会线性下降
非线程安全,socket加入到select之后无法收回,若强行关闭该socket则会出现不可预测的后果
打开FD的数量有限制,这对于连接数量比较大的服务器来说根本不能满足。虽然也可以选择多进程的解决方案( Apache就是这样实现的),不过虽然linux上面创建进程的代价比较小,但仍旧是不可忽视的,加上进程间数据同步远比不上线程间同步的高效,所以也不是一种完美的方案。
缺点
select(第一个被实现)
1983年实现
非线程安全
每次任何一个socket准备好数据后都需要遍历一遍链表, 随着监视的描述符数量的增长,其效率也会线性下降
缺点
无最大链接数的限制
从设计上来说,不再修改传入数组,不过这个要看你的平台
优点
poll
1997年实现,其实拖14年那么久也不是效率问题, 而是那个时代的硬件实在太弱,一台服务器处理1千多个链接简直就是神一样的存在了,select很长段时间已经满足需求。
epoll_create
创建一个epoll的句柄,size用来告诉内核这个监听的数目一共有多大,这个参数不同于select()中的第一个参数,给出最大监听的fd+1的值,参数size并不是限制了epoll所能监听的描述符最大个数,只是对内核初始分配内部数据结构的一个建议。 当创建好epoll句柄后,它就会占用一个fd值,在linux下如果查看/proc/进程id/fd/,是能够看到这个fd的,所以在使用完epoll后,必须调用close()关闭,否则可能导致fd被耗尽。
epoll_ctl
函数是对指定描述符fd执行op操作。
- epfd:是epoll_create()的返回值。
- op:表示op操作,用三个宏来表示:添加EPOLL_CTL_ADD,删除EPOLL_CTL_DEL,修改EPOLL_CTL_MOD。分别添加、删除和修改对fd的监听事件。
- fd:是需要监听的fd(文件描述符)
- epoll_event:是告诉内核需要监听什么事,struct 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队列里epoll_wait
等待epfd上的io事件,最多返回maxevents个事件。 参数events用来从内核得到事件的集合,maxevents告之内核这个events有多大,这个maxevents的值不能大于创建epoll_create()时的size,参数timeout是超时时间(毫秒,0会立即返回,-1将不确定,也有说法说是永久阻塞)。该函数返回需要处理的事件数目,如返回0表示已超时。
涉及三个接口
level trigger 水平触发 (默认)
缺省的工作方式,并且同时支持block和no-block socket。 当epoll_wait检测到描述符事件发生并将此事件通知应用程序,应用程序可以不立即处理该事件。下次调用epoll_wait时,会再次响应应用程序并通知此事件。
edge trigger 边缘触发
只支持no-block socket。 当epoll_wait检测到描述符事件发生并将此事件通知应用程序,应用程序必须立即处理该事件。如果不处理,下次调用epoll_wait时,不会再次响应应用程序并通知此事件。 ET模式在很大程度上减少了epoll事件被重复触发的次数,因此效率要比LT模式高。epoll工作在ET模式的时候,必须使用非阻塞套接口,以避免由于一个文件句柄的阻塞读/阻塞写操作把处理多个文件描述符的任务饿死。
工作模式
epoll (仅支持Linux)
epoll是在2.6内核中提出的,是之前的select和poll的增强版本。只有linux支持。比如BSD上面对应的实现是kqueue。 相对于select和poll来说,epoll更加灵活,没有描述符限制。epoll使用一个文件描述符管理多个描述符,将用户关系的文件描述符的事件存放到内核的一个事件表中,这样在用户空间和内核空间的copy只需一次。
实现
IO多路复用
signal driven IO 信号驱动IO
asynchronous IO 异步非堵塞IO