什么是NIO
NIO又叫New/Non-blocking IO,这个概念基本人人都听过,但是不一定每个人都懂他它的运行的原理。
这里我们来探讨这个问题,先用一个例子解释一下BIO到底阻塞了哪里。
/**
* 这是一个单线程BIOServer
* @author endless
* @create 2020-03-23
*/
public class BioServerDemo {
public static void main(String[] args) throws IOException {
// 创建ServerSocket,并绑定端口
ServerSocket serverSocket = new ServerSocket(9999);
System.out.println("服务启动成功");
while (true) {
Socket socket = serverSocket.accept();
System.out.println("连接成功");
System.out.println("准备接收数据");
byte[] bytes = new byte[1024];
socket.getInputStream().read(bytes);
System.out.println("接收到了数据:" + new String(bytes));
}
}
}
/**
* BIO client
*
* @author endless
* @create 2020-03-23
*/
public class BioClientDemo {
public static void main(String[] args) throws IOException {
// 连接Server
Socket socket = new Socket("127.0.0.1", 9999);
System.out.println("连接成功");
Scanner scanner = new Scanner(System.in);
// 循环等待输入消息
while (true) {
String str = scanner.next();
// 约定退出口令
if ("exit".equalsIgnoreCase(str)) {
socket.close();
System.exit(0);
}
socket.getOutputStream().write(str.getBytes());
socket.getOutputStream().flush();
}
}
}
先运行Server
命令行打印服务启动成功
,此时并无客户端连接,所以连接成功
并未打印,说明程序被阻塞在了serverSocket.accept()
方法
此时运行Client,Server打印日志连接成功
和准备接收数据
,此时Client尚未发送数据,Server被阻塞在socket.getInputStream().read(bytes)
上,因此其他客户端无法进行连接。
在Client输入Hello
回车,此时Server打印接收到了数据:Hello
,说明客户端的连接发送过来数据了,此时服务端线程才解阻塞,在这个情况下,这个Server没有办法处理并发,同时期只能处理一个连接。
那么BIO是如何实现并发呢?答案也很明显,就是使用多线程,我们对Server进行一些小改动。
/**
* 这是一个BIOServer
* @author endless
* @create 2020-03-23
*/
public class BioServerDemo {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(9999);
System.out.println("服务启动成功");
while (true) {
Socket socket = serverSocket.accept();
new Thread(()->{
System.out.println("连接成功");
System.out.println("准备接收数据");
byte[] bytes = new byte[1024];
try {
socket.getInputStream().read(bytes);
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("接收到了数据:" + new String(bytes));
}).start();
}
}
}
使用子线程来对接收到的Socket进行处理,这样每个连接都被阻塞在单独的线程上,就可以实现并发访问Server。
总结:BIO的阻塞有两个地方:accept()和read(),并且BIO的并发只能通过多线程。
但是这里会有一个问题,就是如果绝大部分的连接都没有进行数据传输,只是建立了连接,这样就会产生很多无效的线程,而线程又是非常宝贵的稀缺资源,这样就会白白损失很多的性能,这也是BIO最大的性能瓶颈。
那能不能只用一个线程就能实现并发并且处理全部的连接呢?是否能设计一个Api,让accept和read不再阻塞,使用一个线程就能处理并发连接呢?答案是肯定的,这里就要用到我们的今天的主角NIO了。
NIO在JDK中被封装在了一个新的类中,我们先来写一个例子,这个例子实现了使用单线程来处理多连接。
/**
* NIO Server Demo
*
* @author endless
*