Java nio OP_READ not executed when OP_WRITE exists, in Android? - java

I'm trying to write a Server in Android, using NIO.
I've registered both OP_READ, OP_WRITE after my ServerChannel got accepted.
However, the wired thing is... in the while loop, after I do:
SelectionKey key = (SelectionKey) iterator.next();
iterator.remove();
in the:
if(key.isReadable()){
} else if(key.isWritable()){
It seems that the isReadable never returned true.
My client code is two threads, one thread for reading and another thread for writing.
I wonder why is that so..? I think it would be helpful if anyone can give me some reference to the handling of client side in Java NIO.
Thanks a lot,

You shouldn't normally register OP_WRITE. It is almost always ready. You should only use it when you get a short write result, which indicates that the send buffer is full. OP_READ on the other hand is only ready when there is data in the send buffer. There's nothing in your answer to suggest that there was, at the instant in question. I'm wondering whether you don't have these states back to front mentally somehow.

I made a mistake understanding the .register method, the correct way:
client.register(selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE);
while what I did was:
client.register(selector, SelectionKey.OP_READ);
client.register(selector, SelectionKey.OP_WRITE);
Thus the later replaced the former.

Related

Different ways how to switch channel between read and write modes - when to use which?

Reading various manuals on programming Java NIO socket two different was are used to switch channel/selector mode (expected next operation) between SelectionKey.OP_READ and SelectionKey.OP_WRITE, in order to read or write into the channel. One way is using SelectionKey.interestOps() call, the other to use socketChannel.register().
SelectionKey key = (SelectionKey)keyIterator.next();
...
SocketChannel socketChannel = (SocketChannel) key.channel();
...
socketChannel.register(selector, SelectionKey.OP_READ);
// or ?
key.interestOps(SelectionKey.OP_READ);
The SelectionKey and SocketChannel are clearly bound together, at least in one key loop iteration. Selector is linked to both indirectly. Just saying.
There code source for sun.nio.ch.SelectionKeyImpl.interestOps() is NOT provided with JDK. The source code for java.nio.channels.spi.AbstractSelectableChannel.register() is provided, and yes, internally it indeed calls SelectionKey.interestOps() but also SelectionKey.attach(), the key is there is obtained by a lookup which I see unnecessary as we already have it; and couple of checks. So, both calls do same job under the hood, just that register does a bit more.
My question is: which one to use and when?
2019-09-04 update:
According to the The Rox Java NIO Tutorial, frequently cited on SO, thou quite dated, it's from times before Java 5; what they do is what was suggestested in the comments here:
socketChannel.register(this.selector, SelectionKey.OP_READ) is done only once, on initial accept().
subsequently they use only
key.interestOps(SelectionKey.OP_WRITE) and key.interestOps(SelectionKey.OP_READ)
I also posted a related question: Is it necessary to register interest to write to a NIO socket to send data? investigating when, and if, it is actually necessary to register interest for OP_WRITE.

Is it necessary to register interest to write to a NIO socket to send data?

Is it necessary to register interest to write to a NIO client socket channel to send data? Do I have to always call socketChannel.register(selector, SelectionKey.OP_WRITE), or some equivalent, before writing to the client SocketChannel to be able to write there?
Would not be enough simply to write data to client SocketChannel with channel.write(outputBuffer) and awake potentially blocked Selector, all in a client thread? The main selector loop would then look like this:
Selector selector = SelectorProvider.provider().openSelector();
serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.configureBlocking(false);
while (selector.select() > 0) {
Set<SelectionKey> readyKeys = selector.selectedKeys();
Iterator<SelectionKey> keyIterator = readyKeys.iterator();
while (keyIterator.hasNext()) {
SelectionKey key = (SelectionKey)keyIterator.next();
keyIterator.remove();
while (keyIterator.hasNext()) {
...
if (key.isAcceptable()) {
...
SocketChannel socketChannel = serverSocketChannel.accept();
socketChannel.configureBlocking(false);
socketChannel.register(selector, SelectionKey.OP_READ);
// client socket channel would be permanently in the read mode
...
} else if (key.isReadable()) {
...
} else if (key.isWritable()) {
// the key should know here that the underlying channel
// has something to be send to the wire, so it should get
// here if there are still data to be sent
socketChannel.write(outputBuffer)
}
It would only get to the if (key.isWritable()) branch when there is something remaining to be send, the remainder of data from the initial channel.write(outputBuffer) call; like when the message is too long and needs to be send out into chunks and I want no blocking. The loop would spin until outputBuffer.hasRemaining() is finally false.
I am even thinking, does have to be writing to Channel, that is sending data out, done through Selector at all? Leave only incoming traffic to be handled with Selector, as only incoming traffic need wait states?
UPDATE
From further reading of valuable user207421 posts, which I partly inspired me to post this question, NIO Javadoc, in the push to join the dots, I summarized this:
SocketChannel and SelectionKey are thread safe.
Reading and writing from/to channel socket outside the selector loop is indeed possible; it's independent of it. Reading outside does not make much sense, writing to socket definitively does and it's in fact how most of writing is done.
Writing of data is typically initiated outside the selector loop, in a different thread. write(buffer) will not necessary send all data - if the message is too huge for the socket buffer, remote client is very slow or even went unresponsive. In that case write() has to be called repeatedly. Buffer updates the current position with every call, depending how much was sent, which can be anything between 0 and buffer.remaining().
If write() returns 0 then, and user207421 cannot stress this more, it's the case, possibly the only one, when OP_WRITE needs to be registered and writing is then handled within selector loop.
When all data are written the OP_READ must be immediately restored
Having OP_WRITE set without any data to write would cause selector loop to endlessly spin; waste of CPU time.
It is unclear what to do if write() returns >0 but still not all remaining message data were sent, that is buffer.remaining() > 0. Should I also set OP_WRITE and pass it to selector loop, or loop write(buffer) inside writing thread until there is no remainder or 0 is returned?
Can actually such situation happen? Official SocketChannel Javadoc says initially Unless otherwise specified, a write operation will return only after writing all of the r requested bytes ..meaning, no, it cannot happen! But then they add: Some types of channels, depending upon their state, may write only some of the bytes ..so does it mean write() can return before sending all? Or does that mean that returns '0' if it sent, say, just half of the buffer?
In the write returned 0 scenario, when writing is passed to the selector loop, ie different thread; is the writing thread blocked to write a completely new message, ie new buffer, to the same socket channel? Or I have to ensure it does not happen by other means? The Javadoc says: This method may be invoked at any time. If another thread has already initiated a write operation upon this channel, however, then an invocation of this method will block until the first operation is complete. Does that cover my scenario?
Official Javadoc does not mention any special meaning for 0 as a return value for write() method. It just says: The number of bytes written, possibly zero. Hmm.
I'm also not sure how unresponsive or slow client is handled during the write. I'm guessing that returning 0 from write() and passing it to selector loop is exactly the reason it's done like that. Selector then can, and I'm only guessing here, manage writing in an optimal way: speeding it, slowing it, increasing intervals, sending smaller chunks, whatever; whatever the underlying TCP/IP stack reports back.

Does the order of event registration in NIO matter?

The canonical way of using a Selector in java NIO is :
regist the a ServerSocketChannel with the SelectionKey.OP_ACCEPT event type .
invoke select method (which block the thread)in a loop method
then when a OP_ACCEPT event happened , and the relative AcceptEventHandler is invoked to accept a SocketChannel
then regist the accepted SocketChannel with the SelectionKey.OP_READ event type.
when a read event happend , the ReadEventHandler handle the inputs and then regist the SocketChannel with a SelectionKey.OP_WRITE event type.
My question is, why don't register three event type at once at the beginning? Does the sequence make any sense?
The canonical way of using a Selector in java NIO is:
No it isn't. See below.
why don't register three event type at once at the beginning?
Because you can't. You don't have the accepted channel until you call accept(), and you don't do that until you have registered OP_ACCEPT and had it fire, and you can't register the accepted channel for anything until you have it.
does the sequence make any sense?
Nothing else would make sense.
NB you don't register OP_WRITE until you've encountered a short or zero-length write. The reason is that it is almost always ready, so the strategy is simply to write when you have something to write, and only use OP_WRITE to tell you when it becomes possible to write again after a short write (which means the socket send buffer was full).
I'm going to try to add something to the conversation.
A ServerSocketChannel can only accept() new connections. OP_READ / OP_WRITE won't do you anything there. I'm pretty sure you can add them but they will just be ignored because the ServerSocketChannel's only responsibility is to accept() remote SocketChannel.
Once you accept() and get a new SocketChannel; you want to listen to OP_READ first. If you listen to OP_WRITE then most likely you will get the OP_WRITE returned to you every single time you call select() and that will consume lots of resources.
You only want to listen to OP_WRITE when you tried to write some data to the SocketChannel and not all the data was written.

Java nio SelectionKey.register and interestops

I have been working on Java NIO communications and reading various writeups regarding this. The document says that I could "or" ops that I am interested in. However, I haven't seen a single example of
channel.register(selector,SelectionKey.OP_ACCEPT|SelectionKey.OP_READ|Selection.OP_WRITE)
Is this a bad idea?
Yep. It's wrong.
The only thing that can deliver you an OP_ACCEPT is a ServerSocketChannel.
The only thing that can deliver you an OP_READ or OP_WRITE is a SocketChannel or a DatagramSocketChannel.
So there is no way a single channel can deliver you all three of those events. So there is no sense in registering for them all.
OP_WRITE is almost always ready. It rarely if ever makes sense to register for OP_READ and OP_WRITE at the same time.
The validOps() method tells you which operations are valid for a given channel, not that you should need to know at runtime.

Java 8 non-blocking read has race condition?

now this problem is bugging me for a while.
In a working application that i work on, i use SocketChannel in non-blocking mode to communicate with embedded devices.
Now i receive sporadically corrupted data.
On some PCs it does not happen, now it happens on mine.
But when I change too much in the program, the problem disappears.
So much might have effects. The timing, the network interface hardware, win7, the java version, the company firewall, ...
The data reading boils down to this code:
byteBuffer.compact();
socketChannel.read(byteBuffer); // <<< problem here ?
byteBuffer.flip();
if( byteBuffer.hasRemaining() ){
handleData( byteBuffer );
}
This is run in the same thread as the writing, when the selector wakes up and the interest op OP_READ is set.
This code is the only place where byteBuffer is referenced. socketChannel is used only from the same thread when writing.
I instrumented the code, so i can printout the content of the last few read() calls, when the error happens. At the same time I analyze the network traffic on Wireshark. I added lots of asserts to check the bytebuffer integrity.
In Wireshark, the received stream looks good. No DUP-ACK or something else suspicious. The last read() calls match exactly with the data in Wireshark.
In Wireshark, i see many small TCP frames receiving with 90 bytes of payload data in intervals like 10ms arriving. Normally the Java thread reads the data as well all 10ms when it is just arrived.
When it comes to the problem, the Java thread is a bit delay, as the reading happens after 300ms, and the read returns with like ~3000 bytes which is plausible. But the data is corrupted.
The data looks like, if it was copied into the buffer and concurrently received data has overwritten the first data.
Now I don't know how to proceed. I cannot create a small example, as this only rarely happens and I don't know the exact condition which is needed.
Can someone give a hint?
How can I prove, it is the Java lib or not?
What conditions may be important to look at, too?
thanks
Frank
29-June-2015:
Now i was able to build a example for reproduction.
There is one Sender and a Receiver program.
The Sender is using blocking IO, first waiting for a connection, then sending 90 byte blocks every 2ms. The first 4 byte are a running counter, the remaining is not set. The Sender uses setNoTcpDelay(true).
The Receiver is using non-blocking IO. First it connects to the Sender, then it read the channel whenever the selection key is ready for it. Sometime, the read loop does a Thread.sleep(300).
If they run on the same PC over the loopback, this works for me all the time. If I put the Sender onto another PC, directly connected over LAN, it triggers the error. Checking with Wireshark, the traffic and the data sent looks good.
To run, first start the Sender on one PC, then (after editing the hostaddress) start the Receiver.
As long as it works, it prints a line about every 2 seconds. If it fails, it prints information about the last 5 read() calls.
What i found to be the trigger:
The sender has configured setNoTcpDelay(true)
The receiver has sometimes a Thread.sleep(300) before doing the read().
thanks
Frank
buf.order(ByteOrder.BIG_ENDIAN);
This is the default. Remove this.
buf.clear();
The buffer is already empty, because you just allocated it. Remove this.
buf.limit(0);
The limit is already zero after the clear() and also after the initial allocation. Remove this.
while( true ) {
There should be a select() call here.
Iterator<SelectionKey> it = selector.selectedKeys().iterator();
// ...
if( key == keyData && key.isConnectable() ) {
ch.finishConnect();
This method can return false. You're not handling that case.
// ...
if( key == keyData && key.isReadable() ) {
// ...
readPos += ch.read(buf);
Completely incorrect. You are entirely overlooking the case where read() returns -1, meaning the peer has disconnected. In this case you must close the channel.
// without this Thread.sleep, it would not trigger the error
So? Hasn't the penny dropped? Remove the sleep. It is completely and utterly pointless. The select() will block until data arrives. It doesn't need your help. This sleep is literally a waste of time.
if( rnd.nextInt(20) == 0 ) {
Thread.sleep(300);
}
Remove this.
selector.select();
This should be at the top of the loop, not the bottom.
I turned out to be a driver problem, at least it seems so.
I used the USB to Ethernet adapater "D-Link E-DUB100 Rev A".
Because of wireshark showing the correct data, i thought to eliminate the hardware a possible failure cause.
But meanwhile i tried "D-Link E-DUB100 Rev C1" and the problem disappeared.
So I assume it is a problem in the delivered drivers from D-Link for Rev A. And with Rev C1 it might use a system driver that does not have this problem.
thx for all taking the time to read my question.

Categories