内容目录
4.1.2 建立选择器
现在您可能仍然感到困惑,您在前面的三个清单中看到了大量的方法,但无法分辨出它们具体做什么,或者它们代表了什么意思。在钻研所有这一切的细节之前,让我们看看一个经典的应用实例。它可以帮助我们将所有东西放到一个特定的上下文中去理解。
为了建立监控三个Socket通道的选择器,您需要做像这样的事情(参见图 4-2):
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
的API的细节:
public abstract class Selector { // 这里仅列出部分API public static Selector open() throws IOException public abstract boolean isOpen(); public abstract void close() throws IOException; public abstract SelectionProvider provider(); }
Selector
对象是通过调用静态工厂方法open()
来实例化的。选择器不是像通道或流(stream)那样的基本 I/O 对象:数据从来没有通过它们进行传递。类方法open()
向SPI发出请求,通过默认的SelectorProvider
对象获取一个新的实例。通过调用一个自定义的SelectorProvider
对象的openSelector()
方法来创建一个Selector
实例也是可行的。您可以通过调用provider()
方法来决定由哪个SelectorProvider
对象来创建给定的Selector
实例。大多数情况下,您不需要关心SPI;只需要调用open()
方法来创建新的Selector
对象。在那些您必须处理它们的罕见的情况下,您可以参考在附录B中总结的通道的SPI包。
继续关于将Select作为I/O对象进行处理的话题的探讨:当您不再使用它时,需要调用close()
方法来释放它可能占用的资源并将所有相关的选择键设置为无效。一旦一个选择器被关闭,试图调用它的大多数方法都将导致ClosedSelectorException
。注意ClosedSelectorException
是一个非检查(运行时的)错误。您可以通过isOpen()
方法来测试一个选择器是否处于被打开的状态。
我们将结束对Selector
的API的探讨,但现在先让我们看看如何将通道注册到选择器上。下面是一个之前章节中出现过的SelectableChannel
的API的简化版本:
public abstract class SelectableChannel extends AbstractChannel implements Channel {
// 这里仅列出部分API
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();
}
就像之前提到的那样,register()
方法位于SelectableChannel
类,尽管通道实际上是被注册到选择器上的。您可以看到register()
方法接受一个Selector
对象作为参数,以及一个名为ops
的整数参数。第二个参数表示所关心的通道操作。这是一个表示选择器在检查通道就绪状态时需要关心的操作的比特掩码。特定的操作比特值在SelectonKey
类中被定义为public static
字 段。
在 JDK 1.4 中,有四种被定义的可选择操作:读(read),写(write),连接(connect)和接受(accept)。
并非所有的操作都在所有的可选择通道上被支持。例如,SocketChannel
不支持accept
。试图注册不支持的操作将导致 IllegalArgumentException
。您可以通过调用validOps()
方法来获取特定的通道所支持的操作集合。我们可以在第三章中探讨的socket通道类中看到这些方法。
选择器包含了注册到它们之上的通道的集合。在任意给定的时间里,对于一个给定的选择器和一个给定的通道而言,只有一种注册关系是有效的。但是,将一个通道注册到多于一个的选择器上允许的。这么做的话,在更新interest集合为指定的值的同时,将返回与之前相同的选择键。实际上,后续的注册都只是简单地将与之前的注册关系相关的键进行更新(见 4.2 小节)。
一个例外的情形是当您试图将一个通道注册到一个相关的键已经被取消的选择器上,而通道仍然处于被注册的状态的时候。通道不会在键被取消的时候立即注销。直到下一次操作发生为止,它们仍然会处于被注册的状态(见 4.3 小节)。在这种情况下,未检查的CancelledKeyException
将会被抛出。请务必在键可能被取消的情况下检查SelectionKey
对象的状态。
在之前的清单中,您可能已经注意到了register()
的第二个版本,这个版本接受object
参数。这是一个方便的方法,可以传递您提供的对象引用,在调用新生成的选择键的attach()
方法时会将这个对象引用返回给您。我们将会在下一节更进一步地了解SelectionKey
的API。
一个单独的通道对象可以被注册到多个选择器上。可以调用isRegistered()
方法来检查一个通道是否被注册到任何一个选择器上。这个方法没有提供关于通道被注册到哪个选择器上的信息,而只能知道它至少被注册到了一个选择器上。此外,在一个键被取消之后,直到通道被注销为止,可能有时间上的延迟。这个方法只是一个提示,而不是确切的答案。
任何一个通道和选择器的注册关系都被封装在一个SelectionKey
对象中。keyFor()
方法将返回与该通道和指定的选择器相关的键。如果通道被注册到指定的选择器上,那么相关的键将被返回。如果它们之间没有注册关系,那么将返回null
。
0 条评论
撰写评论