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.
Related
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.
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.
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.
The call on ServerBootstrap.bind() returns a Channel but this is not in a Connected status and thus cannot be used for writing to client.
All the examples in Netty documentation show writing to a Channel from its ChannelHandler's events like channelConnected - I want to be able to get a connected Channel not in the event but as a reference outside the event , lets say some client code using my server component. One way is to manually code for waiting for channelConnected event and then copying the Channel reference.But this may be reinventing the wheel.
So the question is : Is there a blocking call available in Netty that returns a Connected Channel ?
edit : I am using Oio Channels , not Nio.
You could create a blocking call, but I think you maligned the event based approach too quickly. This is a contrived example, just to make sure I understand what you're trying to do:
Netty Server starts
A DataPusher service starts.
When a client connects, the DataPusher grabs a reference to the client channel and writes some data to it.
The client receives the pushed data shortly after connecting.
More or less correct ?
To do this, your DataPusher (or better yet, one of its minions) can be registered as a ChannelHandler in the server pipeline you create. Make it extend org.jboss.netty.channel.SimpleChannelHandler. The handler might look like this:
DataPusher dataPusher = getMyDataPusherReference();
public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e) {
dataPusher.doYourThing(e.getChannel()); // do something in another thread....
}
If you are determined to make it a blocking call from the DataPusher's perspective, just have it wait on a latch and have the minion drop the latch.
Not sure if that's what your'e looking for.....
After all the exchanges above, I still don't see that any of this is necessary.
Surely all you have to do is just accept the connection from the external service; don't register it for any events; then, when the other client connects, register for I/O events on both channels.
The external service doesn't know or care whether you are blocked in a thread waiting for another connection, or just not responding for some other reason.
If he's writing to you, his writes will succeed anyway, up to the size of your socket receive buffer, whether you are blocking or not, as long as you aren't actually reading from him. When that buffer fills up, he will block until you read some of it.
If he is reading from you, he will block until you send something, and again what you are doing in the meantime is invisible to him.
So I think you just need to simplify your thinking, and align it more with the wonderful world of non-blocking I/O.
I am planning to use java NIO for my project, but one of my requirement is to keep the requests configurable, i.e. the client can select the request to be: 1. blocking, 2. non blocking.
So, is it possible to use NIO in a sync. way?
There is an option on the client code when creating the channel:
SocketChannel socketChannel = SocketChannel.open();
socketChannel.configureBlocking(true);
But, I get this error when I make it as true.
This is the client code I am using from this tutorial.
java.nio.channels.IllegalBlockingModeException
at java.nio.channels.spi.AbstractSelectableChannel.register(AbstractSelectableChannel.java:172)
at java.nio.channels.SelectableChannel.register(SelectableChannel.java:254)
at com.dds.client.DDSClient.run(DDSClient.java:77)
at java.lang.Thread.run(Thread.java:680)
The javadocs for register(...) state that if you call the method on a channel that is in blocking mode, that exception will be thrown. A selector can only handle a non-blocking channel.
You need to change your code to use a blocking operations (e.g. read or write) rather than register and select when you want blocking semantics.
You can't use select() on a blocking channel, as the Javadocs say. You more or less have to use the model of a thread per channel.
What is the reason for this strange requirement?