BIO:
同步阻塞式IO,即当当前IO操作未完成时,不能进行其他操作。当服务器监听到有客户端请求连接时,就会创建一个新的线程去处理客户端的需求。
BIO、NIO - 图1
代码如下:
private static void start(int port) throws IOException { Socket socket; sever = new ServerSocket(port);//监听端口 System.out.println(“服务端已启动”); while (true) {//通过无限循环监听端口 socket = sever.accept();//监听是否有服务端连接,该方法阻塞式 in = new BufferedReader(new InputStreamReader(socket.getInputStream())); new ServerThread(socket); } }
缺点:
该模型最大的问题就是缺乏弹性伸缩能力,当客户端并发访问量增加后,服务端的线程个数和客户端并发访问数呈1:1的正比关系,Java中的线程也是比较宝贵的系统资源,线程数量快速膨胀后,系统的性能将急剧下降,随着访问量的继续增大,系统最终就死掉了。
可以使用线程池来改进。
BIO、NIO - 图2
private static void start(int port) throws IOException {
Socket socket;
sever = new ServerSocket(port);//监听端口
service = Executors.newFixedThreadPool(50);
System.out.println(“服务端已启动”);
while (true) {//通过无限循环监听端口
socket = sever.accept();//监听是否有服务端连接
in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
service.execute(new ServerThread(socket));
}
}
缺点:
如果使用CachedThreadPool线程池),其实除了能自动帮我们管理线程(复用),看起来也就像是1:1的客户端:线程数模型,而使用FixedThreadPool我们就有效的控制了线程的最大数量,保证了系统有限的资源的控制,实现了N:M的伪异步I/O模型。
但是,正因为限制了线程数量,如果发生大量并发请求,超过最大数量的线程就只能等待,直到线程池中的有空闲的线程可以被复用。而对Socket的输入流就行读取时,会一直阻塞,直到发生:
有数据可读
可用数据以及读取完毕
发生空指针或I/O异常
所以在读取数据较慢时(比如数据量大、网络传输慢等),大量并发的情况下,其他接入的消息,只能一直等待,这就是最大的弊端。
而后面即将介绍的NIO,就能解决这个难题。
NIO:

BIO是监听到一个请求连接就创建一个线程,如果这个连接什么都不做, 会造成不必要的线程开销,在java1.4后,就有了NIO,它是同步非阻塞式IO,服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理,这样就大大减少了不必要的线程开销。
NIO与BIO最大的区别就是只需要开启一个线程就可以处理来自多个客户端的IO事件。这就需要提到多路复用器
多路复用器 Selector
selector一般称作选择器,也叫做多路复用器作用是检查一个或者多个NIO Channel(通道)的状态是否处于可读、可写。可以实现单线程管理多个channels,也可以管理多个网络请求
Selector是Java NIO 编程的基础。
Selector提供选择已经就绪的任务的能力:Selector会不断轮询注册在其上的Channel,如果某个Channel上面发生读或者写事件,这个Channel就处于就绪状态,会被Selector轮询出来,然后通过SelectionKey可以获取就绪Channel的集合,进行后续的I/O操作。
一个Selector可以同时轮询多个Channel,因为JDK使用了epoll()代替传统的select实现,所以没有最大连接句柄1024/2048的限制。所以,只需要一个线程负责Selector的轮询,就可以接入成千上万的客户端。
它可以用来监听来自多个客户端的IO请求,主要有以下几步:
(1)当服务器监听有客户端的连接请求时,就会为其建立通道(Chnnel),然后返回继续监听,若同时有多个客户端连接请求到来也可以全部收到,依次为它们都建立通通道。这个通道是非阻塞式的,即可读也可写。
(2)若服务端监听到来自已经创建了通信套接字的客户端发送来的数据,就会调用对应接口处理接收到的数据,若同时有多个客户端发来数据也可以依次进行处理。
即在一个线程中可以调用多路复用接口(java中是select)阻塞同时监听来自多个客户端的IO请求,一旦有收到IO请求就调用对应方法处理。

BIO NIO
面向流(Stream Oriented) 面向缓冲区(Buffer Oriented)
阻塞IO(Blocking IO) 非阻塞IO(Non Blocking IO)
(无) 选择器(Selectors)
单向的 双向的
  1. BIO 以流的方式处理数据,而 NIO 以块(缓冲区)的方式处理数据,块 I/O 的效率比流 I/O 高很多。
  2. Selector 能够检测多个注册的通道上是否有事件发生(注意:多个Channel以事件的方式可以注册到同一个Selector),如果有事件发生,便获取事件然后针对每个事件进行相应的处理。这样就可以只用一个单线程去管理多个通道,也就是管理多个连接和请求。只有在连接/通道真正有读写事件发生时,才会进行读写,就大大地减少了系统开销,并且不必为每个连接都创建一个线程,不用去维护多个线程。避免了多线程之间的上下文切换导致的开销。

这里要区分连接和线程,连接相当于客户,线程相当于银行柜台接待人员,不能为每个客户都创建一个线程,即增加一个接待人员,因为不是每个客户每时每刻都有业务要办理。

BIO:(Blocking IO)

BIO

NIO (Non-Blocking IO)

NIO
Selector 一般称为选择器 ,也可以翻译为多路复用器,

事件

连接:Connect(连接就绪)、Accept(接受就绪)、
读写:Read(读就绪)、Write(写就绪)