一、selector简介:选择器提供选择执行已经就绪的任务的能力.从底层来看,Selector提供了询问通道是否已经准备好执行每个I/O操作的能力。Selector 允许一个单一的线程来操作多个 Channel。仅用单个线程来处理多个Channels的好处是,只需要更少的线程来处理通道。事实上,可以只用一个线程处理所有的通道,这样会大量的减少线程之间上下文切换的开销。
二、选择器的创建以及使用
1)创建
Selector selector = Selector.open();
2)注册选择器(Channel这里不介绍)
socketChannel.configureBlocking(false);
socketChannel.register(selector, SelectionKey.OP_READ)
注意:一个通道注册到选择器中,必须是非阻塞的。
3)注册模式有4种
SelectionKey.OP_CONNECT
SelectionKey.OP_ACCEPT
SelectionKey.OP_READ
SelectionKey.OP_WRITE
4)SelectionKey的使用
在选择其中会存在多个选择键SelectionKey,每一个选择键的类型可能不一样,所以我们这里需要判定是哪一种类型
selector.selectedKeys() //获取所有选择键 selectionKey.isConnectable() //是否是连接选择键 selectionKey.isReadable() //读取 selectionKey.isWritable() //写入 selectionKey.isAcceptable() //接收
获取对应的选择键过后可以强转成对应的通信管道。(示例)
SocketChannel channel = (SocketChannel) selectionKey.channel();
三、聊天室的基本写法(基本使用都在里面)
1)客户端
package com.troy.nio.application; 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; public class Client { //选择器 private static Selector selector; //通信管道 private static SocketChannel socketChannel; public static void main(String[] args) { try { clientInit(); listen(); //发送数据 while (true) { Thread.sleep(1000); socketChannel.write(ByteBuffer.wrap(("hello server!").getBytes())); } } catch (Exception e) { e.printStackTrace(); } } //初始化选择器和发送数据 private static void clientInit() throws Exception { //打开一个通道管理器 selector = Selector.open(); //获取一个通信管道 socketChannel = SocketChannel.open(); //设置对应的发送地址和端口 socketChannel.connect(new InetSocketAddress("localhost",9000)); //设置非阻塞 socketChannel.configureBlocking(false); //注册一个写入事件 socketChannel.register(selector, SelectionKey.OP_READ); } //监听服务器返回的数据 private static void listen() throws Exception { Runnable runnable = () -> { while (true) { try { //这里会一直阻塞,直到事件过来 selector.select(); //在选择器中获取对应的注册事件 Iterator<SelectionKey> iterator = selector.selectedKeys().iterator(); while (iterator.hasNext()) { //注册事件 SelectionKey selectionKey = iterator.next(); iterator.remove(); //判断是否是读事件 if (selectionKey.isReadable()) { //获取对应通信管道,并处理层数据 SocketChannel channel = (SocketChannel) selectionKey.channel(); //一次性读取数据量,这里应该做循环,我这里方便没有做 ByteBuffer byteBuffer = ByteBuffer.allocate(1024); channel.read(byteBuffer); byteBuffer.flip(); System.out.println(new String(byteBuffer.array()).trim()); } } } catch (Exception e) { throw new RuntimeException(e.getMessage()); } } }; new Thread(runnable).start(); } }
2)服务端
package com.troy.nio.application; 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; public class Server { //选择器 private static Selector selector; //服务端通信管道 private static ServerSocketChannel channel; public static void main(String[] args) { try { serverInit(); listen(); } catch (Exception e) { e.printStackTrace(); } } //初始化 private static void serverInit() throws IOException { //打开一个选择器 selector = Selector.open(); //打开一个服务端通信管道 channel = ServerSocketChannel.open(); //设置接收端口 channel.socket().bind(new InetSocketAddress(9000)); //设置非阻塞 channel.configureBlocking(false); //注册接收事件 channel.register(selector, SelectionKey.OP_ACCEPT); } //监听 private static void listen() throws IOException { while (true) { //形成阻塞事件,接口完成后进行下一步 selector.select(); //获取选择器中的事件 Iterator<SelectionKey> iterator = selector.selectedKeys().iterator(); while (iterator.hasNext()) { SelectionKey selectionKey = iterator.next(); iterator.remove(); //判断是否是接受事件 if (selectionKey.isAcceptable()) { SocketChannel socketChannel = channel.accept(); socketChannel.configureBlocking(false); socketChannel.register(selector,SelectionKey.OP_READ); } //是否是可读事件 if (selectionKey.isReadable()) { SocketChannel socketChannel = (SocketChannel) selectionKey.channel(); ByteBuffer byteBuffer = ByteBuffer.allocate(1024); //这里的目的是当这个服务端一直存在,因为读取数据存在异常,直接处理掉,下一个客户端景来可以继续接受 try { socketChannel.read(byteBuffer); } catch (Exception e) {
selectionKey.cancel(); continue; } byteBuffer.flip(); System.out.println(new String(byteBuffer.array()).trim()); socketChannel.write(ByteBuffer.wrap("hello client!".getBytes())); } } } } }