总体流程图
共分为4个步骤,读图顺序从步骤1读到步骤4
事件触发条件说明
ACCEPT:接收事件,当被轮询到Channel接受到新的连接时触发
READ:读事件,当被轮询到Channel的缓冲区有数据时触发
NIOServer
public class NIOServer {
public static void main(String[] args) throws IOException {
//创建selector
Selector selector = Selector.open();
//创建serverSocketChannel
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
//通道设置为非阻塞
serverSocketChannel.configureBlocking(false);
serverSocketChannel.socket().bind(new InetSocketAddress(8080));
//通道注册到selector并监听accept事件,等待client连接
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
while (true){
//此处会阻塞,直到有就绪事件(accept或read)
selector.select();
//得到所有就绪事件
Set<SelectionKey> selectionKeys = selector.selectedKeys();
for (SelectionKey key : selectionKeys) {
//接收事件(有新的client连接进入)
if (key.isAcceptable()){
ServerSocketChannel channel = (ServerSocketChannel) key.channel();
//得到client的channel
SocketChannel socketChannel = channel.accept();
//通道设置为非阻塞,不设置无法注册到selector,会抛异常
socketChannel.configureBlocking(false);
//注册client的channel并监听读事件
socketChannel.register(selector,SelectionKey.OP_READ);
}
else if (key.isReadable()){
try(SocketChannel socketChannel = (SocketChannel) key.channel();){
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
//读出client传过来的数据
int len = socketChannel.read(byteBuffer);
if (len < 0){
continue;
}
byteBuffer.flip();
String msg = new String(byteBuffer.array(), 0, byteBuffer.remaining());
System.out.println(msg);
//响应client
socketChannel.write(ByteBuffer.wrap(("response:"+msg).getBytes(StandardCharsets.UTF_8)));
byteBuffer.clear();
System.out.println("---------睡眠1秒---------");
Thread.sleep(1000);
}
catch (IOException | InterruptedException e){
e.printStackTrace();
}
}
}
//循环后需要手动清空,不然下次会被重复执行
selectionKeys.clear();
}
}
}
Client
public class NIOClient {
public static void main(String[] args) throws IOException, InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(5);
//启动5个线程
for (int i = 0; i < 5; i++) {
int finalI = i;
executorService.execute(() -> {
try (SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress(8080));)
{
//向server发送消息
socketChannel.write(ByteBuffer.wrap(("hello nio:"+ finalI).getBytes(StandardCharsets.UTF_8)));
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
//读取server的响应
socketChannel.read(byteBuffer);
byteBuffer.flip();
System.out.println(new String(byteBuffer.array(),0,byteBuffer.limit()));
byteBuffer.clear();
} catch (IOException e) {
e.printStackTrace();
}
});
}
executorService.shutdown();
}
}
输出结果
server
hello nio:2
---------睡眠1秒---------
hello nio:3
---------睡眠1秒---------
hello nio:4
---------睡眠1秒---------
hello nio:1
---------睡眠1秒---------
hello nio:0
---------睡眠1秒---------
client
response:hello nio:2
response:hello nio:3
response:hello nio:4
response:hello nio:1
response:hello nio:0
总结
selector里面存放的内容可理解为是channel+事件的一个组合,当selector轮询到某个channel的事件被触发了,系统就会告知用户系统可开始对该channel进行IO操作了