http://zachary-guo.iteye.com/blog/1564193
◇ 选择器、可选择通道和选择键类
实际上,与选择器相关的类的 api 有三个,它们用于执行就绪选择:
- 选择器(Selector):选择器类管理着一个被注册的通道集合的信息和它们的就绪状态。通道是和选择器一起被注册的,并且使用选择器来更新通道的就绪状态。
- 可选择通道(SelectableChannel):它是所有支持就绪检查的通道类的父类。FileChannel 对象不是可选择的,因为它没有继承 SelectableChannel。所有 socket 通道都是可选择的,包括从管道(Pipe)对象的中获得的通道。只有 SelectableChannel 可以被注册到 Selector 对象上。一个通道可以被注册到多个选择器上,但对每个选择器而言只能被注册一次。
- 选择键(SelectionKey):选择键封装了特定的通道与特定的选择器的注册关系。选择键对象被 SelectableChannel.register() 返回并提供一个表示这种注册关系的标记。选择键包含了两个比特集(以整数的形式进行编码),指示了该注册关系所关心的通道操作,以及通道已经准备好的操作。
- public abstract class SelectableChannel extends AbstractChannel implements Channel {
- // This is a partial API listing
- public abstract SelectionKey register(Selector sel, int ops)
- throws ClosedChannelException;
- public abstract SelectionKey register(Selector sel, int ops, Object att)
- throws ClosedChannelException;
- public abstract boolean isRegistered();
- public abstract SelectionKey keyFor(Selector sel);
- public abstract int validOps();
- public abstract void configureBlocking(boolean block) throws IOException;
- public abstract boolean isBlocking();
- public abstract Object blockingLock();
- }
调用 SelectableChannel 的 register() 方法会将它注册到一个选择器上。如果你试图注册一个处于阻塞状态的通道,register() 将抛出未检查的 IllegalBlockingModeException 异常。此外,通道一旦被注册,就不能回到阻塞状态。试图这么做的话,将在调用 configureBlocking() 方法时将抛出 IllegalBlockingModeException 异常。并且,理所当然地,试图注册一个已经关闭的 SelectableChannel 实例的话,也将抛出 ClosedChannelException 异常,就像方法原型指示的那样。通道在被注册到一个选择器上之前,必须先设置为非阻塞模式(通过调用configureBlocking(false))。
Selector 的相关 API 方法:
- public abstract class Selector {
- public static Selector open() throws IOException
- public abstract boolean isOpen();
- public abstract void close() throws IOException;
- public abstract SelectionProvider provider();
- public abstract int select() throws IOException;
- public abstract int select(long timeout) throws IOException;
- public abstract int selectNow() throws IOException;
- public abstract void wakeup();
- public abstract Set keys();
- public abstract Set selectedKeys();
- }
选择器维护了一个需要监控的通道的集合。一个给定的通道可以被注册到多于一个的选择器上,而且不需要知道它被注册了哪个 Selector 对象上。SelectableChannel 的 register() 将返回一个封装了两个对象的关系的选择键对象。
SelectionKey 的相关 API 方法:
- public abstract class SelectionKey {
- public static final int OP_READ;
- public static final int OP_WRITE;
- public static final int OP_CONNECT;
- public static final int OP_ACCEPT;
- public abstract SelectableChannel channel();
- public abstract Selector selector();
- public abstract void cancel();
- public abstract boolean isValid();
- public abstract int interestOps();
- public abstract void interestOps(int ops);
- public abstract int readyOps();
- public final boolean isReadable();
- public final boolean isWritable();
- public final boolean isConnectable();
- public final boolean isAcceptable();
- public final Object attach(Object ob);
- public final Object attachment();
- }
选择器才是提供管理功能的对象,而不是可选择通道对象。选择器对象对注册到它之上的通道执行就绪选择,并管理选择键。
◇ 建立选择器
来看一段简短的代码,以便能够帮助我们将所有东西放到一个特定的上下文中去理解。 为了建立监控三个 Socket 通道的选择器,你需要做像这样的事情:
- Selector selector = Selector.open();
- channel1.register(selector, SelectionKey.OP_READ);
- channel2.register(selector, SelectionKey.OP_WRITE);
- channel3.register(selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE);
- // Wait up to 10 seconds for a channel to become ready
- readyCount = selector.select(10000);
上面的代码创建了一个新的选择器,然后将这三个(已经存在的) socket 通道注册到选择器上,而且感兴趣的操作各不相同。 select() 方法在将线程置于睡眠状态,直到这些刚兴趣的事情中的操作中的一个发生或者 10 秒钟的时间过去。
Selector 对象是通过调用静态工厂方法 open() 来实例化的。选择器不是像通道或流(stream)那样的基本 I/O 对象:数据从来没有通过它们进行传递。当你不再使用 Selector 时,需要调用 close() 方法来释放它可能占用的资源并将所有相关的选择键设置为无效。一旦一个选择器被关闭,试图调用它的大多数方法都将导致 ClosedSelectorException。注意 ClosedSelectorException 是一个运行时的)错误。你可以通过 isOpen() 方法来测试一个选择器是否处于被打开的状态。
SelectableChannel 的 register() 方法接受一个 Selector 对象作为参数,以及一个名为 ops 的整数参数。第二个参数表示所关心的通道操作。这是一个表示选择器在检查通道就绪状态时需要关心的操作的比特掩码。JDK 中有四种被定义的可选择操作:读(read),写(write),连接(connect)和接受(accept)。并非所有的操作都在所有的可选择通道上被支持。例如,SocketChannel 不支持 accept。试图注册不支持的操作将导致 IllegalArgumentException。你可以通过调用 SelectableChannel 的 validOps() 方法来获取特定的通道所支持的操作集合。