一、BIO通信
1、原理
BIO即同步阻塞模式一请求一应答的通信模型,该模型最大的问题就是缺乏弹性伸缩能力。当客户端并发访问量增加后,服务端的线程个数和客户端并发访问数呈1:1的正比关系。
由于线程是JAVA虚拟机非常宝贵的系统资源,当线程数膨胀之后,系统的性能将急剧下降,随着并发访问量的继续增大,系统会发生线程堆栈溢出、创建新线程失败等问题,
并最终导致进程宕机或者僵死,不能对外提供服务。
BIO的服务端通信模型:
(1)采用BIO通信模型的服务端,通常由一个独立的Acceptor线程负责监听客户端的连接;
(2)当接收到客户端的连接请求后,会为每一个客户端请求创建新的线程进行请求的处理;
(3)处理完成后通过输出流返回信息给客户端,响应完成后销毁线程;
(4)典型的一请求一应答的通信模型;
(5)缺点:缺乏弹性伸缩能力;
一个线程处理一个Socket连接,因为Java Socket是通过InputStream和OutputStream来进行网络读写操作,而这俩个的读写都是阻塞模式,
所以当某个Socket链路的读写操作没有完成时,排在后面的Socket连接是无法得到处理的,长时间的等待可能会导致超时,
因此在同步阻塞模式下,通常会采用一个Socket链路独占一个线程的模型。
如图所示:
二、伪异步IO通信
1、原理
为了解决同步阻塞IO(BIO)所面临的一个链路需要一个线程处理的问题,后来有人对它的线程模型进行了优化,后端通过一个线程池来处理多个客户端的请求接入,
形成客户端个数M:线程池最大线程数N的比例关系,其中M可以远远大于N,通过线程池可以灵活的调配线程资源,设置线程的最大值,防止由于海量并发接入导致线程耗尽。
伪异步IO通信特性:
(1)采用线程池和任务队列实现;
(2)线程池负责连接;
(3)M请求N应答;
(4)线程池阻塞;
当有新的客户端接入的时候,将客户端的Socket封装成一个Task(该任务实现java.lang.Runnable接口)投递到后端的线程池中进行处理,JDK的线程池维护一个消息队列和N个活跃线程对消息队列中的任务进行处理。
由于线程池可以设置消息队列的大小和最大线程数,因此,它的资源占用是可控的,无论多少个客户端并发访问,都不会导致资源的耗尽和宕机。
但是伪异步IO通信也有其缺陷,当有大量客户端请求的时候,随着并发访问量的增长,伪异步IO就会造成线程池阻塞。
如图所示:
三、NIO通信
1、原理
NIO是非阻塞IO(Non-block IO),也有人称之为New IO,因为它相对于之前的IO类库是新增的,所以被称为New IO,这是它的官方叫法。
它是在 JDK 1.4 中引入的。NIO 弥补了原来同步阻塞I/O 的不足,它在标准 Java 代码中提供了高速的、面向块的 I/O。
通过定义包含数据的类,以及通过以块的形式处理这些数据,NIO 不用使用本机代码就可以利用底层优化,这是原来的 I/O 包所无法做到的。
2、NIO之缓冲区Buffer
首先介绍缓冲区(Buffer)的概念,Buffer 是一个对象, 它包含一些要写入或者要读出的数据。 在 NIO类库 中加入 Buffer 对象,体现了新库与原 I/O 的一个重要区别。在面向流的 I/O 中,我们将数据直接写入或者将数据直接读到 Stream 对象中。
在 NIO 库中,所有数据都是用缓冲区进行处理的。在读取数据时,它是直接读到缓冲区中;在写入数据时,它也是写入到缓冲区中。任何时候访问 NIO 中的数据,我们都是通过缓冲区进行读写操作。
缓冲区实质上是一个数组。通常它是一个字节数组(ByteBuffer),也可以使用其它种类的数组。但是一个缓冲区不仅仅是一个数组,缓冲区提供了对数据的结构化访问,及维护读写位置(limit)等信息。
最常用的缓冲区是ByteBuffer,一个ByteBuffer提供了一组功能用于操作byte数组。除了ByteBuffer,还有其它的一些缓冲区,事实上,每一种Java基本类型(除了Boolean类型)都对应有一种缓冲区,
如下所示:
(1)ByteBuffer:字节缓冲区;
(2)CharBuffer:字符缓冲区;
(3)ShortBuffer:短整型缓冲区;
(4)IntBuffer:整型缓冲区;
(5)LongBuffer:长整型缓冲区;
(6)FloatBuffer:浮点型缓冲区;
(7)DoubleBuffer:双精度浮点型缓冲区;
3、NIO之通道Channel
Channel是一个通道,可以通过它读取和写入数据,它就像自来水管一样,网络数据通过Channel读取和写入。通道与流的不同之处在于通道是双向的。
而流只是在一个方向上移动(一个流必须是 InputStream 或者 OutputStream 的子类),而通道可以用于读、写或者同时用于读写。
4、NIO之多路复用器Selector
它是JAVA NIO编程的基础,熟练的掌握Selector对于掌握NIO编程至关重要。多路复用器提供选择已经就绪的任务的能力。
简单来讲,Selector会不断的轮询注册在其上的Channel,如果某个Channel上面有新的TCP连接接入、读和写事件,这个Channel就处于就绪状态,会被Selector轮询出来,
然后通过SelectionKey可以获取就绪Channel的集合进行后续的IO操作。
一个多路复用器Selector可以同时轮询多个Channel,由于JDK使用了epoll()代替传统的select实现,所以它并没有最大连接句柄1024/2048的限制。
这也就意味着只需要一个线程负责Selector的轮询,就可以接入成千上万的客户端,这的确是一个巨大的改进。
四、AIO通信
1、原理
与NIO不同,AIO需要一个连接注册读写事件和回调方法,当进行读写操作时,只须直接调用API的read或write方法即可。
这两种方法均为异步的,对于读操作而言,当有流可读取时,操作系统会将可读的流传入read方法的缓冲区,并通知应用程序;
对于写操作而言,当操作系统将write方法传递的流写入完毕时,操作系统主动通知应用程序。 即可以理解为,read/write方法都是异步的,完成后会主动调用回调函数。
AIO异步通道提供了两种方式获取操作结果:
(1)通过java.util.concurrent.Future
类来表示异步操作的结果;
(2)在执行异步操作的时候传入一个java.nio.channels.CompletionHandler
接口的实现类作为操作完成的回调。
AIO的异步套接字通道是真正的异步非阻塞IO,对应于UNIX网络编程中的事件驱动IO(AIO),它不需要通过多路复用器(Selector)对注册的通道进行轮询操作即可实现异步读写,从而简化了NIO的编程模型。
AIO通信的特性:
(1)连接注册读写事件和回调函数;
(2)读写方法异步;
(2)主动通知程序;