一 简介
Java NIO, 是从Java 1.4版本开始引入的 一个新的IO API,可以替代标准的Java IO API。 NIO与原来的IO有同样的作用和目的,但是使用 的方式完全不同, NIO支持面向缓冲区的、基于 通道的IO操作。 NIO将以更加高效的方式进行文 件的读写操作。
io 与 nio的区别如下
IO | NIO |
面向流(Stream Oriented) | 面向缓冲区(Buffer Oriented) |
阻塞IO(Blocking IO) | 非阻塞IO(Non Blocking IO) |
(无) | 选择器(Selectors) |
了解如下概念:
通道(Channel):负责连接。
缓冲区(Buffer):负责数据的存取
选择器(Selector):是 SelectableChannle 对象的多路复用器, Selector 可 以同时监控多个 SelectableChannel 的 IO 状况,也就是说,利用 Selector 可使一个单独的线程管理多个 Channel。 Selector 是非阻塞 IO 的核心。选择器主要根据 注册的通道的SelectionKey来监听。
读 : SelectionKey.OP_READ (1)
写 : SelectionKey.OP_WRITE (4)
连接 : SelectionKey.OP_CONNECT (8)
接收 : SelectionKey.OP_ACCEPT (16)
传统的阻塞式IO与NIO的区别:
传统的 IO 流都是阻塞式的。也就是说,当一个线程调用 read() 或 write() 时,该线程被阻塞,直到有一些数据被读取或写入,该线程在此期间不 能执行其他任务。因此,在完成网络通信进行 IO 操作时,由于线程会 阻塞,所以服务器端必须为每个客户端都提供一个独立的线程进行处理, 当服务器端需要处理大量客户端时,性能急剧下降。
Java NIO 是非阻塞模式的。当线程从某通道进行读写数据时,若没有数 据可用时,该线程可以进行其他任务。线程通常将非阻塞 IO 的空闲时 间用于在其他通道上执行 IO 操作,所以单独的线程可以管理多个输入 和输出通道。因此, NIO 可以让服务器端使用一个或有限几个线程来同时处理连接到服务器端的所有客户端。
二 示例
Server.java
package com.lxj.nio; import java.io.IOException; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.nio.ByteBuffer; import java.nio.channels.ClosedChannelException; import java.nio.channels.SelectableChannel; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; import java.security.cert.PKIXRevocationChecker.Option; import java.util.Iterator; import java.util.Scanner; public class Server implements Runnable{ private Selector selector; private ByteBuffer byteBuffer = ByteBuffer.allocate(1024); private ByteBuffer writeBuffer = ByteBuffer.allocate(1024); public Server(int port) { try { //1.打开多路复用器 selector = Selector.open(); //2.打开服务器通道 ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); //3.设置为非阻塞模式 serverSocketChannel.configureBlocking(false); //4.绑定端口号 serverSocketChannel.bind( new InetSocketAddress(port)); //5.将服务器通道注册到多路复用器上,并且监听阻塞状态 serverSocketChannel.register(selector,SelectionKey.OP_ACCEPT); } catch (Exception e) { e.printStackTrace(); } } @Override public void run() { while(true) { try { //1.多路复用器开始监听 selector.select(); //2.获取所有的selectedKeys结果集进行遍历 Iterator<SelectionKey> iterator = selector.selectedKeys().iterator(); //3.进行遍历 while(iterator.hasNext()) { //4.获取选择的一个元素 SelectionKey selectionKey = iterator.next(); //5.从集合移除当前选择的selectionKey iterator.remove(); //6.判断当前元素是否是有效的 if(selectionKey.isValid()) { //7.如果是阻塞的话 if(selectionKey.isAcceptable()) { //8.获取服务器的通道 ServerSocketChannel serverSocketChannel = (ServerSocketChannel)selectionKey.channel(); //9.进行等待 SocketChannel socketChannel = serverSocketChannel.accept(); //10.设置为非阻塞的模式 socketChannel.configureBlocking(false); //11.将客户端注册到多路复用器上,监听可读事件 socketChannel.register(selector, SelectionKey.OP_READ); } //12.如果是可读状态 if(selectionKey.isReadable()) { byteBuffer.clear(); //13.如果是可读状态 SocketChannel socketChannel = (SocketChannel)selectionKey.channel(); int readCount = socketChannel.read(byteBuffer); //14.如果没有数据 if(readCount < 0) { selectionKey.channel().close(); selectionKey.cancel(); return; } //15.切换成读模式 byteBuffer.flip(); byte[] dst = new byte[byteBuffer.limit()]; byteBuffer.get(dst); String result = new String(dst).trim(); //16.打印 System.out.println("Server : "+result); //17.回应Client byte[] b = new byte[writeBuffer.capacity()]; System.in.read(b, 0, b.length); writeBuffer.put(b); writeBuffer.flip(); socketChannel.write(writeBuffer); writeBuffer.clear(); } } } } catch (IOException e) { e.printStackTrace(); } } } public static void main(String[] args) { new Thread(new Server(9999)).start(); } }
Client.java
package com.lxj.nio; import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.SocketChannel; import java.util.Scanner; public class Client implements Runnable{ private SocketChannel socketChannel; private ByteBuffer writeBuffer = ByteBuffer.allocate(1024); private ByteBuffer readBuffer = ByteBuffer.allocate(1024); public Client(String hostname, int port) { try { socketChannel = SocketChannel.open(); socketChannel.connect(new InetSocketAddress(hostname, port)); Scanner scanner = new Scanner(System.in); while(true) { String string = scanner.next(); writeBuffer.put(string.getBytes()); writeBuffer.flip(); socketChannel.write(writeBuffer); writeBuffer.clear(); while(socketChannel.read(readBuffer)!=-1) { readBuffer.flip(); byte [] dst = new byte[1024]; readBuffer.get(dst, 0, readBuffer.limit()); System.out.println(new String(dst)); readBuffer.clear(); break; } } } catch (IOException e) { e.printStackTrace(); }finally { if(socketChannel != null) { try { socketChannel.close(); } catch (IOException e) { e.printStackTrace(); } } } } public static void main(String[] args) throws Exception { Client client = new Client("127.0.0.1",9999); new Thread(client).start(); } @Override public void run() { } }
通过上面的代码,可以看到NIO在服务器端开启了一个线程用于轮询操作,如果客户端有连接过来,就会注册到多路复用器,便可以监听不同的通道了。