Java的中BIO、NIO、AIO-2

Java的中BIO、NIO、AIO-2

举个栗子

接上一篇接着说,C/S模式、Reactor模式、Proactor模式是服务器处理IO常用的处理模型,这一篇就来解释一下这几种模式:
以一个餐饮为例,每一个人来就餐就是一个事件,他会先看一下菜单,然后点餐。就像一个网站会有很多的请求,要求服务器做一些事情。处理这些就餐事件的就需要我们的服务人员了。

在多线程处理的方式会是这样的:

一个人来就餐,一个服务员去服务,然后客人会看菜单,点菜。 服务员将菜单给后厨。

二个人来就餐,二个服务员去服务……

五个人来就餐,五个服务员去服务……

这个就是多线程的处理方式,一个事件到来,就会有一个线程服务。很显然这种方式在人少的情况下会有很好的用户体验,每个客人都感觉自己是VIP,专人服务的。如果餐厅一直这样同一时间最多来5个客人,这家餐厅是可以很好的服务下去的。(这样就很熟悉了,这既是最简单来一个Socket连接,就创建一个线程。)

但是越来越多的人对这家餐厅满意,客源又多了,同时来吃饭的人到了20人,老板高兴不起来了,再请服务员吧,占地方不说,还要开工钱,再请人就攒不到钱了。怎么办呢?老板想了想,10个服务员对付20个客人也是能对付过来的,服务员勤快点就好了,伺候完一个客人马上伺候另外一个,还是来得及的。综合考虑了一下,老板决定就使用10个服务人员的线程池啦。(创建固定数量的线程池,建一个任务队列,使用多线程的方式对任务进行处理)
但是如果人数太多,那么10个人的服务员根本忙不过来,同样,没有人的时候,10个人的服务员在闲等着又造成了很大的资源浪费。

老板后来发现,客人点菜比较慢,大部服务员都在等着客人点菜,其实干的活不是太多。老板能当老板当然有点不一样的地方,终于发现了一个新的方法,那就是:当客人点菜的时候,服务员就可以去招呼其他客人了,等客人点好了菜,直接招呼一声“服务员”,马上就有个服务员过去服务。老板决定改变经营模式。(这就是Reactor模式!!)

后面老板做出了名声,有钱了,萌生出一个新的想法,做外卖。用户打电话告诉接单员他的订单,然后接单员把订单发给餐饮部门,接单员可以继续工作接单,等餐饮部门把订单做好的时候,告诉接单员订单完成来取饭,接单员根据订单信息把饭菜交给外卖小哥送出去。(这就是Proactor模式!!)

前面是举例子说明这几种模式,可能这样理解起来很懵,下面就每种模式详细的说明一下:

1. BIO中的C/S编程模型

网络编程的基本模型是C/S模型,也就是两个进程间的通信。服务端提供IP和监听端口,客户端通过向服务器端的端口发起连接请求,通过三次握手如果连接成功,双方就能进行套接字通信了,这就是TCP连接,当然只是废话。
在传统的同步阻塞开发模型中,服务器ServerSocket负责绑定IP地址,启动监听端口;Socket负责发起连接,连接成功之后,双方通过输入输出流进行同步阻塞式通信。采用BIO通信模型的服务器端,通常有一个独立的线程负责监听客户端的连接,它接收到客户端的请求连接之后会为每一个客户端创建一个线程进行链路的连接,连接结束之后销毁线程。看下图示例:

BIO通信模型

BIO通信模型

该模型最大的缺点是缺乏弹性伸缩能力,服务端线程个数和客户端的并发访问数是1:1的关系,当客户端的数目过多的时候,服务器可能承受不住,频繁的创建、销毁线程也很消耗系统资源。

2.伪装异步编程模型

一对一的TCP连接服务太浪费资源,为了改进这一点,可以使用线程池来管理这些线程,为什么是伪异步呢,因为其实对于线程池中线程而言还是一对一的服务这点并没有什么改变。使用线程池知识能够对资源实现有效的管控和约束。

伪异步IO编程模型

伪异步IO编程模型

当线程池中的数目有限的时候,如果有大量的并发请求,超过最大线程使用数目之后只能进行等待,知道线程池中有空闲的线程可以使用,同时线程对socket的处理模型还是采用阻塞的方式进行处理。

3.Reactor模式

Reactor模式

Reactor模式

Reactor模式用于同步IO。开发者开始的时候要把自己感兴趣的时间注册到事件分离器,并提供相应的处理函数。时间分离器等待某个事件可应用或者某个操作的状态发生改变(比如文件描述符可读写、或者是Socket可读写),事件分离器会把这个事件传给时间注册的事件处理器或者回调函数,由其来完成实际的读写操作。下面以读操作为例来说明一下Reactor的具体步骤:

  1. 程序注册读就绪事件和相关联的处理器
  2. 事件分离器等待事件的发生
  3. 当发生读就绪事件的时候,事件分离器调用第一步注册的事件处理器
  4. 事件处理器执行实际的读取操作。如果需要,它可以再次的宣称对这个读就绪事件感兴趣,重复上面的步骤。

觉得这样说明起来其实很虚,第三篇将会演示具体的代码。

4.Proactor模式

Proactor模式

Proactor模式

Proactor模式用于异步IO。它其实和reactor模式很相似,也是需要先注册事件到时间分离器,并提供相应的处理函数。但是这个事件不是就绪事件,而是直接发起一个异步的读写操作(请求)事件,但是实际的工作有操作系统完成。在发起事件的时候,需要的提供的参数包括用于存放数据的缓存区,以及这个请求完成之后的回调函数等信息。事件分离器等待这个事件的完成,然后转发完成后的事件给相应的处理函数或者回调等。
下面来以读事件为例说明一下该模式的具体步骤:

  1. 应用程序初始化一个异步读取操作,然后注册相应的事件处理器,此时事件处理器不关注读取就绪事件,而是关注读取完成事件,这是区别于Reactor的关键。
  2. 事件分离器等待读取操作完成事件
  3. 在事件分离器等待读取操作完成的时候,操作系统调用内核线程完成读取操作,并将读取的内容放入用户传递过来的缓存区中。这也是区别于Reactor的一点,Proactor中,应用程序需要传递缓存区。
  4. 事件分离器捕获到读取完成事件后,激活应用程序注册的事件处理器,事件处理器直接从缓存区读取数据,而不需要进行实际的读取操作。如果有需要还可以继续注册事件到事件处理器,重复上面的步骤。

简单总结一下,Reactor和Proactor的异同:

  • 他们相同的地方框架大致相同,程序都需要注册相应的时间到事件分离器,事件分离器根据状态的变化来告知相应的事件处理器。都是事件驱动型的IO通信复用模型。
  • 他们不同的地方,关注的事件不一样,Reactor模式关注的是时间的就绪状态,Proactor模式关注的是事件的完成状态。Reactor模式的程序自己读取或者写入数据,而Proactor模式中,应用程序不需要进行实际的读写过程,它只需要从缓存区读取或者写入即可,操作系统会读取缓存区或者写入缓存区到真正的IO设备(Proactor需要操作系统提供完善的异步操作的API接口)。

所以总的来说,同步和异步是相对于应用和内核的交互方式而言的,同步 需要主动去询问,而异步的时候内核在IO事件发生的时候通知应用程序,而阻塞和非阻塞仅仅是系统在调用系统调用的时候函数的实现方式而已。

参考资料

猜你喜欢

转载自www.cnblogs.com/chailinbo/p/9226274.html