GoMind Hub

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