channel的注册中心selector是整个NIO的核心,选择器实现了I/O多路复用。使得单一线程有办法同时对多个socket通道实现监控并及时发现需要处理的IO事件。选择的使用比较简单,主要是三个步骤:
-
1、将通道注册到一个通道上
这里创建里一个Selector,并将
ServerSocketChanne
l注册到selector上//创建一个selector对象 Selector sel = Selector.open(); // 创建severSocketChannel,并设置为非阻塞的模式 ServerSocketChannel server = sel.provider().openServerSocketChannel(); server.configureBlocking(false); //绑定通道到指定的端口 ServerSocket socket = server.socket(); socket.bind(new InetSocketAddress(8080)); //向selector注册感兴趣的事件 server.register(sel, SelectionKey.OP_ACCEPT);
-
2、选择器执行select()方法阻塞获取已经等待就绪的事件
下面是获取key的示例:
ByteBuffer buffer = ByteBuffer.allocate(1024); try { while(true){ //这是一个阻塞的动作 selector.select(); Set<SelectionKey> keys = selector.selectedKeys(); Iterator<SelectionKey> iterator = keys.iterator(); while(iterator.hasNext()){ SelectionKey key = iterator.next(); //将当前的key 删除掉,防止重复处理 iterator.remove(); processKey(key,selector, buffer); } } }catch (Exception e){ e.printStackTrace(); }
-
3、使用获取到的key来反向获取channel,然后进行逻辑处理
下面是处理key的示例:
private void processKey(SelectionKey key, Selector selector, ByteBuffer buffer) throws IOException { if(key.isAcceptable()){ ServerSocketChannel serverSocket = (ServerSocketChannel) key.channel(); SocketChannel accept = serverSocket.accept(); accept.configureBlocking(false); accept.register(selector,SelectionKey.OP_READ,SelectionKey.OP_WRITE); }else if(key.isReadable()){ //读事件 SocketChannel channel = (SocketChannel) key.channel(); int len = channel.read(buffer); if(len > 0 ){ buffer.flip(); String content = new String(buffer.array()); SelectionKey register = channel.register(selector, SelectionKey.OP_WRITE); register.attach(content); }else{ channel.close(); } }else if(key.isWritable()){ //可写事件 SocketChannel channel = (SocketChannel) key.channel(); String attach = (String) key.attachment(); ByteBuffer wrap = ByteBuffer.wrap(("输出内容:" + attach).getBytes()); // channel.write(wrap); } }
这里我们需要简单的介绍下SelectionKey
的四个事件:
-
OP_READ:读事件,当前channel发生读事件,将会获得
-
OP_WRITE:写事件。当前channel写事件就绪的时候触发
-
OP_CONNECT:channel连接成功事件。当前channel连接事件发生的时候触发
-
OP_ACCEPT:出现可接入的客户端事件,主要被服务端使用
下面是这几个事件对应的默认值
public static final int OP_READ = 1 << 0;
public static final int OP_WRITE = 1 << 2;
public static final int OP_CONNECT = 1 << 3;
*/
public static final int OP_ACCEPT = 1 << 4;