BIO、NIO、AIO通讯机制

1、BIO的理解

首先咱们经过通讯模型图来熟悉下BIO的服务端通讯模型:采用BIO通讯模型的服务端,一般由一个独立的Acceptor线程负责监听客户端的链接,它接收到客户端的链接请求以后为每一个客户端建立一个新的线程进行链路处理,处理完成以后,经过输出流返回应答给客户端,线程销毁。这就是典型的一请求一应答通讯模型。这个是在多线程状况下执行的。当在单线程环境下时,在while循环中服务端会调用accept方法等待接收客户端的链接请求,一旦接收到一个链接请求,就能够创建socket,并在该socket上进行读写操做,此时不能再接收其它客户端的链接请求,只能等待同当前链接的客户端的操做执行完成。 
编程

 

该模型最大的问题就是缺少弹性伸缩能力,当客户端并发访问量增长后,服务端的线程个数和客户端并发访问数呈1:1的正比关系,因为线程是Java虚拟机很是宝贵的系统资源,当线程数膨胀以后,系统的性能将急剧降低,随着并发访问量的继续增大,系统会发生线程堆栈溢出、建立新线程失败等问题,并最终致使进程宕机或者僵死,不能对外提供服务。后端

 

2、伪异步I/O编程

为了解决同步阻塞I/O面临的一个链路须要一个线程处理的问题,后来有人对它的线程模型进行了优化,后端经过一个线程池来处理多个客户端的请求接入,造成客户端个数M:线程池最大线程数N的比例关系,其中M能够远远大于N,经过线程池能够灵活的调配线程资源。设置线程的最大值,防止因为海量并发接入致使线程耗尽。 
采用线程池和任务队列能够实现一种叫作伪异步的I/O通讯框架。模型图以下。 
数组

 当有新的客户端接入时,将客户端的Socket封装成一个Task(该任务实现Java.lang.Runnablle接口)投递到后端的线程池中进行处理,JDK的线程池维护一个消息队列和N个活跃线程对消息队列中的任务进行处理。因为线程池能够设置消息队列的大小和最大线程数,所以,它的资源占用是可控的,不管多少个客户端并发访问,都不会致使资源的耗尽和宕机。 服务器

因为线程池和消息队列都是有界的,所以,不管客户端并发链接数多大,它都不会致使线程个数过于膨胀或者内存溢出,相对于传统的一链接一线程模型,是一种改良。 
伪异步I/O通讯框架采用了线程池实现,所以避免了为每一个请求都建立一个独立线程形成的线程资源耗尽问题。可是因为它底层的通讯依然采用同步阻塞模型,所以没法从根本上解决问题。 
经过对输入和输出流的API文档进行分析,咱们了解到读和写操做都是同步阻塞的,阻塞的时间取决于对方IO线程的处理速度和网络IO的传输速度,本质上讲,咱们没法保证生产环境的网络情况和对端的应用程序能足够快,若是咱们的应用程序依赖对方的处理速度,它的可靠性就会很是差。网络

 

3、NIO编程(非阻塞IO)

与Socket类和ServerSocket类相对应,NIO也提供了SocketChannel和ServerSocketChannel两种不一样的套接字通道实现,在JDK1.4中引入。这两种新增的通道都支持阻塞和非阻塞两种模式。阻塞模式使用很是简单,可是性能和可靠性都很差,非阻塞模式则正好相反。咱们能够根据本身的需求来选择合适的模式,通常来讲,低负载、低并发的应用程序能够选择同步阻塞IO以下降编程复杂度,可是对于高负载、高并发的网络应用,须要使用NIO的非阻塞模式进行开发。 
首先来了解一些概念 多线程

(1)缓冲区Buffer 
Buffer是一个对象,它包含一些要写入或者要读出的数据,在NIO库中,全部数据都是用缓冲区处理的。在读取数据时,它是直接读到缓冲区中的;在写入数据时,写入到缓冲区中,任什么时候候访问NIO中的数据,都是经过缓冲区进行操做。 
缓冲区实质上是一个数组。一般它是一个字节数组(ByteBuffer),也可使用其余种类的数组,可是一个缓冲区不只仅是一个数组,缓冲区提供了对数据的结构化访问以及维护读写位置(limit)等信息。经常使用的有ByteBuffer,其它还有CharBuffer、ShortBuffer、IntBuffer、LongBuffer、FloatBuffer、DoubleBuffer 并发

 

(2)通道Channel 
Channel是一个通道,能够经过它读取和写入数据,它就像自来水管同样,网络数据经过Channel读取和写入。通道与流的不一样之处在于通道是双向的,流只是一个方向上移动(一个流必须是InputStream或者OutputStream的子类),并且通道能够用于读、写或者用于读写。同时Channel是全双工的,所以它能够比流更好的映射底层操做系统的API。特别是在Unix网络编程中,底层操做系统的通道都是全双工的,同时支持读写操做。咱们经常使用到的ServerSocketChannnel和SocketChannel都是SelectableChannel的子类。 框架


(3)多路复用器Selector 
多路复用器Selector是Java NIO编程的基础,多路复用器提供选择已经就绪的任务的能力,简单的说,Selector会不断的轮询注册在其上的Channel,若是某个Channel上面有新的TCP链接接入、读和写事件,这个Channel就处于就绪状态,会被Selector轮询出来,而后经过SelectionKey能够获取就绪Channel的集合,进行后续的I/O操做。异步

 
一个多用复用器Selector能够同时轮询多个Channel,因为JDK使用了epoll()代替传统的select实现,因此它并无最大链接句柄1024/2048的限制,这也意味着只须要一个线程负责Selector的轮询,就能够接入成千上万的客户端。 
socket

 


尽管NIO编程难度确实比同步阻塞BIO大不少,可是咱们要考虑到它的优势: 

(1)客户端发起的链接操做是异步的,能够经过在多路复用器注册OP_CONNECT等后续结果,不须要像以前的客户端那样被同步阻塞。 

(2)SocketChannel的读写操做都是异步的,若是没有可读写的数据它不会同步等待,直接返回,这样IO通讯线程就能够处理其它的链路,不须要同步等待这个链路可用。 

(3)线程模型的优化:因为JDK的Selector在Linux等主流操做系统上经过epoll实现,它没有链接句柄数的限制(只受限于操做系统的最大句柄数或者对单个进程的句柄限制),这意味着一个Selector线程能够同时处理成千上万个客户端链接,并且性能不会随着客户端的增长而线性降低,所以,它很是适合作高性能、高负载的网络服务器。

  

4、AIO(异步非阻塞IO)

JDK1.7升级了NIO类库,升级后的NIO类库被称为NIO2.0。也就是咱们要介绍的AIO。NIO2.0引入了新的异步通道的概念,并提供了异步文件通道和异步套接字通道的实现。异步通道提供两种方式获取操做结果。 

(1) 经过Java.util.concurrent.Future类来表示异步操做的结果;

(2) 在执行异步操做的时候传入一个Java.nio.channels. 


CompletionHandler接口的实现类做为操做完成的回调。 

NIO2.0的异步套接字通道是真正的异步非阻塞IO,它对应UNIX网络编程中的事件驱动IO(AIO),它不须要经过多路复用器(Selector)对注册的通道进行轮询操做便可实现异步读写,从而简化了NIO的编程模型。 

咱们能够得出结论:异步Socket Channel是被动执行对象,咱们不须要像NIO编程那样建立一个独立的IO线程来处理读写操做。对于AsynchronousServerSocketChannel和AsynchronousSocketChannel,它们都由JDK底层的线程池负责回调并驱动读写操做。正由于如此,基于NIO2.0新的异步非阻塞Channel进行编程比NIO编程更为简单。

 

总结:

由上述总结得出,并不意味着全部的Java网络编程都必需要选择NIO和Netty,具体选择什么样的IO模型或者NIO框架,彻底基于业务的实际应用场景和性能诉求,若是客户端并发链接数很少,周边对接的网元很少,服务器的负载也不重,那就彻底不必选择NIO作服务端;若是是相反状况,那就考虑选择合适的NIO框架进行开发。

相关文章
相关标签/搜索