本文分享的是理解五种IO模型的基本概念, 重点是IO多路转接。
什么是IO
其实我们在读写数据的时候,比如使用了write、read、recv、send等等函数,本质是对数据的拷贝。即在写的时候,将数据拷贝到了TCP协议的发送缓冲区中。在读的时候,将接收到的数据先拷贝到接收缓冲区中。
那么在读写的时候,等待是离不开的环节。因此,我们可以这样理解IO:
IO = 等待 + 拷贝数据
而对于我们研究IO的时候,其实最主要最多的话题,都是在对“等待”做研究或者是等待的方式。
既然IO是等待+拷贝数据,那么高效IO的本质,就是需要减少等待在单位时间内的比重!
五种IO模型概念
我将用钓鱼作为例子,通俗地介绍五种IO模型是哪五种。
阻塞IO
有一天,张三来到鱼塘边钓鱼,张三把钓鱼的准备工作做好之后,就一直在那坐着,盯着鱼漂,啥也不去干,就一直在那盯着,直到鱼儿上钩,就拉钩成功钓到鱼。张三这种行为,换在IO中,便是阻塞IO。
阻塞IO:在内核将数据准备好之前, 系统调用会一直等待。所有的套接字, 默认都是阻塞方式。
阻塞IO是最常见的IO模型。
非阻塞IO
过一会儿,一个叫李四的人也来钓鱼了。李四跟张三的钓鱼方式不一样,李四没有一直盯着看,而是在等鱼儿上钩的期间,一会看看书,一会刷刷视频等等,然后过一会看看鱼漂有没有动静,如果没有,那么又转头去做别的事情。直到鱼儿上钩。李四这种行为,换在IO中就叫做非阻塞IO。
非阻塞IO往往需要程序员循环的方式反复尝试读写文件描述符, 这个过程称为轮询。这对CPU来说是较大的浪费, 一般只有特定场景下才使用。
非阻塞IO: 如果内核还未将数据准备好, 系统调用仍然会直接返回, 并且返回EWOULDBLOCK错误码。
信号驱动
又过一会,一个叫王五的人也来钓鱼了,王五是一个聪明人,他将一个铃铛挂在鱼线上(假设没有其它外界影响使得铃铛响),然后在等待鱼上钩的期间,王五不会去主动看看鱼漂有没有动静,而是一直去看别的事情,直到铃铛响起,王五就知道鱼儿上钩了!王五这种行为,就是IO信号驱动。
信号驱动IO: 内核将数据准备好的时候, 使用SIGIO信号通知应用程序进行IO操作。
多路转接
再过一会,一个叫赵六的人也来钓鱼了。赵六带来了一百根鱼竿,同时用来钓鱼!等待鱼儿上钩期间,赵六跟张三的阻塞IO类似,只不过,赵六的一百根鱼竿,能够极大地提高钓鱼效率!赵六这个行为,就是IO多路转接。
IO多路转接: 虽然从流程图上看起来和阻塞IO类似. 实际上最核心在于IO多路转接能够同时等待多个文件描述符的就绪状态。
在IO多路转接中,使用系统调用select来进行等待,使用recvfrom来进行拷贝数据。
以上四种方法都属于同步IO。
异步IO
此时一个叫田七的人路过鱼塘,看见那么多人在钓鱼,他就很想吃鱼了,但是他不想钓,于是他喊他的助手小明去帮他钓,田七给了小明一个装鱼的桶子,并告诉小明:“你钓完鱼了就跟我说,就把桶子给我吧!”。然后小明就去钓鱼了,田七就暂时离开,即不负责钓鱼的等待操作,也不负责把鱼钓上来的操作。田七这种行为,便是异步IO。
异步IO: 由内核在数据拷贝完成时, 通知应用程序(而信号驱动是告诉应用程序何时可以开始拷贝数据)。
同步IO和异步IO
同步和异步关注的是消息通信机制。
所谓同步,就是在发出一个调用时,在没有得到结果之前,该调用就不返回. 但是一旦调用返回,就得到返回值了。换句话说,就是由调用者主动等待这个调用的结果。
异步则是相反, 调用在发出之后,这个调用就直接返回了,所以没有返回结果。换句话说,当一个异步过程调用发出后,调用者不会立刻得到结果。而是在调用发出后, 被调用者通过状态、通知来通知调用者,或通过回调函数处理这个调用。
就拿上述的五种IO模型来讲,前四种,不管是谁,起码都有等待或者把鱼钓上来这两个操作的其中之一,即参与了就属于同步。而在异步IO中,不管是等待还是执行操作,都没有参与。
另外,我们需要区分这里的同步与异步,同步与互斥。
同步与异步是消息通信的,而同步与互斥是进程/线程的制约关系。因此,我们在看到同步的时候,需要区分是同步通信异步通信的同步, 还是同步与互斥的同步。