NIO(1、概述)
NIO(2、Buffer)
NIO(3、Channel)
NIO(4、Selector)html
前面两个章节都描述了Buffer和Channel,那这个章节就描述NIO三个最核心部分的最后一块内容 - 选择器(Selector)
java
在前面的章节中描述过多路复用,一个线程经过选择器处理和管理多个通道。因而可知,选择器是用来处理多个通道并监听其通道事件的组件。socket
Selector selector = Selector.open();
ServerSocketChannel channel = ServerSocketChannel.open(); channel.configureBlocking(false); SelectionKey selectionKey = channel.register(selector, SelectionKey.OP_ACCEPT);
在注册通道以前,把通道设置成非阻塞模式,观察源码会发现 register() 会校验当前通道是否为非阻塞模式,当是阻塞模式时,会抛出IllegalBlockingModeException 异常。在前面一个章节也提过,为何FileChannel没有继承SelectableChannel,由于它不须要多路复用,因此在使用通道的时候,只有FileChannel不能向选择器注册通道,凡是继承SelectableChannel都可以向选择器注册通道。
注册通道方法的第二个参数是SelectionKey中定义的操做类型,你能够填入任何你感兴趣的操做类型,只要这个通道支持,一样,在执行 register() 方法时也会校验该通道是否可以支持该操做。
注册方法一样也会返回一个SelectionKey对象。线程
public abstract SelectionKey register(Selector sel, int ops, Object att) throws ClosedChannelException;
例如,使用时附加上一个字符串:rest
String ch_name = "123"; SelectionKey selectionKey = channel.register(selector, SelectionKey.OP_ACCEPT,ch_name);
获取这个字符串可经过 attachment() 来获取:code
// 接收数据 String ch_name_accept = (String) selectionKey.attachment();
固然,注册时返回的SelectionKey对象也能够在使用时候附加你想要的附加对象:htm
selectionKey.attach(ch_name);
selector.select();
参数5000是5秒,参数以毫秒为单位。这个方法会一直阻塞5秒,5秒以内若是没有通道事件就绪的话程序会往下运行:对象
selector.select(5000);
selectNow()其实就是非阻塞,不管有无通道事件就绪,程序都会向下执行:blog
selector.selectNow();
这三个方法的本质区别无非是选择器阻塞或者等待一个或多个通道的事件就绪有多长时间。继承
Set<SelectionKey> selectionKeys = selector.selectedKeys();
通常这个方法是在select() 以后执行,由于到这一步就意味着要经过这个轮询每一个就绪的通道。
Iterator<SelectionKey> iterator = selectionKeys.iterator(); while (iterator.hasNext()) { SelectionKey key = iterator.next(); if (key.isAcceptable()) { // 执行通道的操做 } //执行完成移除 iterator.remove(); }
到这里说的是已就绪的通道,那么全部的 SelectionKey 集能够经过 keys() 方法获取:
Set<SelectionKey> keys = selector.keys();
SelectionKey的功能相似于通道的一个注册令牌。
这个类定义了4个操做类型,每种操做类型都对应了相应的事件,经过监听这几种不一样的事件,在触发该事件时表示所对应的操做已准备就绪:
操做类型 | 值 | 描述 | |
---|---|---|---|
OP_READ | 1 << 0 | 读操做 | |
OP_WRITE | 1 << 2 | 写操做 | |
OP_CONNECT | 1 << 3 | 链接socket操做 | |
OP_ACCEPT | 1 << 4 | 接受socket操做 |
这里得提一句,全部继承SelectableChannel的通道都会定义本身可以支持的操做类型,能够经过具体通道的 validOps() 方法查看,例如SocketChannel支持read、write、connect这几种操做:
// SocketChannel类 public final int validOps() { return (SelectionKey.OP_READ | SelectionKey.OP_WRITE | SelectionKey.OP_CONNECT); }
private volatile int interestOps; private int readyOps;
interestOps用来存储感兴趣的操做集,readyOps用来存储已经就绪的操做集。
其中 interestOps() 方法和 nioInterestOps() 都会返回interestOps,不一样的是interestOps()会校验是否已执行 cancel() ,若是已经取消则会抛出 CancelledKeyException 异常。readyOps一样也有 readyOps() 和 nioReadyOps() 方法,逻辑与interestOps几乎一致。
观察这段SelectionKey抽象类已经实现的代码:
// SelectionKey 类 public final boolean isAcceptable() { return (readyOps() & OP_ACCEPT) != 0; }
当判断是否访问就绪的时候,只要 readyOps() 与相应的操做类型相与,非零就返回true,表明接受请求操做已就绪。这个是SelectionKey已提供的方法,可是SelectionKey并未提供一样返回boolean判断某个操做在interestOps集是否存在,咱们能够本身实现这些方法:
private boolean isInterestRead(SelectionKey selectionKey){ return (selectionKey.interestOps() & SelectionKey.OP_ACCEPT) == SelectionKey.OP_READ; } private boolean isInterestWrite(SelectionKey selectionKey){ return (selectionKey.interestOps() & SelectionKey.OP_WRITE) == SelectionKey.OP_WRITE; } private boolean isInterestConnect(SelectionKey selectionKey){ return (selectionKey.interestOps() & SelectionKey.OP_CONNECT) == SelectionKey.OP_CONNECT; } private boolean isInterestAccept(SelectionKey selectionKey){ return (selectionKey.interestOps() & SelectionKey.OP_ACCEPT) == SelectionKey.OP_ACCEPT; }
SelectableChannel selectableChannel = selectionKey.channel(); Selector sel = selectionKey.selector();