网络IO模型总共有5种,分别是:阻塞IO、非阻塞IO、多路复用IO、信号驱动IO、异步IO

前四个都是同步IO,在内核数据copy到用户程序时都是阻塞的,而第五个则是异步的。

对网络IO来说,它会涉及两个部分,一个是用户空间、一个是内核。当用户程序给内核发送请求,表示需要一些数据的时候,内核就会开始准备,里面的数据需要互相copy,不能直接拿来就用。 


目录

 

阻塞IO

非阻塞IO

IO多路复用

信号驱动IO

总结 


阻塞IO

           

我在烧水,把水倒入壶中开始烧之后,就一直盯在那等着,不做其他的事情。

当用户进程调用了 recvfrom这个系统调用的时候,内核首先就要开始准备数据,但是对于网络IO而言,很多数据一开始还没有到达,所以这时候就只能wait。直到内核等到数据报准备好了就开始将数据从内核拷贝到用户空间中,拷贝完成后才会返回结果,整个过程中,用户一直是处于阻塞状态,一直到返回了成功结果后才会解除这种状态。

所以阻塞IO可以被分为两个阶段,第一个阶段是内核一直在等待数据报准备好,这时,用户进程的状态是阻塞的。第二个阶段是把数据从内核复制到用户空间里,这时,用户进程的状态还是阻塞的。这就相当于我们最开始学习的BIO,一个线程一个请求。在高并***况下,CPU切换上下文消耗太大。


非阻塞IO

 

 

我在烧水,在烧水的过程中我可以玩手机,玩电脑,看书等,但是得要不停的去看一下水烧开了没。

非阻塞IO与阻塞IO前面有些地方比较像,就比如当用户进程调用了 recvfrom这个系统调用的时候,内核首先就要开始准备数据,但是对于网络IO而言,很多数据一开始还没有到达,阻塞IO会选择一直在那wait,而非阻塞IO会选择返回一个error,表示这个数据报还没准备好。如果用户进程判断这个结果是个error的时候,就知道就知道数据报还没有准备好。就过会再去发送,以轮询的方式去发送系统调用,直到数据报准备好。这时内核开始复制数据到用户空间,复制完成后再返回成功结果。

所以在非阻塞IO中,用户进程就是不断地去给内核以轮询的方式问内很是否准备还,这里第一个阶段的用户状态是非阻塞的,当内核把数据还没准备好时,就返回一个error回去。第二个阶段的用户状态是阻塞的,和阻塞IO的第二阶段一样。

一般而言,轮询这种方式只适用于特殊的场景,因为轮询方式对CPU资源有很大的浪费。


IO多路复用

 

IO多路复用其实和阻塞IO差不太多,主要就是它有select/epoll的方式,它可以同时处理多个网络连接的IO。当用户进程调用了 select这个系统调用的时候,就会开始阻塞,内核监视select负责的所有socket,当其中有一个准备好之后就会返回可读条件给用户进程。然后用户进程调用recvfrom这个系统调用,数据从内核拷贝到用户进程中,再返回成功结果。

在这里,select它只负责等待而recvfrom只负责拷贝。而且从图上看,IO多路复用和阻塞IO是差不多的,但是IO多路复用的优势在于select可以同时处理多个连接。所以当连接数比较少的时候,还不如用多线程+阻塞IO来的快。


信号驱动IO

 

 

这个IO模型一般不太用,它就相当于是我烧水的时候,买那种水开了之后就会响的那种壶,当水开之后,壶会发出声音提示我们水开了。

用户进程首先先调用SIGIO的信号处理程序也就是sigaction系统调用,然后内核会立刻返回给用户进程后内核开始等待数据,等到数据准备完成后,内核再给用户进程递交SIGIO,然后用户进程调用recvfrom,内核开始拷贝数据到用户空间中,拷贝完成后在发送成功提示。

 

 


 

这个IO模型可以说是这5个网络IO模型中,效率最高的一个了。它就相当于当我想烧水的时候,我说一句siri,我要烧水。然后机器人自己将水接好然后去烧,等烧好后siri再告诉我水已经烧好了。

用户进程首先发送一个aio_read系统调用后,内核会直接返回,在返回的同时然后开始准备数据报。接着内核等待数据,等到数据准备好之后它不会向前面4个那样提示用户进程,我已经准备好了,而是自己默默地将数据报从内核复制到用户空间里面。在复制完成后才会发一个信号给用户进程。

其中,这个IO模型它的第一阶段和第二阶段都是异步的,而用户进程只需要给内核发送一个aio_read后就可以了,就可以忙自己的事去了。等到内核给用户进程发送一个信号之后,再去处理数据。


总结