Blocking I/O and non-blocking I/O

Rukshani Athapathu
Coder's Corner
Published in
8 min readAug 31, 2017
Image Courtesy: Pexels

Complete source code for both IO and NIO scenario can be found here.

In client server applications, when a client makes a request to a server, server processes the request and sends back a response. For this to happen, both the client and the server first needs to establish a connection with one another and that’s where the sockets come into play. Both the client and the server has to bind itself to a socket at the end of it’s connection and the server waits listening to its socket for the client to make a request for a connection.

So when a connection is made between client and the server, they both write and read data from the socket that is bound to that connection. (How the TCP layer identifies which application to send the data is by server’s port number which is bound to the socket.)

Note: I/O refers to Input/Output in computing world to denote how the communication happen between systems.

Blocking I/O

With blocking I/O, when a client makes a request to connect with the server, the thread that handles that connection is blocked until there is some data to read, or the data is fully written. Until the relevant operation is complete that thread can do nothing else but wait. Now to fulfill concurrent requests with this approach we need to have multiple threads, that is we need to allocate a new thread for each client connection. Let’s understand this with a simple code snippet.

  • Here, we have created a new server socket to listen to request connections on a specific port and now the server is bound to this port.
ServerSocket serverSocket = new ServerSocket(portNumber);
Sever Socket listening for client connections
  • Next, when we call the accept() method, server starts to wait for the client to make a connection and when a client makes a request, the server socket accepts the connection from the client and returns a new socket to communicate with the client. Until this new connection is established server socket gets blocked but once the connection is made it goes back to listen to client connections over the original server socket.
Socket clientSocket = serverSocket.accept();
  • Next, we can get the input and output streams from the socket.
BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));

PrintWriter out =
new PrintWriter(clientSocket.getOutputStream(), true);
  • Then we read a line of information from input stream and process what the client has sent and writes the response to the client trough the output stream attached to the socket. This happens until the client sends “Done” or type end-of-input character (by pressing Ctrl-C).
String request, response;
while ((request = in.readLine()) != null) {
response = processRequest(request);
out.println(response);
if ("Done".equals(request)) {
break;
}
}

Now the important thing to remember here that this code works for one connection at a time. To handle multiple concurrent users we need to allocation a new thread for each client socket.

while (listening) {
accept a connection;
create a thread to deal with the client;
}

Full client and the server code for this blocking IO scenario can be found here in github.

One thread per connection

There are few drawbacks to this approach.

  • Each thread requires a stack of memory allocated to it and with the increase number of connections, spawning multiple threads and switching between them will become cumbersome.
  • At any give point in time there can be multiple threads just waiting for the client requests and that is just a waste of resources.

Therefore, this blocking I/O approach is not ideal if you have to cater to a large number of clients, but to a small to a moderate number of clients this would do just fine.

But what are we going to do when we have to support a large number of simultaneous connections? Luckily, there’s an alternative.

Non-blocking I/O

With non-blocking I/O, we can use a single thread to handle multiple concurrent connections. Before we go into details there are few terms that we need to understand first.

  • In NIO based systems, instead of writing data onto output streams
    and reading data from input streams, we read and write data from “buffers”. You can think of the “buffer” as a temporary storage place and there are different types of Java NIO buffer classes (eg:- ByteBuffer , CharBuffer , ShortBuffer etc..) available for us to use, even though most network programs use ByteBuffer exclusively.
  • Channel” is the medium that transports bulk of data into and out of buffers and it can be viewed as an endpoint for communication. (For example if we take “SocketChannel” class, it reads from and writes to TCP sockets. But the data must be encoded in ByteBuffer objects for reading and writing.)
  • Then we need to understand a concept called “Readiness Selection” which basically means the ability to choose a socket that will not block when data is read or written. Let’s explore this a little bit more.

Java NIO has a class called “Selector” that allows a single thread to examine I/O events on multiple channels. That is, this selector can check the readiness of a channel for operations, such as reading and writing. Now remember different channels can be registered with a “Selector” object and you can specify which operations you are interested in observing and a another thing to remember is that each of these channels are assigned a separate “SelectionKey” which serve as a pointer to a channel.

Now it’s time to create a simple NIO based client and a server

First let’s look at the server code.

  • First we need to create a selector to handle multiple channels and more importantly they allow the server to find all the connections that are ready to receive output or send input.
Selector selector = Selector.open();
  • Now let’s create a server socket channel in a non blocking manner and this “ServerSocketChannel” class is wholly responsible for accepting new incoming connections.
ServerSocketChannel serverChannel = ServerSocketChannel.open();
serverChannel.configureBlocking(false);
  • Then we can bind the server socket channel to a particular host and a port.
InetSocketAddress hostAddress = new InetSocketAddress(hostname, portNumber);serverChannel.bind(hostAddress);
  • Now we need to register this server socket channel with the selector and the “SelectionKey.OP_ACCEPT” parameter tells the “selector” to listen to only incoming connections. Basically the second parameter tells what events we are interested in listening for in the monitored channel. In our case, “OP_ACCEPT” tells that the server socket channel is ready to accept a new connection from a client.
serverChannel.register(selector, SelectionKey.OP_ACCEPT);
  • Next we will call selector’s select() method to check whether there’s anything ready to be acted on. Call this inside an infinite loop if you want to wait infinitely for new activity. “readyCount” denotes the number of ready channels. If we don’t have any ready channels we can continue to wait.
while (true) {
int readyCount = selector.select();
if (readyCount == 0) {
continue;
}
// process selected keys...
}
  • Once the selector finds a ready channel, “selectedKeys()” method returns a set of “readyKeys” each representing a ready channel and we can loop through each channel and perform the necessary operations.
// process selected keys...
Set<SelectionKey> readyKeys = selector.selectedKeys();
Iterator iterator = readyKeys.iterator();
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
// Remove key from set so we don't process it twice
iterator.remove();
// operate on the channel...
}
  • Important thing to notice here is, only one thread, the main thread, processes multiple simultaneous connections.
  • Next, let’s see when we get a channel, how we can handle the operational logic. The channel represented by SelectionKey can either be server socket informing that a new connection has been made or a client socket that is ready to read or write data onto the channel.
  • If key is “acceptable” that means client requires a connection.
 // operate on the channel...
// client requires a connection
if (key.isAcceptable()) {
ServerSocketChannel server = (ServerSocketChannel) key.channel();
// get client socket channel
SocketChannel client = server.accept();
// Non Blocking I/O
client.configureBlocking(false);
// record it for read/write operations (Here we have used it for read)
client.register(selector, SelectionKey.OP_READ);
continue;
}
  • If key is “readable” that means server is ready to read data from client.
    // if readable then the server is ready to read 
if (key.isReadable()) {

SocketChannel client = (SocketChannel) key.channel();

// Read byte coming from the client
int BUFFER_SIZE = 1024;
ByteBuffer buffer = ByteBuffer.allocate(BUFFER_SIZE);
try {
client.read(buffer);
}
catch (Exception e) {
// client is no longer active
e.printStackTrace();
continue;
}
  • If key is “writable” that means server is ready to write data to client.
if (key.isWritable()) {
SocketChannel client = (SocketChannel) key.channel();
// write data to client...
}

Now we will write a simple client to connect to the server.

  • First the client has to create a socket channel to connect to the server
SocketAddress address = new InetSocketAddress(hostname, portnumber);
SocketChannel client = SocketChannel.open(address);
  • Now instead of asking for socket’s input and output streams, we are going to write data to the channel itself. But as we know now, we need to encode data in “ByteBuffer” objects to write to the channel. So let’s create a ByteBuffer with a 74-byte capacity.
ByteBuffer buffer = ByteBuffer.allocate(74);
  • Fill the buffer with client message and write to the channel.
buffer.put(msg.getBytes());
buffer.flip();
client.write(buffer);

You can find the complete source code for NIO scenario in github.

So, this is basically how you can create a simple client-server using Java NIO, but instead of directly building your applications using Java NIO, you can use a reliable, high performing networking framework like netty for your application needs. This post is written merely to help you understand basic theories around blocking and non-blocking IO.

Source code can be found in github.

--

--