Can't set SelectionKey 's interestOps in separate thread - java

I'm trying to make a simple server using NIO with selectable channels and to move all "heavy" logic outside the main NIO loop into a separate thread. But I can't register SelectionKey from other thread. Sorry for the long read.
Server in started as usual:
ServerSocketChannel serverChannel;
Selector selector;
try {
serverChannel = ServerSocketChannel.open();
ServerSocket ss = serverChannel.socket();
InetSocketAddress address = new InetSocketAddress(port);
ss.bind(address);
serverChannel.configureBlocking(false);
selector = Selector.open();
serverChannel.register(selector, SelectionKey.OP_ACCEPT);
} catch (IOException ex) {
ex.printStackTrace();
return;
}
Then goes the main loop where on accept stage (key.isAcceptable()) I perform accept (I'd prefer to accept a connection in a separate thread, but it seems that without accept in main NIO loop I will not get SocketChannel object):
ServerSocketChannel server = (ServerSocketChannel) key.channel();
SocketChannel sChann = server.accept();
and then I pass current SocketChannel and SelectionKey to the 2nd thread in order to make some checks and decide if channel should be closed or I can read the data from the channel. If all checks are successfully passed I'm trying to register OP_READ flag for this key and get the following problems:
It's written in Java manual, that SelectionKey is constant for the channel. However, when in 2nd thread I'm trying to perform
key.interestOps(SelectionKey.OP_READ);
I got the following exception:
Exception in thread "Thread-0" java.lang.IllegalArgumentException
at java.base/sun.nio.ch.SelectionKeyImpl.interestOps(SelectionKeyImpl.java:98)
at ConnectionAcceptor.run(ConnectionAcceptor.java:55)
at java.base/java.lang.Thread.run(Thread.java:834)
I've read about this exception in manual
IllegalArgumentException - If a bit in the set does not correspond to
an operation that is supported by this key's channel, that is, if (ops
& ~channel().validOps()) != 0
and added some ckecks to see if it's my case. The checks in 2nd thread are:
System.out.println("ConnectionAcceptor: valid options " + ci.sockChan.validOps());
System.out.println("ConnectionAcceptor: OP_ACCEPT " + SelectionKey.OP_ACCEPT);
System.out.println("ConnectionAcceptor: OP_READ " + SelectionKey.OP_READ);
System.out.println("ConnectionAcceptor: OP_WRITE " + SelectionKey.OP_WRITE);
result is:
ConnectionAcceptor: valid options 13
ConnectionAcceptor: OP_ACCEPT 16
ConnectionAcceptor: OP_READ 1
ConnectionAcceptor: OP_WRITE 4
so, the rule from manual is not violated and IllegalArgumentException should not be raised.
Here I've found another method to set required flag:
sockChan.keyFor(selector).interestOps(SelectionKey.OP_READ);
but using it in my 2nd thread I get
Exception in thread "Thread-0" java.lang.NullPointerException
at ConnectionAcceptor.run(ConnectionAcceptor.java:59)
at java.base/java.lang.Thread.run(Thread.java:834)
As a result, it looks like while channel and key objects were transferred to 2nd thread, the main NIO loop maid some iterations and channel's SelectionKey became not valid.
Please, help me to find the way of registering channel's selector flag from 2nd thread.

With
key.interestOps(SelectionKey.OP_READ);
you're trying to change the interest set of the registration from
/* SelectionKey key = */ serverChannel.register(selector, SelectionKey.OP_ACCEPT);
That ServerSocketChannel only supports OP_ACCEPT.
What you want to do is register a new OP_READ on the newly accepted socket channel
SelectionKey aNewKey = sChann.register(selector, SelectionKey.OP_READ);
That new key will drive the operations on that socket.
Your second attempt failed because your SocketChannel wasn't registered for any Selector and therefore keyFor returned null.

Related

When socket select blocks is it waiting on the server?

When a select call on a write channel blocks, is it waiting on the server to respond? Or is it just waiting for the network to respond.
SocketChannel sChannel;
Selector selector;
SelectionKey selectionKey;
...
selector = Selector.open();
selectionKey = sChannel.register(selector, 0);
selectionKey.interestOps(SelectionKey.OP_WRITE);
selector.select(timeout*1000);
When a select call on a write channel blocks, is it waiting on the server to respond?
When a select() call on a channel registered only for OP_WRITE, like your example, blocks, it is waiting for space in the socket send buffer, which in turn is waiting for space in the receiver's socket receive buffer, which in turn is waiting for the peer to read.

why selector.select() is always return 0

I want to determine port whether open.
InetSocketAddress address = new InetSocketAddress("www.google.com", 80);
Selector selector = Selector.open();
SocketChannel socketChannel = SocketChannel.open();
socketChannel.configureBlocking(false);
socketChannel.connect(address);
socketChannel.register(selector, SelectionKey.OP_WRITE);
int result = selector.select();
System.out.println(result);
If the port is not open, think I look the same it return 0,
but when the port is open,it also return 0,I expect it can return 1.
It's because you're selecting for the wrong event. You should have registered the channel for OP_CONNECT. Then, when you get it, call finishConnect(), and if it returns true deregister OP_CONNECT and register whatever event you're interested in next, i.e. OP_READ or OP_WRITE.
Note that if finishConnect() returns false you should just keep selecting, and if it throws an exception the connection has failed and you should close the channel.
If you want to avoid all this complication it is usually simpler to do the connect while still in blocking mode, and then put the channel into non-blocking mode and select.
Although there is really very little point in using NIO in a client at all.
See here for a fuller version of this answer.

Is there a way to de-register a selector on a socket channel

This is a pretty straight forward question, but I have found a need to unregister a selector overlooking my socket channel for java.
SocketChannel client = myServer.accept(); //forks off another client socket
client.configureBlocking(false);//this channel takes in multiple request
client.register(mySelector, SelectionKey.OP_READ | SelectionKey.OP_WRITE);//changed from r to rw
Where I can later in the program call something like
client.deregister(mySelector);
And that the selector will no longer catch data keys for that socket channel. This would make life much easier for me given my server/client design.
Call cancel() on the selection key:
SelectionKey key = client.register(mySelector,
SelectionKey.OP_READ | SelectionKey.OP_WRITE);
...
key.cancel();
or
...
SelectionKey key = client.keyFor(mySelector);
key.cancel();
In addition to #Nikolai 's answer. Doing client.close() will also deregister the channel.
A key is added to its selector's cancelled-key set when it is cancelled, whether by closing its channel or by invoking its cancel method.
From https://docs.oracle.com/javase/7/docs/api/java/nio/channels/Selector.html

when does java nio selector unblock on select() call

I am learning the NIO package. I refer the NioServer example from here. The selector thread in NioServer.java blocks on
this.selector.select();
Iterator<SelectionKey> selectedKeys = this.selector.selectedKeys().iterator();
while (selectedKeys.hasNext()) {
SelectionKey key = selectedKeys.next();
selectedKeys.remove();
if (!key.isValid()) {
continue;
}
if (key.isAcceptable()) {
this.accept(key);
} else if (key.isReadable()) {
this.read(key);
} else if (key.isWritable()) {
this.write(key);
}
When a remote client connects, this.accept(key) is called and in this method the interest
interestOps is changed to Read and awakes the selector.
is this what causes the selector to select this channel? So we signal in this way for the channel to be picked?
Now suppose in write to a socket channel selector is signalled by changing the
interest that the channel is ready for write.
But suppose in write did not complete due to socket buffer full as shown in code, then we don't change the interest and keep it as is as write only.
then when will the selector select this channel?
this.accept(key) calls serverSocketChannel.accept() which returns a new socket channel for communication with the client. It's this channel that is registered with the selector for "read" operations, i.e. the selector now has two registrations:
the original ServerSocketChannel, with OP_ACCEPT
the SocketChannel for the new client, with OP_READ
If a write cannot complete due to the buffer filling up, the corresponding SocketChannel remains registered with OP_WRITE. Once the client reads some data off the other end, the channel will be selected again, allowing us to write the remaining data before flipping the interest set back to OP_READ.
OP_WRITE triggers when then there is room in the socket send buffer.
NB getting a zero length write() result is the only occasion for using OP_WRITE. Most of the time there is room, so OP_WRITE will keep triggering. You don't want this, so you normally don't have OP_WRITE registered for a channel: only when it has just returned zero from write; and you deregister it when that write eventually completes via being re-triggered after an OP_WRITE.

Java NIO select() returns without selected keys - why?

In writing some test code I have found that Selector.select() can return without Selector.selectedKeys() containing any keys to process. This is happening in a tight loop when I register an accept()ed channel with
SelectionKey.OP_READ | SelectionKey.OP_CONNECT
as the operations of interest.
According to the docs, select() should return when:
1) There are channels that can be acted upon.
2) You explicitly call Selector.wakeup() - no keys are selected.
3) You explicitly Thread.interrupt() the thread doing the select() - no keys are selected.
If I get no keys after the select() I must be in cases (2) and (3). However, my code is not calling wakeup() or interrupt() to initiate these returns.
Any ideas as to what is causing this behaviour?
Short answer: remove OP_CONNECT from the list of operations you are interested in for the accepted connection -- an accepted connection is already connected.
I managed to reproduce the issue, which might be exactly what's happening to you:
import java.net.*;
import java.nio.channels.*;
public class MyNioServer {
public static void main(String[] params) throws Exception {
final ServerSocketChannel serverChannel = ServerSocketChannel.open();
serverChannel.configureBlocking(true);
serverChannel.socket().bind(new InetSocketAddress("localhost", 12345));
System.out.println("Listening for incoming connections");
final SocketChannel clientChannel = serverChannel.accept();
System.out.println("Accepted connection: " + clientChannel);
final Selector selector = Selector.open();
clientChannel.configureBlocking(false);
final SelectionKey clientKey = clientChannel.register(selector, SelectionKey.OP_READ | SelectionKey.OP_CONNECT);
System.out.println("Selecting...");
System.out.println(selector.select());
System.out.println(selector.selectedKeys().size());
System.out.println(clientKey.readyOps());
}
}
After the above server receives a connection, the very first select() on the connection exits without blocking and there are no keys with ready operations. I don't know why Java behaves in this way, but it appears many people get bitten by this behavior.
The outcome is the same on Sun's JVM 1.5.0_06 on Windows XP as well as Sun's JVM 1.5.0_05 and 1.4.2_04 on Linux 2.6.
The reason is that OP_CONNECT and OP_WRITE are the same thing under the hood, so you should never be registered for both simultaneously (ditto OP_ACCEPT and OP_READ), and you should never be registered for OP_CONNECT at all when the channel is already connected, as it is in this case, having been accepted.
And OP_WRITE is almost always ready, except when the socket send buffer in e kernel is full, so you should only register for that after you get a zero length write. So by registering the already connected channel for OP_CONNECT, you were really registering for OP_WRITE, which was ready, so select() got triggered.
You should use OP_CONNECT when you connect to server, not when you are listening for incoming connections. Also make sure to configureBlocking before connect:
Selector selector = Selector.open();
SocketChannel serverChannel = SocketChannel.open(StandardProtocolFamily.INET);
serverChannel.configureBlocking(false);
serverChannel.connect(new InetSocketAddress("localhost", 5454));
serverChannel.register(selector, SelectionKey.OP_CONNECT);
// event process cycle
{
int count = selector.select();
for (SelectionKey key : selector.selectedKeys()) {
log.info(" {}", key.readyOps());
if (key.isConnectable()) {
log.info("Connection is ready");
key.interestOps(SelectionKey.OP_READ);
}
if (key.isReadable()) {
// read data here
}
}

Categories