Java NIO: Avoid HashMapNode memory allocations? - java

Classic NIO client:
Create a selector
Register channel to selector for READ WRITE
Loop:
select
iterate selectedKeys
do work for Readable channel and Writeable channel
I wrote a simple UDP NIO client for android like above, but found 30k+ HashMapNode memory allocations every 10 seconds. Since the channel was concerned both READ and WRITE, the select() call returned immediately because of it was writeable, during every select(), at least one SelectionKey was added to HashMap returned by SelectedKeys().
I changed the design to only register for READ at the beginning, and call select() with a small timeout(say 10ms), and if the buffer to write was not empty, register for WRITE, do the writes, and register for READ again, memory allocation problem fixed, but write operation will be delayed because you have to wait for the READ select timeout.
Any better approach?

Ok, I think I got it. Here it goes.
Main loop:
Open channel and configureBlocking(false)
Open selector
Register channel to selector, only concern about OP_READ
LOOP:
selector.select() // No timeout
if write buffer is empty or no channel been selected, continue
LOOP for selectionKey in selectedKeys:
if selectionKey is readable
do read operation
else if selectionKey is writeable
do write operation
register channel to OP_READ
remove selectionKey from selectedKeys
Write:
write data to write buffer
register channel to OP_WRITE
selector.wakeup()

Related

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.

Non-blocking writes to Java SocketChannel

If I obtain a SocketChannel that is set to the non-blocking mode, what happens when I write to the channel and the underlying socket buffer is full because the other side cannot keep up? Would the data be discarded or something to that effect?
The write() method returns zero and the data stays in the ByteBuffer. At that point you should register the channel for OP_WRITE, remember the output buffer, return to the select loop. When the channel becomes writable, retry the write, and this time as long as it completes, i.e. doesn't return zero or less than the remaining bytes in the buffer to be written, deregister OP_WRITE.

The SelectionKey.attach() method

Please give me the concept of the atach() method. Could I work with NIO without atach() method?.
I have read that with this method we can attach any object in a determined SelectionKey, but, I don't find a motive for this, because after registering my SocketChannel within Selector with the READ and WRITE operations, I could read or write toward or from some socket without attaching a buffer, when the Selector tell me that there are read keys, to write I could create a bytebuffer and fill it and write to the socket, and to read I could create a bytebuffer too.
Then, why the programers use atach() method when they register their channels with READ or WRITE operations?
SocketChannel cliente=SocketChannel.open(localhost);
cliente.configureBlocking(false);
Selector selector=Selector.open();
SelectionKey key=cliente.register(selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE);
//the programers ByteBuffer buf=ByteBuffer.allocate(100); key.attach(buf);
...............................
...............................
selector.select();
Set<SelectionKey> readyKeys=selector.selectedKeys();
Iterator<SelectionKey> iterator =readyKeys.iterator();
while(iterator.hasNext()){
SelectionKey key2=iterator.next();
iterator.remove();
if(key2.isReadable()){
SocketChannel cliente2=(SocketChannel)key2.channel();
ByteBuffer buf=ByteBuffer.allocate(100) //The programers: ByteBuffer buf2=(ByteBuffer)
//key2.attachment();
cliente.read(buf);
}
if(key2.isWritable()){
SocketChannel cliente2=(SocketChannel)key2.channel();
ByteBuffer buf=ByteBuffer.allocate(100) //The programers: ByteBuffer buf2=(ByteBuffer)
//key2.attachment();
buf2.put("Hello Server".getBytes("UTF-8"));
cliente2.write(buf2);
}
}
please only give me the concept of the atach() method
There is no atach() method. There is however an attach() method: "Attaches the given object to this key. An attached object may later be retrieved via the attachment method. Only one object may be attached at a time; invoking this method causes any previous attachment to be discarded. The current attachment may be discarded by attaching null."
I want to know only that please. Could I work with NIO without atach() method?
Yes, assuming you mean the attach() method.
Indeed thank you for answer my question. I have read that with this method we can attach any object in a determined SelectionKey, but, I don't find a motive for this, because after registering my SocketChannel within Selector with the READ and WRITE operations, I could read or write toward or from some socket without attaching a buffer, when the Selector tell me that there are read keys, to write I could create a bytebuffer and fill it and write to the socket, and to read I could create a bytebuffer too.
You could indeed, and you would be creating a lot of buffers, one per read, when you should be creating one per channel and associating it via the SelectionKey.attach() method.
Then, why the programers use atach() method when they register their channels with READ or WRITE operations?.
To associate some context with the channel. The attachment can be just a buffer, or it can be some kind of context object that contains a buffer, or two, and maybe a session identifier, user credentials, all sorts of things.

how to register multiple SocketChannel on one thread with a Selector on different thread

I am developing an Android app in which I am using SocketChannel with Selector for sending network requests to my server.
I have a selector on one threads which has registered for OP_CONNECT, OP_WRITE and OP_READ and which calls the select() method to receive these events.
can anybody suggest me correct way of doing the multi-threaded registration of SocketChannel with a single Selector ?
The time-consuming part is the actual read from/write to the channels. So basically you would
Set up a set of socket channels
Register all them to a selector.
In your main selection thread call select(). Once return value > 0, get the set of selection keys and determine if you want to read/write on the associated channels.
Create a thread pool. Do all your actual read/write to the channels by that pool. Or even, cache your Runnable instances if you want zero object loop.

Categories