引言

在深入学习netty之前,还是有必要先回顾下I/O模型,I/O模型理解起来会比较抽象,第一次理解需要借助图片以及实际例子去让自己加深印象

模型分类

在开始介绍之前,我们先列举一下Unix下可用的5种I/O模型

  • 阻塞式I/O
  • 非阻塞式I/O
  • I/O复用
  • 信号驱动I/O
  • 异步I/O

接下来的模型介绍都是以UDP做为例子,原因是UDP是以数据报的方式进行数据传输,概念比较简单(整个数据要么收到要么没有收到),便于我们去理解I/O模型的核心概念

阻塞式I/O

阻塞IO.png
我们最熟悉的I/O模型就是阻塞式I/O模型,在上图中,应用进程系统调用recvfrom接收数据,但是此时内核缓冲区中数据报还未准备好,所以应用进程会一直阻塞直到内核缓冲区有数据报到达且被复制到应用进程缓冲区
这里我们提一下这里的内核缓冲区和应用进程缓冲区所处的阶段
一个输入操作通常包括两个不同的阶段:

  1. 等待数据报准备好 (通常是等待数据从网络上到达,当所等待的分组数据到达后,会被复制到内核的某个缓冲区中)
  2. 从内核向进程复制数据 (把数据从内核缓冲区复制到应用进程缓冲区中)

例子
商场排队吃饭,只能老老实实排队,并且排队的时候不能做其他事情

非阻塞式I/O

如果不想进程一直阻塞在那里的话,我们可以设置本次套接字连接为非阻塞的

非阻塞IO.png
查看上图可知,在设置连接为非阻塞时,当应用进程系统调用recvfrom没有数据返回时,内核会立即返回一个EWOULDBLOCK错误,而不会一直阻塞到数据准备好。如上图在第四次调用时有一个数据报准备好了,所以这时数据会被复制到应用进程缓冲区,于是recvfrom成功返回数据
当一个应用进程这样循环调用recvfrom时,我们称之为轮询polling。这么做往往会耗费大量CPU时间,实际使用很少
例子
还是商场吃饭,只是现在可以取号了。不过仍然需要时不时的去看一下有没有叫到号

I/O 复用

IO多路复用.png
Linux I/O复用模型提供了select poll epoll三组系统调用可做选择,进程通过将一个或多个文件描述符(fd)传递给select或poll或epoll系统调用,通过它们来监测多个fd是否处于就绪状态。select或poll是顺序扫描fd是否就绪,而且支持的fd数量有限,因此使用上有制约。epoll调用基于事件驱动,因此性能更高,当fd就绪时会立即回调rollback
解释一下文件描述符
Linux 内核将所有外部设备都看做一个文件来操作,对一个文件的读写操作会调用内核提供的系统命令,返回一个file descriptor(fd 文件描述符)。而对一个socket的读写也会有相应的描述符,称为socket fd。
现在再来看一下上图,上图以select为例。不难发现进程会阻塞于select调用,直到所关注的某一个文件描述符(套接字)变为可读状态
例子
还是商场吃饭,但是现在你可以在手机APP上同时叫多个号了,只要多个号里面有一个号好了就会通知你了

信号驱动I/O

信号驱动I/O.png
信号驱动I/O的意思就是我们现在不用傻等着了,也不用去轮询。而是让内核在数据就绪时,发送信号通知我们。
调用的步骤是,我们通过系统调用sigaction,并注册一个信号处理的回调函数,该调用会立即返回,但是当内核数据就绪时,内核会为该进程产生一个SIGIO信号,并回调我们注册的信号回调函数,这样我们就可以在信号回调函数中系统调用recvfrom获取数据
例子
商场吃饭,只要取了号,你也不用去一直看看大屏幕有没有好了,要是叫到号了,会主动发消息告诉你了

异步I/O

异步IO.png
异步I/O 与 信号驱动I/O最大区别在于,信号驱动是内核通知我们何时开始一个I/O操作,而异步I/O是由内核通知我们I/O操作何时完成,两者有本质区别
例子
都不用去商场吃饭了,直接点个外卖,把等待上菜的时间也给省了

总结

本篇文章主要介绍了5种I/O模型,例子也是参考了网上个人觉得比较合理的解释,希望能对此有疑惑的同学有所帮助。另外文章主要参考了Unix 网络编程这本书,有兴趣的同学也可以深入了解

作者:香芋牛奶面包
链接:https://www.jianshu.com/p/d863a26dd32a
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。