概述
由于BIO(同步阻塞IO)对系统资源的浪费较大。Java1.4中引⼊了NIO框架,在java.nio包中提供了Channel、Selector、Buffer等抽象类,可以快速构建多路复⽤的IO程序,⽤于提供更接近操作系统底层的⾼性能数据操作⽅式。
NIO(Non Blocking IO)是同步⾮阻塞的IO,服务器可以使⽤⼀个线程来处理多个客户端请求,客户端发送的请求会注册到多路复⽤器Selector上,由多路复⽤器Selector轮询各客户端的请求并进⾏处理。
NIO与BIO的比较
-
BIO以流的方式处理数据,而NIO以块的方式处理数据,块IO的效率比流IO高得多
-
BIO是阻塞的,NIO是非阻塞的
-
BIO基于字节流和字符流进行操作,而NIO基于Channel(通道)和Buffer(缓冲区)进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区中写入到通道中。Selector(选择器)用于监听多个通道的事件(比如:连接请求、读写请求等),因此使用单个线程就可以监听多个客户端的通道
NIO可以先将数据写入到缓冲区,然后再由缓冲区写入通道,因此可以做到同步非阻塞
BIO是面向流的,读写数据都是单向的。因此是同步阻塞。
NIO线程模型
NIO包含三⼤组件:
-
Channel通道:每个通道对应⼀个buffer缓冲区
-
Buffer缓冲区:buffer底层是数组,类似于蓄⽔池,channel就是⽔管
-
Selector选择器:selector对应⼀个或多个线程。channel会注册到selector上,由selector根据channel读写事件的发⽣交给某个空闲线程来执⾏。
-
Buffer和Channel都是既可读也可写。
NIO初体验
-
服务端程序
package com.my.io.nio; import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; import java.util.Iterator; import java.util.Set; /** * @author zhupanlin * @version 1.0 * @description: NIO服务端 * @date 2024/1/24 17:23 */ public class NIOServer { public static void main(String[] args) throws IOException { // 创建ServerSocketChannel对象 ServerSocketChannel ssc = ServerSocketChannel.open(); // 设置为非阻塞的方式 ssc.configureBlocking(false); // 设置服务端程序端口 ssc.socket().bind(new InetSocketAddress(9090)); // 创建Selector多路复用器 Selector selector = Selector.open(); // 把ServerSocketChannel对象注册到Selector上,标识该channel只做客户端连接服务端的事情 // selectionKey标识唯一的channel SelectionKey selectionKey = ssc.register(selector, SelectionKey.OP_ACCEPT); while (true){ System.out.println("等待事件发生"); // 在事件发生之前一直阻塞,轮询监听所有注册到selector上的channel int select = selector.select(); System.out.println("某个事件发生了"); // 获得所有发生事件的channel Set<SelectionKey> selectionKeys = selector.selectedKeys(); // 遍历所有channel Iterator<SelectionKey> iterator = selectionKeys.iterator(); while (iterator.hasNext()){ // selectionKey==>对应着一个channel SelectionKey key = iterator.next(); handle(key); // 删除本次处理的key,防止重复处理 iterator.remove(); } } } private static void handle(SelectionKey selectionKey) throws IOException { // 判断channel上发生了什么事件 if (selectionKey.isAcceptable()){ System.out.println("有客户端连接了"); // 服务端处理客户端的连接,得到ServerSocketChannel,代表着服务端 ServerSocketChannel channel = (ServerSocketChannel) selectionKey.channel(); // 服务端接收客户端的连接,得到socketChannel,建立起了服务端和客户端具体的连接通道,(阻塞的) SocketChannel socketChannel = channel.accept(); // 把socketChannel设置成非阻塞 socketChannel.configureBlocking(false); // 把socketChannel注册到selector,只处理读事件的发生 socketChannel.register(selectionKey.selector(), SelectionKey.OP_READ); }else if (selectionKey.isReadable()){ System.out.println("有客户端向服务端写数据"); // 获得服务端和客户端之间的通道SocketChannel SocketChannel socketChannel = (SocketChannel) selectionKey.channel(); // 创建Buffer,可用空间为1024B ByteBuffer buffer = ByteBuffer.allocate(1024); // 通过socketChannel把数据读到buffer中,返回的是这一次读的字节个数 // NIO非阻塞的体现,read本身就是非阻塞的,read方法在执行的时刻一定客户端写的操作发生了。 int len = socketChannel.read(buffer); if (len != -1){ System.out.println("读到客户端的数据:" + new String(buffer.array(), 0, len)); } // 服务端返回数据给客户端 ByteBuffer byteBuffer = ByteBuffer.wrap("hello nio".getBytes()); // 由通道channel去写数据 socketChannel.write(byteBuffer); // 监听下一次事件,读或者写 selectionKey.interestOps(SelectionKey.OP_READ | SelectionKey.OP_WRITE); } } }
-
客户端程序
package com.my.io.nio; import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.SocketChannel; import java.util.Iterator; import java.util.Set; /** * @author zhupanlin * @version 1.0 * @description: NIO客户端 * @date 2024/1/24 18:19 */ public class NIOClient { public static void main(String[] args) throws IOException { // 获得channel通道 SocketChannel channel = SocketChannel.open(); // 设置成非阻塞 channel.configureBlocking(false); // 获得Selector Selector selector = Selector.open(); // 客户端连接上服务端 channel.connect(new InetSocketAddress("127.0.0.1", 9090)); // 将channel注册到Selector上,并且监听连接事件 // 由于是客户端主动去连服务端,所以要处理的事件便为连接事件 channel.register(selector, SelectionKey.OP_CONNECT); // 轮询访问selector while (true){ // 阻塞的,在客户端的角度这个方法只面向一个客户端的channel selector.select(); Set<SelectionKey> selectionKeys = selector.selectedKeys(); Iterator<SelectionKey> iterator = selectionKeys.iterator(); while (iterator.hasNext()){ SelectionKey selectionKey = iterator.next(); // 处理任务 handle(selectionKey); // 防止重复处理 iterator.remove(); } } } private static void handle(SelectionKey selectionKey) throws IOException { SocketChannel socketChannel = (SocketChannel) selectionKey.channel(); if (selectionKey.isConnectable()){ // 如果是正在连接,则完成连接 if (socketChannel.isConnectionPending()){ // finishConnect这个方法执行才表示连接完成了 socketChannel.finishConnect(); // 设置成非阻塞的模式 socketChannel.configureBlocking(false); // 给服务端发消息 ByteBuffer byteBuffer = ByteBuffer.wrap("hello server".getBytes()); socketChannel.write(byteBuffer); // 获得服务端返回的数据 socketChannel.register(selectionKey.selector(), SelectionKey.OP_READ); } }else if (selectionKey.isReadable()){ // 读服务端返回的数据 // 创建缓冲区 ByteBuffer buffer = ByteBuffer.allocate(1024); // 读缓冲区中的数据 int len = socketChannel.read(buffer); if (len != -1){ System.out.println("服务端返回的数据:" + new String(buffer.array(), 0, len)); } } } }
-
通信逻辑