Selection Keys
The java.nio.channels.SelectionKey class encapsulates the information about a channel registered with a Selector. Each SelectionKey object holds the following information:
- The channel
- The Selector
- An arbitrary object attachment, normally used to point to the data the channel is reading or writing
- The operations the channels is interested in performing
- The operations the channel is currently ready to perform without blocking (or, more accurately, was ready to perform when select( ) was last called)
Most of the methods in the SelectionKey class amount to setters and getters for this information. There's also one method that cancels the key.
The only constructor in SelectionKey is protected, but it's rare that you yourself extend this class. Instead, SelectionKey objects tend to be returned by the selectedKeys( ) method of Selector and the register( ) methods of SelectableChannel. The first thing you'll usually want to do with such a key is find out what its channel is ready to do: read, write, connect, or accept. The readyOps( ) method returns a group of bitwise flags inside an int indicating which operations are possible on this key's channel:
public abstract int readyOps( )
The low-order four bits of the return value are 1 or 0, depending on whether the key's channel is ready for reading, writing, connecting, or accepting. The specific masks to use for the flags are stored in named constants:
public static final int OP_READ public static final int OP_WRITE public static final int OP_CONNECT public static final int OP_ACCEPT
For example, this code fragment checks to see if the key is ready for reading:
if (key.readyOps( ) & SelectionKey.READ != 0) { // read from the key's channel... }
However, it's usually more convenient to ask about each of the four operations individually with these four methods:
public final boolean isReadable( ) public final boolean isWritable( ) public final boolean isConnectable( ) public final boolean isAcceptable( )
If the channel is ready for reading, read it; if the channel is ready for writing, write it; and so on. Of course, you can simply ignore operations you aren't interested in performing. Example 16-2 didn't bother to check if the channel was ready for connecting or reading because it was never going to do either of those operations. However, in the most general case, a Selector is registered with multiple channels, some of which are used for reading, some for writing, and some for both. The Selector only tells you which channels are ready. It does not read or write or accept or connect itself.
The interestOps( ) methods set and get the operations the key's Selector is interested in performing:
public abstract int interestOps( ) public abstract SelectionKey interestOps(int ops)
They use the same bitwise constants that readyOps( ) does. The no-args version tells you the operations for which this key will be tested. The one-arg version lets you change those operations, so you can change the Selector from checking for reading to checking for writing or vice versa, for example.
16.4.1. Getters
The Selector returns a set of SelectionKeys to indicate which channels are ready. It does not return the channels themselves. To read or write (or accept or connect), you have to get the channel from its key using the channel( ) method:
public abstract SelectableChannel channel( )
You usually cast the result to a more specific subclass of SelectableChannel. In context, it's normally obvious what kind of channel is returned. For instance, if you've only registered socket channels with a Selector for reading, the channel( ) method of any key that is ready for reading returns a SocketChannel:
SocketChannel client = (SocketChannel) key.channel( );
There's no corresponding setter method. You cannot change the channel with which a key is associated. One channel may have multiple keys, but each key has only a single channel.
Less commonly, you might need to get the Selector given a key. That is what the selector( ) method does:
public abstract Selector selector( )
16.4.2. Attachments
Because this is nonblocking I/O, it may not be possible to write or read as much data as you want with each call. For instance, when reading from a network, a thousand bytes might be waiting for you in the Ethernet card's buffer that can be read immediately. However, there could be megabytes of data yet to come. You need some sort of data structure where you can store the data you've read that keeps track of your place in the stream. Exactly what this data structure is depends on what you're trying to do. For instance, it might be a byte array, or a file, or a string. Often, this data structure is represented as some sort of java.nio.Buffer object. This is exactly what buffers are designed to do.
Whatever this data structure is, it is normally attached to the key using the attach( ) method (or the register( ) method in SelectableChannel) and retrieved from the key using the attachment( ) method:
public final Object attach(Object ob) public final Object attachment( )
|
16.4.3. Canceling
You can deregister a Selector from a channel by canceling its key with the cancel( ) method:
public abstract void cancel( )
This is not required as strongly as closing a stream is required. However, it can be a good idea if you're finished with a channel and do not want the Selector to monitor it any longer.
The isValid( ) method tells you whether or not the key is still meaningful:
public abstract boolean isValid( )
It returns true if the key's channel and Selector are both open and the key has not been cancelled.