在高并发(持续大量的连接同时请求)场景中,之前的两种 BIO 优化方案都需要消耗大量的线程来维持连接。并且 CPU 在线程切换上消耗很大。
Java NIO 模型的主要优势:少量的线程就可以处理大量连接的请求。
主要组成:
- Channel 通道:IO 传输发生时数据通过的入口
- Buffer 缓冲区:可以理解为数据在管道传输时的起点和终点
- Selector 选取器(IO监听器):负责监听 IO 事件
所用通道都向 Selector 注册,Selector 负责轮询检测,然后服务端进程会阻塞在 Selector 的 select() 方法,直到注册的通道有事件就绪。
代码示例 : NIOServer.java
package org.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;
public class NIOServer {
// 服务端通道
private ServerSocketChannel serverChannel;
// 选择器
private Selector selector;
// 默认服务绑定端口
private static int DEFAULT_BIND_PORT = 9000;
public NIOServer(int port) {
initServer(port);
}
private void initServer(int port) {
try {
// 开启一个服务通道
this.serverChannel = ServerSocketChannel.open();
// 将通道绑定到指定端口
this.serverChannel.bind( (port < 1 || port > 65535) ?
new InetSocketAddress(DEFAULT_BIND_PORT) :
new InetSocketAddress(port));
// 将通道设置为非阻塞模式
this.serverChannel.configureBlocking(false);
// 打开一个 IO 监视器:Selector
this.selector = Selector.open();
// 将服务通道注册到 Selector 上,并在服务端通道注册 OP_ACCEPT 事件
this.serverChannel.register(this.selector, SelectionKey.OP_ACCEPT);
} catch (IOException ioException) {
ioException.printStackTrace();
System.out.println("init exception: " + ioException);
}
}
public void startServer() throws InterruptedException {
while (true){
System.out.println("Selector 巡查 IO 事件---------------开始");
try {
int ioEventCount = this.selector.select(); // 此处以收集到所有 IO 事件
System.out.println("Selector 检测到:" + ioEventCount);
} catch (IOException ioException) {
ioException.printStackTrace();
break;
}
// 对各个 IO 事件做出对应的响应
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeys.iterator();
while (iterator.hasNext()){
SelectionKey key = iterator.next();
iterator.remove(); //通过调用迭代器的 remove() 方法将这个键 key 从已选择键的集合中删除
try {
// 可接收连接 能注册SelectionKey.OP_ACCEPT事件的只有 ServerSocketChannel通道
if (key.isAcceptable()) {
System.out.println("监控到 OP_ACCEPT 连接事件");
ServerSocketChannel server = (ServerSocketChannel) key.channel();
// 接受客户端连接
SocketChannel client = server.accept();
System.out.println("Accept connection from " + client);
client.configureBlocking(false); // 设置客户端通道非阻塞
// 为客户端通道注册 OP_WRITE 和 OP_READ 事件
SelectionKey clientKey = client.register(selector,
SelectionKey.OP_WRITE |
SelectionKey.OP_READ);
// 为客户端通道添加一个数据缓存区
ByteBuffer buffer = ByteBuffer.allocate(100);
clientKey.attach(buffer);
}
// 可读数据
if (key.isReadable()) {
SocketChannel client = (SocketChannel) key.channel();
ByteBuffer output = (ByteBuffer) key.attachment();
int read = client.read(output);
System.out.println("Read data from client: " + client);
System.out.println("------------MSG : " + output.toString());
}
// 可写数据
if (key.isWritable()) {
SocketChannel client = (SocketChannel) key.channel();
ByteBuffer output = (ByteBuffer) key.attachment();
output.flip();
client.write(output);
output.compact();
System.out.println("Write data to " + client);
}
} catch (IOException ioException) {
ioException.printStackTrace();
}
}
Thread.sleep(2000);// 为了观察控制台打印数据
System.out.println("Selector 巡查 IO 事件---------------完成");
}
}
public static void main(String[] args) throws InterruptedException {
NIOServer nioServer = new NIOServer(DEFAULT_BIND_PORT);
nioServer.startServer();
}
}