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.
Related
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.
I'm trying to connect to a remote server and send a login message in my Thread:
#Override
public void run() {
try {
address = new InetSocketAddress(host, port);
incomingMessageSelector = Selector.open();
socketChannel = SocketChannel.open();
socketChannel.configureBlocking(false);
socketChannel.connect(address);
socketChannel.register(incomingMessageSelector, SelectionKey.OP_READ);
serverManager.loginToServer();
}
}
the loginServer() is a method which send a message to ther server but i keep getting an:
java.nio.channels.NotYetConnectedException
how can i check and wait for connection before sending this loginServer() method?
If you're connecting in non-blocking mode you should:
register the channel for OP_CONNECT
when it fires call finishConnect()
if that returns true, deregister OP_CONNECT and register OP_READ or OP_WRITE depending on what you want to do next
if it returns false, do nothing, keep selecting
if either connect() or finishConnect() throws an exception, close the channel and try again or forget about it or tell the user or whatever is appropriate.
If you don't want to do anything until the channel connects, do the connect in blocking mode and go into non-blocking mode when the connect succeeds.
i've found an answer.. i should use:
socketChannel = SocketChannel.open(address);
socketChannel.configureBlocking(false);
while (!socketChannel.finishConnect());
//my code after connection
because the NIO is in not blocking mode we have to wait until it finish its connection
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.
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
}
}
When I code like this:
ServerSocketChannel ssc = ServerSocketChannel.open();
InetSocketAddress sa = new InetSocketAddress("localhost",8888);
ssc.socket().bind(sa);
ssc.configureBlocking(false);
ssc.socket().accept();
the ServerSocket.accept() method throws java.nio.channels.IllegalBlockingModeException. Why can't I call accept(), even though I set blocking to false?
The problem is that you are calling ssc.socket().accept(), not ssc.accept(). If you change the last line to ssc.accept() then it will work as expected, which is to return a SocketChannel if one is waiting or null if not.
Because that's what javadoc for serversocket.accept() says?
IllegalBlockingModeException - if this socket has an associated channel, and the channel is in non-blocking mode.
The Javadoc specifically states that ServerSocketChannel.accept():
Accepts a connection made to this channel's socket.
If this channel is in non-blocking mode then this method will immediately
return null if there are no pending connections. Otherwise it will block
indefinitely until a new connection is available or an I/O error occurs.
The general idea is:
If you want to block while waiting for incoming connections, you leave the server socket in blocking mode. If you're writing a server that has nothing to do until a connection actually comes in, then blocking mode is what you want.
If you want to do other things, and periodically check to see whether there's a pending connection, you want non-blocking mode.
Blocking mode is the default for a reason: Most servers don't want to poll their accepting socket for incoming connections.