I/O模型简介
引言
在深入学习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
接收数据,但是此时内核缓冲区
中数据报还未准备好,所以应用进程会一直阻塞直到内核缓冲区
有数据报到达且被复制到应用进程缓冲区
这里我们提一下这里的内核缓冲区
和应用进程缓冲区
所处的阶段
一个输入操作通常包括两个不同的阶段:
- 等待数据报准备好 (通常是等待数据从网络上到达,当所等待的分组数据到达后,会被复制到内核的某个缓冲区中)
- 从内核向进程复制数据 (把数据从内核缓冲区复制到应用进程缓冲区中)
例子
商场排队吃饭,只能老老实实排队,并且排队的时候不能做其他事情
非阻塞式I/O
如果不想进程一直阻塞在那里的话,我们可以设置本次套接字连接为非阻塞的
非阻塞IO.png查看上图可知,在设置连接为非阻塞时,当应用进程系统调用recvfrom
没有数据返回时,内核会立即返回一个EWOULDBLOCK
错误,而不会一直阻塞到数据准备好。如上图在第四次调用时有一个数据报准备好了,所以这时数据会被复制到应用进程缓冲区
,于是recvfrom
成功返回数据
当一个应用进程这样循环调用recvfrom
时,我们称之为轮询polling
。这么做往往会耗费大量CPU时间,实际使用很少
例子
还是商场吃饭,只是现在可以取号了。不过仍然需要时不时的去看一下有没有叫到号
I/O 复用
IO多路复用.pngLinux 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 网络编程
这本书,有兴趣的同学也可以深入了解