在IO网络模型中大致可以分为,同步阻塞、同步非阻塞、异常非阻塞这三种类型,有些人可能对同步和阻塞这两个概念容易混淆。
同步阻塞
同步阻塞指的是用户的请求操作会引起进程的阻塞,直到IO完成。
举一个现实中的小场景:饭店烧菜。
在同步阻塞这种场景下,假如有10个客户点了10份砂锅,那么厨师只能站在砂锅旁烧一份等一份,期间不能做任何其他事情。
假如你使用多线程的方式,那也就相当于,厨师给每份砂锅都安排了一个徒弟专门看管,但看管期间小弟除了等砂锅烧开,也不能做其他任何事情。
10份砂锅:10个客户端请求。
厨师:服务端。
徒弟:每接收到一个请求就交给一个新的线程去处理(徒弟是有限的。。。)。
砂锅烧开了:请求的数据到达了,或者关注的事件产生了。
同步非阻塞
同步非阻塞就是用户的请求不会被阻塞了,无论是否有数据都会立即返回。
有了同步非阻塞,厨师就不用站在砂锅旁等了,只要你炉子多就可以10份同时烧,烧的过程中厨师还可以做点别的事情,比如去大厅招待招待客人(饭店人手紧缺),但是厨师必须要隔一段时间就得回厨房看看是否有砂锅烧好。
注意:无论是NIO还是多路复用IO都是同步非阻塞模型
NIO
炉子:炉子越多就意味着厨师一次性需要查看的砂锅数量就越多,也就越耗时。
从客厅回到厨房:每一个查看数据是否到达都会引起系统调用,用户态内核态的切换(这个比喻还不太准确,在NIO的时候,厨师实际上每次回一次厨房只能查看一份砂锅的情况,所以如果同时烧了10份,就需要来来回回10次。)。
多路复用
现在我们给10个砂锅编上号,前5个砂锅是三鲜砂锅,后5个砂锅是牛肉砂锅,并且为你准备了一个徒弟,厨师烧之前告诉徒弟如果是前5个砂锅,在烧开时再通知我,如果是后5个砂锅再烧5成熟时就通知我(往里加牛肉。。。)。
徒弟:内核空间。
给砂锅编号分别通知:就相当于告诉内核对什么样的事件感兴趣,当产生发生了用户感兴趣的事件时再通知用户。
多路复用中的select和epoll
多路复用中又有select、poll和epoll函数,其中有差异的主要就是select和epoll。
select:假设徒弟发现编号1的砂锅烧开了,那么就通知厨师,但是通知厨师时其实是告诉厨师前5个砂锅有情况了,厨师并不知道具体哪一个或者哪几个烧开了,所以必须再从5个砂锅中一个个查看。
epoll:简单点说就是徒弟直接告诉你具体哪一个砂锅烧开了,厨师直奔目标去即可。
NIO、多路复用IO比较
在NIO中,需要不断的询问内核是否有数据到达,而多路复用中如果发现有你所关心的事件到达时就会直接通知你,一个是你主动请求,一个是回调。
但是无论是NIO还是多路复用都需要你主动的去调用revcfrom方法,把数据从内核空间复制到用户空间,而这一段复制的过程是阻塞的,所以他们两都属于同步IO模型。
异步非阻塞
这种模型目前在linux中并还没有提供相应的支持,所以我们也不需要太多的关注。
假设你拥有了这种模型,那么现在你的厨师可以放心的去忙其他的事情了,你只需要告诉徒弟一旦有砂锅烧好就给我端到哪个餐桌上即可,所以现在你可以彻底安心的做其他事情了。