Non-Blocking IO (Java NIO)

Anmol Sehgal
4 min readApr 25, 2020

Let's understand the different methods a Server can handle load/connections:

  1. Simple blocking server (everything handled by Main Thread)
  2. Threaded blocking server (Every req handled by a different Thread)
  3. Threaded pool blocking servers(Using executorService to handle Requests)
  4. Java NIO blocking(Single-threaded model, using NIO API)
  5. Java NIO non-blocking pooling (Polling at every socket so see if anything coming)
  6. Java NIO non-blocking selector

Simple Blocking Server

Let's say we have a simple Server, which inverts the case and returns back the changed output.

Blocking Server

To test this server, telnet into this port and write any input. You will receive the Output with the inverted case:

telnet localhost 8080
> hElLo
HeLlO

Issues with this server:
- accept() is blocking, so it can not do anything until the socket connection is opened.
- Say we have multiple telnet connections opened, but only ONE will be able to communicate and open socket. Others can only do so one the first one is closed.
THIS IS A VERY BAD SERVER.

Threaded blocking server

As you might have guessed, to handle the multiple connections, we can use different threads to do so:

Here the handle method runs on a different Thread.
Now multiple telnet connections can send and receive output.

Issues with this:
- Say if we have many high numbers of connections, the server may crash/go outOfMemory/etc. Also, the number of threads we can create for a process is limited(even if they are idle).
So there is still a limited number of connections we can make.

Example java program to test this by creating multiple connections:

Run this while the ThreadedSingleserver is running, and it may cause the server to crash, with such a high number of simultaneous connections.

Thread Pool Blocking Server

As you might have guessed again: instead of creating the threads ourselves, let's leave it to the Java API which reuses a given number of threads. Any new operations beyond that number will wait in the queue until a thread is active again.
Thread pool executor Service is such an API.

Issues with this:
- Although the server will never crash for OutOfMemory, only a fixed number of connections can sustain at a given time(as we have given a fixed number of threads).
i.e. the new connection will be blocked until the thread frees up.
This blocking issue is solved by java NIO.

NIO(New IO) Blocking Server

This uses different methods to open Socket.
Also, the Input/Output streams are not familiar to this and instead rely on Byte Buffers(a very good thing to learn- would be writing on ButeBuffers very shortly).

Byte Buffer:

It is Bytes with a bunch of other data inside. It knows how much data is inside it.
So we start reading from position=0 and limit=80(as defined while allocating it).

Say it has content = “hello”, now its position is at 5 and limit=80 still.

To read, we set position=0 and limit=5. This is done using flip() method.

After the entire content is read, and written to the socket, we reset the position and limit to 0 and 80 resp. This is done using compact() method.

To perform the operation on the ByteButter, read everybyte from pos->limit and perform the operation on every byte.

This uses NIO API, but it's the same as the first one- Single blocking server. It can only accept a single connection at a time. As everything is happening in Main Thread.

Java NIO non-blocking pooling (BAD Approach)

Here the server polls at all the sockets to listen if they want to say anything. But it's bad as the number of sockets inc, it will screw the performance.

NIO has configureBlocking API and setting it false makes it nonBLocking, but an issue with this is that now accept() may return null, as there may not be anything on the socket always.

This will work for multiple connections but is a really very bad approach.

NIO non-blocking selector

This is the best of all the Approaches we have discussed. It is non-blocking and it only operates once a particular event is received to do on the socket.

This wakes up(the thread handling the request), whenever something comes up on any socket(instead of us polling every socket).
So we register the serverSocketChannel with the selector(which can accept the connection and sends callback).

So we register the events we are interested in.
Say Socket1 is ready for connection, the Accept event(selectionKey==ACCEPT) will be received.
On accept key, we accept the socket and create the connection, then we listen to the READ event for this socket.
Now say Socket1 has to say anything, the Read event(selectionKey==Read) will be received. On Read key, we read the input in that socket, and perform the operation on it. Now to write it to the socket, we listen to write Operation.
Now say Socket1 is ready to take the output, the Write event(selectionKey==Write) will be received. We write back the output which Read prepared before.

Thanks to the ITT for their Video, explaining these things in detail.

--

--