Java NIO(New Input/Output)是Java提供的一种新的I/O(输入/输出)模型,引入了基于通道(Channel)和缓冲区(Buffer)的概念,相比传统的流式I/O模型,提供了更高效、更灵活的I/O操作方式。NIO中的 N 可以理解为 Non-blocking,同步非阻塞IO。
NIO 提供了与传统 BIO 模型中的 Socket
和 ServerSocket
相对应的 SocketChannel
和 ServerSocketChannel
两种不同的套接字通道实现。
NIO 这两种通道都支持阻塞和非阻塞两种模式。
阻塞模式使用就像传统中的支持一样,比较简单,但是性能和可靠性都不好;非阻塞模式有较好的性能和可靠性。
Java NIO 提供了一种更高效、更灵活的方式来处理I/O操作
因此特别适用于高并发
、大规模数据处理
的场景。它在网络编程
、文件处理
、多媒体处理
等领域都有广泛的应用。
Java NIO 的核心概念包括以下几个部分:
通道(Channel):
缓冲区(Buffer):
选择器(Selector):
对于NIO来说selector主要用来接受客户端的连接,所以一般用在server端。我们以一个NIO的服务器端和客户端聊天室为例来讲解NIO在JDK中是怎么使用的。
我们选择Socket协议为基础的ServerSocketChannel
首先就是open这个Server channel:
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.bind(new InetSocketAddress("localhost", 9090));
serverSocketChannel.configureBlocking(false);
然后向server channel中注册selector:
Selector selector = Selector.open();
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
虽然是NIO,但是对于Selector来说,它的select方法是阻塞方法,
只有找到匹配的channel之后才会返回,为了多次进行select操作,我们需要在一个while循环里面进行selector的select操作:
while (true) {
selector.select();
Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> iter = selectedKeys.iterator();
while (iter.hasNext()) {
SelectionKey selectionKey = iter.next();
if (selectionKey.isAcceptable()) {
register(selector, serverSocketChannel);
}
if (selectionKey.isReadable()) {
serverResponse(byteBuffer, selectionKey);
}
iter.remove();
}
Thread.sleep(1000);
}
selector中会有一些SelectionKey,SelectionKey中有一些表示操作状态的OP Status,根据这个OP Status的不同,selectionKey可以有四种状态,分别是isReadable,isWritable,isConnectable和isAcceptable。
当SelectionKey处于isAcceptable状态的时候,表示ServerSocketChannel可以接受连接了,我们需要调用register方法将serverSocketChannel accept生成的socketChannel注册到selector中,以监听它的OP READ状态,后续可以从中读取数据:
private static void register(Selector selector, ServerSocketChannel serverSocketChannel)
throws IOException {
SocketChannel socketChannel = serverSocketChannel.accept();
socketChannel.configureBlocking(false);
socketChannel.register(selector, SelectionKey.OP_READ);
}
当selectionKey处于isReadable状态的时候,表示可以从socketChannel中读取数据然后进行处理:
private static void serverResponse(ByteBuffer byteBuffer, SelectionKey selectionKey)
throws IOException {
SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
socketChannel.read(byteBuffer);
byteBuffer.flip();
byte[] bytes= new byte[byteBuffer.limit()];
byteBuffer.get(bytes);
log.info(new String(bytes).trim());
if(new String(bytes).trim().equals(BYE_BYE)){
log.info("BYE");
socketChannel.write(ByteBuffer.wrap("BYE".getBytes()));
socketChannel.close();
}else {
socketChannel.write(ByteBuffer.wrap("BYE—BYE".getBytes()));
}
byteBuffer.clear();
}
上面的serverResponse方法中,从selectionKey中拿到对应的SocketChannel,然后调用SocketChannel的read方法,将channel中的数据读取到byteBuffer中,要想回复消息到channel中,还是使用同一个socketChannel,然后调用write方法回写消息给client端,到这里一个简单的回写客户端消息的server端就完成了。
接下来就是对应的NIO客户端,在NIO客户端需要使用SocketChannel,首先建立和服务器的连接:
socketChannel = SocketChannel.open(new InetSocketAddress("localhost", 9090));
然后就可以使用这个channel来发送和接受消息了:
public String sendMessage(String msg) throws IOException {
byteBuffer = ByteBuffer.wrap(msg.getBytes());
String response = null;
socketChannel.write(byteBuffer);
byteBuffer.clear();
socketChannel.read(byteBuffer);
byteBuffer.flip();
byte[] bytes= new byte[byteBuffer.limit()];
byteBuffer.get(bytes);
response =new String(bytes).trim();
byteBuffer.clear();
return response;
}
向channel中写入消息可以使用write方法,从channel中读取消息可以使用read方法。
这样一个NIO的客户端就完成了。
虽然以上是NIO的server和client的基本使用,但是基本上涵盖了NIO的所有要点
优点
缺点
NIO 可能的瓶颈