A Selector is a Java NIO component which can examine one or more NIO Channel's, and determine which channels are ready for e.g. reading or writing. This way a single thread can manage multiple channels, and thus multiple network connections.
Here is a list of the topics covered in this text:
Keep in mind though, that modern operating systems and CPU's become better and better at multitasking, so the overheads of multithreading becomes smaller over time. In fact, if a CPU has multiple cores, you might be wasting CPU power by not multitasking. Anyways, that design discussion belongs in a different text. It suffices to say here, that you can handle multiple channels with a single thread, using a Selector.
Here is an illustration of a thread using a Selector to handle 3 Channel's:
Java NIO: A Thread uses a Selector to handle 3 Channel's
Selector selector = Selector.open();
channel.configureBlocking(false); SelectionKey key = channel.register(selector, SelectionKey.OP_READ);
Notice the second parameter of theregister() method. This is an "interest set", meaning what events you are interested in listening for in theChannel, via the Selector. There are four different events you can listen for:
These four events are represented by the four SelectionKey constants:
int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE;I'll return to the interest set a bit further down in this text.
int interestSet = selectionKey.interestOps(); boolean isInterestedInAccept = interestSet & SelectionKey.OP_ACCEPT; boolean isInterestedInConnect = interestSet & SelectionKey.OP_CONNECT; boolean isInterestedInRead = interestSet & SelectionKey.OP_READ; boolean isInterestedInWrite = interestSet & SelectionKey.OP_WRITE;As you can see, you can AND the interest set with the given SelectionKey constant to find out if a certain event is in the interest set.
int readySet = selectionKey.readyOps();You can test in the same way as with the interest set, what events / operations the channel is ready for. But, you can also use these four methods instead, which all reaturn a boolean:
selectionKey.isAcceptable(); selectionKey.isConnectable(); selectionKey.isReadable(); selectionKey.isWritable();
Channel channel = selectionKey.channel(); Selector selector = selectionKey.selector();
selectionKey.attach(theObject); Object attachedObj = selectionKey.attachment();You can also attach an object already while registering the Channel with the Selector, in the register() method. Here is how that looks:
SelectionKey key = channel.register(selector, SelectionKey.OP_READ, theObject);
Here are the select() methods:
select()blocks until at least one channel is ready for the events you registered for.
select(long timeout) does the same as select() except it blocks for a maximum of timeout milliseconds (the parameter).
selectNow() doesn't block at all. It returns immediately with whatever channels are ready.
The int returned by theselect() methods tells how many channels are ready. That is, how many channels that became ready since last time you calledselect(). If you call select() and it returns 1 because one channel has become ready, and you callselect() one more time, and one more channel has become ready, it will return 1 again. If you have done nothing with the first channel that was ready, you now have 2 ready channels, but only one channel had become ready between eachselect() call.
select()
methods and its return value has indicated that one or more channels are ready, you can access the ready channels via the "selected key set", by calling the selectors
selectedKeys()
method. Here is how that looks:
Set<SelectionKey> selectedKeys = selector.selectedKeys();When you register a channel with a
Selector
the
Channel.register()
method returns a
SelectionKey
object. This key represents that channels registration with that selector. It is these keys you can access via the
selectedKeySet()
method. From the
SelectionKey
.
You can iterate this selected key set to access the ready channels. Here is how that looks:
Set<SelectionKey> selectedKeys = selector.selectedKeys(); Iterator<SelectionKey> keyIterator = selectedKeys.iterator(); while(keyIterator.hasNext()) { SelectionKey key = keyIterator.next(); if(key.isAcceptable()) { // a connection was accepted by a ServerSocketChannel. } else if (key.isConnectable()) { // a connection was established with a remote server. } else if (key.isReadable()) { // a channel is ready for reading } else if (key.isWritable()) { // a channel is ready for writing } keyIterator.remove(); }
Notice thekeyIterator.remove()
call at the end of each iteration. TheSelector
does not remove the SelectionKey
instances from the selected key set itself. You have to do this, when you are done processing the channel. The next time the channel becomes "ready" theSelector
will add it to the selected key set again.
The channel returned by theSelectionKey.channel()
method should be cast to the channel you need to work with, e.g aServerSocketChannel or SocketChannel etc.
select()
method which is blocked, can be made to leave the
select()
method, even if no channels are yet ready. This is done by having a different thread call the
Selector.wakeup()
method on the
Selector
which the first thread has called
select()
on. The thread waiting inside
select()
will then return immediately.
If a different thread callswakeup()
and no thread is currently blocked inside select()
, the next thread that calls select()
will "wake up" immediately.
Selector
you call its
close()
method. This closes the
Selector
and invalidates all
SelectionKey
instances registered with this
Selector
. The channels themselves are not closed.
Selector
, registers a channel with it (the channel instantiation is left out), and keeps monitoring the
Selector
for "readiness" of the four events (accept, connect, read, write).
Selector selector = Selector.open(); channel.configureBlocking(false); SelectionKey key = channel.register(selector, SelectionKey.OP_READ); while(true) { int readyChannels = selector.select(); if(readyChannels == 0) continue; Set<SelectionKey> selectedKeys = selector.selectedKeys(); Iterator<SelectionKey> keyIterator = selectedKeys.iterator(); while(keyIterator.hasNext()) { SelectionKey key = keyIterator.next(); if(key.isAcceptable()) { // a connection was accepted by a ServerSocketChannel. } else if (key.isConnectable()) { // a connection was established with a remote server. } else if (key.isReadable()) { // a channel is ready for reading } else if (key.isWritable()) { // a channel is ready for writing } keyIterator.remove(); } }