同步IO
阻塞IO模型
- 同步IO操作定义为导致进程阻塞直到IO完成的操作
- IO过程主要分两个阶段:
-
数据准备阶段
-
内核空间将数据复制回用户进程缓冲区空间
-
非阻塞IO模型
-
客户客户端不被阻塞可以继续执行但是需要不断询问内核是否有数据准备好(即文件描述符缓冲区是否就绪)
-
当有数据报准备好时,就进行拷贝数据报的操作。当没有数据报准备好时,也不阻塞程序,内核直接返回未准备就绪的信号,等待用户程序的下一个轮寻
-
半包问题:调用IO函数后直接返回,但需要不断轮询IO是否完成
-
半包问题解决:通过一个buffer缓存所有读进来的消息
异步IO
- 在调用完IO函数后,有数据后再继续IO操作,此过程中也可进行其他操作,此即为异步
IO多路转接
- IO多路复用即为select/poll/epoll,单个进程可以处理多个网络连接IO(时间驱动I/O)
- 基本原理:这些func会不断轮询所负责的所有socket,某个socket有数据到达后会通知用户进程
- IO多路转接是属于阻塞IO,但可以对多个文件描述符进行阻塞监听,所以效率较阻塞IO的高。
select
- 用以解决IO阻塞
- 参数列表中fd_set结构体(描述集)用来告诉内核监听多个文件描述符。内核中由一个数组来维护哪些fd被置位,需要遍历数组查找是否有fd需要处理
问题
- 内置数组是的select监听的文件描述符数受限于FD_SIZE这个参数
- 每次调用select前都要先重新初始化fd _set,将fd从用户态拷贝到内核态,调用select后要把fd从内核态拷贝到用户态
- 需要遍历内置数组查看需要IO处理的fd,在fd数量大时开销大
poll
- 通过一个变长数组解决了select文件描述符个数受限的问题
- 数组中存放保存fd信息的结构体,每增加一个fd就向数组中加入一个结构体且只需拷贝一次到内核态
- poll解决了select重复初始化的问题
epoll
- epoll底层由红黑树实现,只返回状态发生改变的fd避免了因轮询排查所有fd而造成效率低服务器并发能力受限
- epoll对fd的操作有LT(默认)和ET两种模式
LT模式(水平触发)
- 同时支持阻塞socket和非阻塞socket,内核会告诉用户线程fd是否有事件发生,可以对就绪fd操作,如果不操作内核会继续通知
ET模式(边缘触发)
- 高速工作模式,只支持非阻塞socket
- fd有事件发生时,内核会通过epoll通知并且认为用户线程已经收到通知并不发出更多该fd就绪通知直到因为IO操作等原因致该fd不为就绪(如果一直不对该fd操作,则内核不会发出更多通知)(通知之后必须立刻处理事件)
- ET模式在很大程度减少了epoll事件被重复触发的次数,效率比LT高
- ET必须使用非阻塞套接口以避免一个fd的阻塞读写操作把处理多个fd的任务饿死
适用场景
- select适用于实时性要求比较高的场景,比如核反应堆的控制( timeout 参数精度为微秒)
- poll 没有最大描述符数量的限制,如果平台支持并且对实时性要求不高,应该使用 poll 而不是 select
- 只需要运行在 Linux 平台上,有大量的描述符需要同时轮询,并且这些连接最好是长连接
ET和LT模式的区别
- LT模式 epoll_wait检测到fd有事件发生时会将其通知应用程序若不立即处理,下次调用epoll_wait会再次通知该fd的事件
- ET模式:当epoll_wait检测到fd事件会将其通知应用程序,可以立即处理,若不处理下次调用epoll_wait时不会再通知
信号驱动IO(signal blocking I/O)
信号驱动IO模型,应用进程告诉内核:当数据报准备好的时候,给我发送一个信号,对SIGIO信号进行捕捉,并且调用我的信号处理函数来获取数据报
异步IO
应用进程执行异步IO后会立即返回,应用进程可以继续执行,不会被阻塞,内核会在所有操作完成之后向应用进程发送信号
异步IO与信号驱动IO
异步 I/O 的信号是通知应用进程 I/O 完成,而信号驱动 I/O 的信号是通知应用进程可以开始 I/O
总结
阻塞程度:阻塞IO>非阻塞IO>多路转接IO>信号驱动IO>异步IO,效率是由低到高的