这节重点探讨两个概念性的问题:什么是NIO?为什么要用NIO?
在引入NIO之前,有必要聊下BIO,因为NIO是相对于BIO所提出的新的Java IO api,但这里不会深入,每本java书籍都会介绍javaIO的。
BIO:blocking IO,即阻塞IO,是java的传统IO api,以流的方式处理数据,一般可分为文件IO(处理文件)和网络IO(Socket网络编程),这里重点探讨网络IO,通过一个小的例子,让大家了解阻塞IO的具体含义。
服务端代码:(代码中用到java8中的try-with-resources写法,可以自动关闭流和socket连接,jdk1.7就有了try-with-resources)
public class BIOServer {
public static void main(String[] args) {
while (true){
try(ServerSocket serverSocket = new ServerSocket(9000); //创建ServerSocket对象
//监听客户端的连接请求
final Socket socket= serverSocket.accept(); //当没有客户端连接时,就会阻塞到这里,一直等待客户端的连接请求,下面的代码不会执行
//从连接中取出输入流来接收客户端发来的消息
InputStream is = socket.getInputStream(); //阻塞
//从连接中取出输出流回应客户端
OutputStream os = socket.getOutputStream()) {
System.out.println("我是风清扬"); //在客户端启动之前,不会打印输出,因为前面的accept()方法已经使线程阻塞,等待客户端连接,客户端启动后,该语句执行
//读取消息
byte[] b = new byte[1024];
int len;
String clientIP = socket.getInetAddress().getHostAddress();
while ((len = is.read(b)) != -1){ //当没有读取到客户端发送的消息时,就会阻塞到这里,InputStream.read()方法下面的代码不会执行
System.out.println(clientIP + "说:" + new String(b,0,len));
}
System.out.println("我是令狐冲"); //该语句在客户端没有发送消息前不会执行,只有上面的read()方法接收到服务端发送的消息时才会执行该语句
//发送消息
Scanner scanner = new Scanner(System.in);
String msg = scanner.nextLine();
os.write(msg.getBytes());
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
客户端代码:
public class BIOClient {
public static void main(String[] args) {
while (true){
try(Socket socket = new Socket(InetAddress.getByName("127.0.0.1"),9000); // 创建Socket对象
//从连接中取出输入流读消息
final InputStream is = socket.getInputStream(); //阻塞
//从连接中取出输出流发消息
final OutputStream os = socket.getOutputStream()) {
System.out.println("请输入:");
Scanner scanner = new Scanner(System.in);
String msg = scanner.nextLine();
os.write(msg.getBytes());
socket.shutdownOutput();
byte[] b = new byte[1024];
int len;
while ((len = is.read(b)) != -1){ //当没有读取到服务端发送的消息时,就会阻塞到这里,InputStream.read()方法下面的代码不会执行
System.out.println("服务端说:" + new String(b));
}
System.out.println("我是逍遥子"); //该语句在服务端没有发送消息前不会执行,只有上面的read()方法接收到服务端发送的消息时才会执行该语句
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
执行结果分析:
BIOServer控制台输出:
BIOClient控制台输出:
一、什么是NIO:
百度百科对NIO的解释如下:
NIO:non-blocking IO,即非阻塞IO,以块的方式处理数据,相比于BIO用流的方式处理数据,块IO的效率要比流IO高很多,有的书籍也叫做新IO(New IO),这里我们与BIO进行比较,non-blocking IO更加语义化,能够突出对比性。JDK1.4中,新增了许多新的用于处理输入输出的类,这些类放在java.nio包下(java11是放在java.base模块下的java.nio包中):
NIO提出了三大核心的概念:Channel(通道)、Buffer(缓冲区)、Selector(选择器),后面的内容也是主要围绕这三个核心概念展开,再适当加一些charset字符集的东西。相对于BIO基于 字节流和字符流 对数据进行操作,NIO是基于Channel和Buffer对数据进行操作。读数据的时候先将数据从通道读取到缓冲区,写数据的时候要将数据从缓冲区写如到通道,Selector用于监听多个通道的事件,从而使单个线程就可以监听多个客户端。下面通过安倍和特朗普通电话的事例代码(本人属于段子手类型,平时扫地,偶尔开车,如果例子举的有不当的地方,还请大家包涵),让大家了解NIO相对于BIO的非阻塞的特性,涉及到的api不了解也没关系,后面会详细介绍,可以把代码运行一遍,体会下NIO的非阻塞特性。
客户端代码:
public class NioClient {
public static void main(String[] args) throws IOException {
//1.得到一个通道
final SocketChannel channel = SocketChannel.open();
//2.设置非阻塞方式,默认是阻塞方式,所以需要手动设置为非阻塞方式
channel.configureBlocking(false);
//3.设置服务端的ip和端口号
InetSocketAddress address = new InetSocketAddress("127.0.0.1",9000);
//4.连接服务器端
if(!channel.connect(address)){ //当connect连接没有成功时,尝试再次连接服务端,但不能再用connect方法连接,此方法只能连接一次,再次连接需要用finishConnect()方法
while (!channel.finishConnect()){ //当连接服务端时,客户端不会处于阻塞状态,还可以继续执行下面的代码
System.out.println("安倍:在特朗普老兄的电话还未接通之前,小弟我可以干些其他的事情:哎呀,我大日本的网络有点令人担忧啊,半天了," +
"特朗普老兄的电话还是没有接通,这老家伙,不会在做些羞羞的事情吧,不行我还是先看个苍老师或是波多老师的剧情片吧,真有意思,先看再说!");
}
}
//5.提供一个缓冲区并存入数据
String msg = "您好,特朗普兄长,我是安倍,可否赏脸陪小弟喝个便茶,小弟最近有点迷茫啊,突然感觉没有了高潮!";
final ByteBuffer buffer = ByteBuffer.wrap(msg.getBytes());
//6..发送数据
channel.write(buffer);
//7.当程序执行到这里,已经执行完,channel就会关闭,服务器端就会抛出异常,所以让程序阻塞到这里
System.in.read();
}
}
服务端代码:
public class NioServer {
public static void main(String[] args) throws IOException {
//1.得到一个通道
final ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
//2.得到一个Selector对象
final Selector selector = Selector.open();
//3.绑定端口号
serverSocketChannel.bind(new InetSocketAddress(9000));
//4.设置非阻塞方式
serverSocketChannel.configureBlocking(false);
//5.把ServerSocketChannel对象注册给Selector对象
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
//6.实现业务
while (true){
//6,1 一直监控客户端,直到有客户端连接上来
if(selector.select(2000) == 0){
System.out.println("特朗普:安倍这小子好几天就越好今天和我通话,现在也没来,不会干火星上去了吧,不管他了,我还是干别的事情吧:82年拉菲," +
"加拿大总理特鲁多小老弟送我的,我去找我的merry女秘书喝两杯,cheers,大爷我要嫖了!");
continue;
}
//6.2 得到SelectionKey
final Iterator selectionKeyIterator = selector.selectedKeys().iterator();
while (selectionKeyIterator.hasNext()){
final SelectionKey selectionKey = selectionKeyIterator.next();
if(selectionKey.isAcceptable()){ //客户端连接事件
System.out.println("连接...............................................");
final SocketChannel socketChannel = serverSocketChannel.accept();
socketChannel.configureBlocking(false);
socketChannel.register(selector,SelectionKey.OP_READ, ByteBuffer.allocate(1024));
}
if(selectionKey.isReadable()){ //读取客户端数据事件
final SocketChannel channel = (SocketChannel) selectionKey.channel();
final ByteBuffer buffer = (ByteBuffer) selectionKey.attachment();
channel.read(buffer);
System.out.println("客户端发来数据:" +new String(buffer.array()));
}
//6.3 手动从当前集合中移除key,防止重复处理
selectionKeyIterator.remove();
}
}
}
}
运行结果分析:
1.只启动服务端,不启动客户端
2.只启动客户端,不启动服务端
3.启动服务端和客户端
通过以上的代码举例,想必大家对NIO的非阻塞特性应该有所理解。下面聊下NIO的今生,AIO。
AIO: asynchronous IO,即异步IO,并且是非阻塞的,也被称作NIO2,算是对NIO的增强和补充吧,在JDK1.7中新加入的,AIO最大的特性就是异步能力,jdk1.7新增三个异步通道,用于对异步IO的支持:
1.AsynchronousFileChannel: 用于文件异步读写;
2.AsynchronousSocketChannel: 客户端异步socket;
3.AsynchronousServerSocketChannel: 服务器异步socket。
二、为什么要用NIO:
NIO比普通的BIO提供了功能更加强大,处理数据更快的解决方案,大大提升IO的吞吐量,常用在高性能服务器上,在大多数涉及java高性能应用软件中,NIO是必不可少的技术之一,例如Netty就是封装了NIO,而互联网微服务架构中常用的Dubbo,Elasticsearch等中间件底层网络通信都用Netty实现,可见NIO对于高性能网络通信的作用。
三、总结:
这个小节由BIO引出了NIO,通过将NIO与BIO相比较,突出NIO非阻塞IO的优势,简单介绍了什么是NIO,回答了为什么要用NIO的疑问。