JavaData & JavaWhat Is Non-blocking Socket Programming in Java?

What Is Non-blocking Socket Programming in Java?

Developer.com content and product recommendations are editorially independent. We may make money when you click on links to our partners. Learn More.

Socket programs in Java can be made to work in both blocking and non-blocking mode. In blocking socket mode, a system call event halts the execution until an appropriate reply has been received. In non-blocking sockets, it continues to execute even if the system call has been invoked and deals with its reply appropriately later. This article elaborates on the concept of non-blocking socket programming and its allied areas, with appropriate examples.

Overview

There are different types of sockets and their types are classified according to their communication properties. The communication properties are defined by the protocols they support. Usually, it requires a same type of sockets to establish any sort of communication because the standard set of rules for transferring data is defined by the protocols they use. The protocols are TCP or UDP. The application program specifies the protocol it supports. Typically, an application program supports more than one protocol because the programmer who has written it provided the required code to support the particular socket type in this domain. If you have been wondering the type sockets supported by the TCP/IP, they are as follows:

Socket Type Protocol Description
SOCK_STREAM TCP Provides reliable connection-oriented service
SOCK_DGRAM UDP Provides connectionless service for datagrams
SOCK_RAW IP, RAW, ICMP Provides support for lower-layer protocols, such as IP

Both blocking and non-blocking sockets have their uses, but, blocking sockets are more common because they are easy to handle and are the default choice of socket programming in Java. To put it in a simple manner: In a blocking socket program, lines of code execute one after another but they get blocked or halt at the instruction where system calls are made. It waits there until an appropriate reply is received, timeout occurs, or some error is encountered. The non-blocking socket program, on the other hand, does not wait for the reply and continues execution even though the call may not have been completed. Any incomplete reply from invoked system call is dealt with separately.

Use of Blocking vs. Non-blocking Sockets

The blocking sockets are appropriate for a situation where a reply from the system call is crucial. For example, assume a client program wanting to connect to a server. The program must be responsive only when the connection is established or some error has occurred. This means the socket must be blocking user control until an appropriate reply has been received.

On the other hand, consider that a client is trying to establishing multiple connections and each connection can be made exclusive of another. In such a case, the socket must not be rigid to block another system call to establish the connection. This is a scenario for non-blocking sockets.

Non-blocking Sockets in Java

Java has TCP and UDP sockets. The methods such as connect(), accept(), read(), and write() defined in the ServerSocket and Socket class are used for blocking socket programming. For example, when a client invokes the read() method to read data from the server, the thread gets blocked until the data is available. This situation is undesirable under some circumstances. Instead, what we can do is use the waiting period to do some other task. The client socket then can notify when the data is available. Another problem is that, in a multi-socket connection, each client is a separate thread. Therefore, there is an overhead of maintaining a pool of client threads.

Blocking sockets are simple due to their sequential execution. Non-blocking sockets, on the other hand, are non-sequential. They require a different perspective to implement them in programming. In a way, non-blocking socket programs are a little complex and a bit more advanced technique of socket communication.

Non-blocking Socket APIs

The classes that support non-blocking socket communication in Java are as follows.

Non-blocking socket classes Description (*)
ServerSocketChannel A selectable channel for stream-oriented listening sockets. The channel is created by invoking the open method of this class. This class uses the ServerSocket class behind the scenes. An instance of this class is used to accept a new connection request in a server like the ServerSocket class instance.
SocketChannel A selectable channel for stream-oriented listening sockets. The channel is created by invoking the open method of this class. This class uses the Socket class behind the scenes. An instance of this class is used to establish connection between the client and the server like the Socket class instance.
Selector A multiplexor for the SelectableChannel object.
SelectionKey Used for representing the registration of SelectableChannel with a Selector.

(*) Refer to the Java API documentation for more information.

How It Works

The Selector class object acts as an interface between remote clients and the server in the following manner.

The Selector class object
Figure 1: The Selector class object

The way to create the selector object is by calling its open() static method:

Selector selector=Selector.open();

The ServerSocketChannel instance, which listens to the new connection request from the client, is created in a similar manner by invoking its static open() method:

ServerSocketChannel serverSocketChannel=ServerSocketChannel.open();

The channel thus created is by default is in a blocking state. We must set it to an unblocking state explicitly in the following manner:

serverSocketChannel. configureBlocking(false);

The server socket channel must be bound to an IP address and a port number so that the client socket can communicate:

InetAddress ip=InetAddress.getByName("localhost");
serverSocketChannel.bind(new InetSocketAddress(ip, 1234));

Now, the server socket must register itself with the selector. The selector acts as a multiplexor of selectable channels. This primarily means selectors registration of a specific operation. The SelectionKey class integer constraints to designate each operation. For example:

  • SelectonKey.OP_CONNECT: Designates connect operation progress
  • SelectonKey.OP_ACCEPT: Designates client request for new connection
  • SelectonKey.OP_READ: Designates ready to read some data
  • SelectonKey.OP_WRITE: Designates ready to write some data

The registration is done as follows:

ServerSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

The selector is now ready to intercept a client socket connection and relay to the server socket channel.

A Quick Example

This is a simple implementation of an echo server using a Java non-blocking socket channel.

package org.mano.example;
import java.net.*;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.*;
public class NonblockingServer {
   public static void main(String[] args)
         throws Exception {
      InetAddress host = InetAddress.getByName("localhost");
      Selector selector = Selector.open();
      ServerSocketChannel serverSocketChannel =
         ServerSocketChannel.open();
      serverSocketChannel.configureBlocking(false);
      serverSocketChannel.bind(new InetSocketAddress(host, 1234));
      serverSocketChannel.register(selector, SelectionKey.
         OP_ACCEPT);
      SelectionKey key = null;
      while (true) {
         if (selector.select() <= 0)
            continue;
         Set<SelectionKey> selectedKeys = selector.selectedKeys();
         Iterator<SelectionKey> iterator = selectedKeys.iterator();
         while (iterator.hasNext()) {
            key = (SelectionKey) iterator.next();
            iterator.remove();
            if (key.isAcceptable()) {
               SocketChannel sc = serverSocketChannel.accept();
               sc.configureBlocking(false);
               sc.register(selector, SelectionKey.
                  OP_READ);
               System.out.println("Connection Accepted: "
                  + sc.getLocalAddress() + "n");
            }
            if (key.isReadable()) {
               SocketChannel sc = (SocketChannel) key.channel();
               ByteBuffer bb = ByteBuffer.allocate(1024);
               sc.read(bb);
               String result = new String(bb.array()).trim();<
               System.out.println("Message received: "
                  + result
                  + " Message length= " + result.length());
               if (result.length() <= 0) {
                  sc.close();
                  System.out.println("Connection closed...");
                  System.out.println(
                     "Server will keep running. " +
                     "Try running another client to " +
                     "re-establish connection");
               }
            }
         }
      }
   }
}



package org.mano.example;
import java.io.*;
import java.net.*;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.*;
public class NonBlockingClient {
   private static BufferedReader input = null;
   public static void main(String[] args) throws Exception {
      InetSocketAddress addr = new InetSocketAddress(
         InetAddress.getByName("localhost"), 1234);
      Selector selector = Selector.open();
      SocketChannel sc = SocketChannel.open();
      sc.configureBlocking(false);
      sc.connect(addr);
      sc.register(selector, SelectionKey.OP_CONNECT |
         SelectionKey.OP_READ | SelectionKey.
            OP_WRITE);
      input = new BufferedReader(new
         InputStreamReader(System.in));
      while (true) {
         if (selector.select() > 0) {
            Boolean doneStatus = processReadySet
               (selector.selectedKeys());
            if (doneStatus) {
               break;
            }
         }
      }
      sc.close();
   }
   public static Boolean processReadySet(Set readySet)
         throws Exception {
      SelectionKey key = null;
      Iterator iterator = null;
      iterator = readySet.iterator();
      while (iterator.hasNext()) {
         key = (SelectionKey) iterator.next();
         iterator.remove();
      }
      if (key.isConnectable()) {
         Boolean connected = processConnect(key);
         if (!connected) {
            return true;
         }
      }
      if (key.isReadable()) {
         SocketChannel sc = (SocketChannel) key.channel();
         ByteBuffer bb = ByteBuffer.allocate(1024);
         sc.read(bb);
         String result = new String(bb.array()).trim();
         System.out.println("Message received from
            Server: " + result + " Message length= "
            + result.length());
      }
      if (key.isWritable()) {
         System.out.print("Type a message (type quit
            to stop): ");
         String msg = input.readLine();
         if (msg.equalsIgnoreCase("quit")) {
            return true;
         }
         SocketChannel sc = (SocketChannel) key.channel();
         ByteBuffer bb = ByteBuffer.wrap(msg.getBytes());
         sc.write(bb);
      }
      return false;
   }
   public static Boolean processConnect(SelectionKey key) {
      SocketChannel sc = (SocketChannel) key.channel();
      try {
         while (sc.isConnectionPending()) {
            sc.finishConnect();
         }
      } catch (IOException e) {
         key.cancel();
         e.printStackTrace();
         return false;
      }
      return true;
   }
}

Conclusion

This is a glimpse of what a non-blocking socket channel is and how to implement it in Java. Apart from providing some extra classes by the Java API library which specifically address the issues of non-blocking socket programming, the implementation is more or less similar to normal socket programming. In relation to this, there are classes that support asynchronous socket channels. We’ll take them up in a separate article.

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Latest Posts

Related Stories