Java Nio 二十

2018/01/27 Java

Java NIO 二十

使用选择键

建立监控三个 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 的 API 的细节:

public abstract class Selector
{
           // This is a partial API listing
           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 实例。只需要调用open( )方法来创建新的Selector对象。

不再使用它时,需要调用close( ) 方法来释放它可能占用的资源并将所有相关的选择键设置为无效。一旦一个选择器被关闭,试图调用它的大多数方法都将导致 ClosedSelectorException。注意 ClosedSelectorException 是一个非检查(运行时的)错误。可以通过 isOpen( )方法来测试一个选择器是否处于被打开的状态。

SelectableChannel 的 API的简化版本:


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( );
}

register( )方法位于SelectableChannel类,尽管通道实际上是被注册到选择器上的。 register( )方法接受一个 Selector 对象作为参数,以及一个名为 ops 的整数参数。

JDK1.4中有四种被定义的可选择操作:读(read),写(write),连接(connect)和接受 (accept)。(JDK1.7中还是这四种操作)。并非所有的操作都在所有的可选择通道上被支持。可以通过调用 validOps( )方法来 获取特定的通道所支持的操作集合。

选择器包含了注册到它们之上的通道的集合。在任意给定的时间里,对于一个给定的选择器和 一个给定的通道而言,只有一种注册关系是有效的。但是,将一个通道注册到多于一个的选择器上允许的。这么做的话,在更新 interest 集合为指定的值的同时,将返回与之前相同的选择键。实际 上,后续的注册都只是简单地将与之前的注册关系相关的键进行更新。

一个例外的情形是当试图将一个通道注册到一个相关的键已经被取消的选择器上,而通道仍 然处于被注册的状态的时候。通道不会在键被取消的时候立即注销。直到下一次操作发生为止,它 们仍然会处于被注册的状态。在这种情况下,未检查的 CancelledKeyException 将会被抛出。

register( )的第二个版本,这个版本接受 object 参数。 这是一个方便的方法,可以传递对象引用,在调用新生成的选择键的 attach( )方法时会将这个对象引用返回。

一个单独的通道对象可以被注册到多个选择器上。可以调用 isRegistered( )方法来检查一个通道 是否被注册到任何一个选择器上。这个方法没有提供关于通道被注册到哪个选择器上的信息,而只能知道它至少被注册到了一个选择器上。此外,在一个键被取消之后,直到通道被注销为止,可能有时间上的延迟。

任何一个通道和选择器的注册关系都被封装在一个SelectionKey对象中。keyFor( )方法将 返回与该通道和指定的选择器相关的键。如果通道被注册到指定的选择器上,那么相关的键将被返回。如果它们之间没有注册关系,那么将返回 null。

SelectableChannel类的一个register( )方法的重载版本接受一个Object类型的参数。这是一个方便在注册时附加一个对象到新生成的键上的方法。

下面两段代码是等价的:


SelectionKey key =
          channel.register (selector, SelectionKey.OP_READ, myObject);


SelectionKey key = channel.register (selector, SelectionKey.OP_READ);
key.attach (myObject);


SelectionKey 对象是线程安全的。

如果选择键的存续时间很长,但附加的对象不应该存在那么长时间,完成后需要清理附件。否则附加的对象将不能被垃圾回收,可能会面临内存泄漏问题。

Show Disqus Comments

Search

    Table of Contents