Open In App

How to Create a Non-Blocking Server in Java?

Last Updated : 09 May, 2022
Improve
Improve
Like Article
Like
Save
Share
Report

A Non-Blocking server means that it is able to have multiple requests in progress at the same time by the same process or thread because it uses Non-Blocking I/O. In the Non-Blocking approach – one thread can handle multiple queries at a time. A server using a Non-Blocking socket works in an asynchronous manner means once the request is received it lets the system perform the task on it, and continues to take other requests and once the task is over it responds according to each request. In this article, we see how we implement our own Non-blocking server and client. Let’s first discuss Java NIO components and classes implementing Java NIO channels.

Selectors

The Selector is one of the Java NIO classes. The channels that we want to listen to must be registered in Selector. Each channel is assigned with selectionKey. SelectionKey is an object that identifies a channel and contains information about the channel like whether the channel is ready to accept the connection. It holds information about the type of the request and who is making the request. The instance of Selector can monitor more socket channels and it also informs the application to process the request. We can create a selector by calling the Selector.open() method:

Selector selector = Selector.open(); 

Buffers

Buffers define the core functionality which is common to all buffers the limit, capacity, and current position. Java NIO buffers are used for interacting with NIO channels. It is the block of memory into which we can write data, which we can later be read again. The memory block is wrapped with an NIO buffer object, which provides easier methods to work with the memory block. There is a buffer for each basic data type:

  • CharBuffer
  • DoubleBuffer
  • IntBuffer
  • ByteBuffer
  • ShortBuffer
  • FloatBuffer
  • LongBuffer

Syntax to allocate the ByteBuffer with the capacity of 1024 bytes:

ByteBuffer buffer = ByteBuffer.allocate(1024);

Classes Implementing the Java NIO channel

SocketChannel

The Java NIO SocketChannel is used for connecting a channel with a TCP network socket. It is equivalent to Java Networking Sockets used in network programming. It also uses the factory methods for creating the new object. A SocketChannel in Java NIO can be created :

  1. When an incoming connection arrives at the ServerSocketChannel.
  2. To connect with a server anywhere using the internet.

We can open a SocketChannel by calling the SocketChannel.Open() method.

SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("localhost", 8089));

ServerSocketChannel

The Java NIO ServerSocketChannel is also used for connecting a channel with a TCP network socket. It is equivalent to Java Networking Sockets used in network programming. The class of ServerSocketChannel is located in the java.nio.channels package. A server-socket channel is created by invoking the .open() method of this class. A newly-created server-socket channel is open but not yet bound. An attempt to invoke the .accept() method of an unbound server-socket channel will cause a NotYetBoundException to be thrown. A server-socket channel can be bound by invoking one of the bind methods defined by this class.

ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.socket().bind(new InetSocketAddress("localhost", 8089));

while(true) {
   SocketChannel socketChannel = serverSocketChannel.accept();
}

Steps to Create a Non-Blocking Server

Project Structure (IntelliJ IDEA)

Project Structure

 

Step 1: Open Selector that handles channel

We create a Selector object by calling the static .open() method of the Selector class. A Selector is used to hold a reference to a set of channels and can be asked to supply a set of channels that are ready to accept the connection. 

Syntax:  

selector = Selector.open();

Step 2: Bind the Server Port

We create ServerSocketChannel by calling the .open() method, after that we call the .configureBlocking() method on it, passing in false as a parameter to tell the channel not to block. Then we obtain the ServerSocket from the channel by calling the .socket() method. We bind the socket to port 8089, so the server can start listening on that port. 

ServerSocketChannel socket = ServerSocketChannel.open();
socket.configureBlocking(false);

ServerSocket serverSocket = socket.socket();
serverSocket.bind(new InetSocketAddress("localhost", 8089));

Step 3: Register ServerSocket to Selector

We need to register our server with the selector so that when operations are required to be performed, the server channel will be selected and the operations will be performed. This can be done by calling the .register() method. This registers the object of ServerSocketChannel to accept incoming connections.

socket.register(selector, ops, null);

Step 4: Wait for the event

Now we enter an infinite while loop and then call the .select() method of the selector, which selects a set of keys whose corresponding channels are ready for I/O operations. This method performs a blocking selection operation. It returns only after at least one channel is selected.

selector.select();

Step 5: Get the Selection Keys

We can get the selected keys that require an operation to be performed by calling the .selectedKeys() method of the selector, which returns the keys as a set.

Set<SelectionKey> selectedKeys = selector.selectedKeys();

We use an iterator by calling the .iterator() method that helps to traverse through all the keys in the set. We get the key by calling the .next() method of the iterator.

Iterator<SelectionKey> i = selectedKeys.iterator();

Step 6: Check whether the client is ready to accept

To test whether a channel corresponding to a key is ready to accept a new socket connection we use .isAcceptable() method. It behaves in exactly the same way as the expression key.readyOps() & OP_ACCEPT != 0

if (key.isAcceptable()) {
    // New client has been accepted
 }

Step 7: Read the Client’s message

We register the channel for a read operation, as once the client’s connection is accepted, the client will send the message back to the server. 

client.register(selector, SelectionKey.OP_READ);

It will read data from the channel and put it into the buffer. After that, we will send data from the buffer onto the screen.

Step 8: Close the connection

Now closes the connection to the client by using the .close() method of the SocketChannel object.

Below is the implementation of the above-mentioned steps:

File: Server.java

Java




/*package whatever //do not write package name here */
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;
  
public class Server {
    private static Selector selector = null;
  
    public static void main(String[] args)
    {
  
        try {
            selector = Selector.open();
            // We have to set connection host,port and
            // non-blocking mode
            ServerSocketChannel serverSocketChannel
                = ServerSocketChannel.open();
            ServerSocket serverSocket
                = serverSocketChannel.socket();
            serverSocket.bind(
                new InetSocketAddress("localhost", 8089));
            serverSocketChannel.configureBlocking(false);
            int ops = serverSocketChannel.validOps();
            serverSocketChannel.register(selector, ops,
                                         null);
            while (true) {
                selector.select();
                Set<SelectionKey> selectedKeys
                    = selector.selectedKeys();
                Iterator<SelectionKey> i
                    = selectedKeys.iterator();
  
                while (i.hasNext()) {
                    SelectionKey key = i.next();
  
                    if (key.isAcceptable()) {
                        // New client has been  accepted
                        handleAccept(serverSocketChannel,
                                     key);
                    }
                    else if (key.isReadable()) {
                        // We can run non-blocking operation
                        // READ on our client
                        handleRead(key);
                    }
                    i.remove();
                }
            }
        }
        catch (IOException e) {
            e.printStackTrace();
        }
    }
  
    private static void
    handleAccept(ServerSocketChannel mySocket,
                 SelectionKey key) throws IOException
    {
  
        System.out.println("Connection Accepted..");
  
        // Accept the connection and set non-blocking mode
        SocketChannel client = mySocket.accept();
        client.configureBlocking(false);
  
        // Register that client is reading this channel
        client.register(selector, SelectionKey.OP_READ);
    }
  
    private static void handleRead(SelectionKey key)
        throws IOException
    {
        System.out.println("Reading client's message.");
        
        // create a ServerSocketChannel to read the request
        SocketChannel client = (SocketChannel)key.channel();
  
        // Create buffer to read data
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        
        client.read(buffer);
        
        // Parse data from buffer to String
        String data = new String(buffer.array()).trim();
        if (data.length() > 0) {
            System.out.println("Received message: " + data);
            if (data.equalsIgnoreCase(
                    "I Love KirikoChan")) {
                client.close();
                System.out.println("Connection closed...");
            }
        }
    }
}


File: Client.java

Java




/*package whatever //do not write package name here */
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
  
public class Client {
    public static void main(String[] args)
    {
        try {
            String[] messages
                = { "Non-Blocking servers are the best.",
                    "I Love GeeksForGeeks",
                    "I Love KirikoChan" };
            System.out.println(
                "Connection accepted by the Server..");
            SocketChannel client = SocketChannel.open(
                new InetSocketAddress("localhost", 8089));
  
            for (String msg : messages) {
                ByteBuffer buffer
                    = ByteBuffer.allocate(1024);
                buffer.put(msg.getBytes());
                buffer.flip();
                int bytesWritten = client.write(buffer);
                System.out.println(String.format(
                    "Sending Message: %s\nbufforBytes: %d",
                    msg, bytesWritten));
            }
  
            client.close();
            System.out.println("Client connection closed");
        }
        catch (IOException e) {
            e.printStackTrace();
        }
    }
}


Connecting the client to our server 

Now we have a client and a Non-Blocking server ready for communication. First, run the .main() inside the Server.java file it must be ready when the client sends a message. 

Output: Server.java

Output

The output of the Server.java file

Output: Client.java

Output

The output of the Server.java file



Like Article
Suggest improvement
Previous
Next
Share your thoughts in the comments

Similar Reads