Java NIO Selectors using NIO Client/Server Example

Java NIO Selectors using NIO Client/Server Example

Using this Java NIO tutorial you will cover the Selector class from the java.io.channels package library to build High-Performance I/O client-server using NIO.

What are Java NIO Selectors

A Selector allows a single thread to examine I/O events on multiple channels. These events determine which channels can be read from and written to by examining the the selector’s selected keys. Another way to think about selectors is to consider it a multiplexor of SelectableChannel objects. The job of the selector allows it to combine multiple channels using a single thread and simultaneously use all channels at the same time.

Selectable Channels and NIO Selectors

A selectable channel implements the SelectableChannel interface. This interface has the configureBlocking() method which adjusts the channel’s blocking mode. Additionally, this interface supports an efficient selectable I/O process that is more akin to Java listeners. Selectable channels require that the channel is registered with a selector. Once the channel is registered the selector can check and ensure that I/O operations such as ready to read, or ready to write are serviced accordingly. In actuality the Selector does work with channels directly but uses SelectionKey objects instead. When we register our channel with the selector we also choose which operations we have interest in observing.

nio_selector

Create Selector Instance

Let’s go ahead and create a Selector by calling the Selector.open() method.

Selector selector = Selector.open();

Registering Channels with Selector

Once we have a selector, we will call the register() method available on the channel objects (DatagramChannel, ServerSocketChannel, ServerSocket). This is done to register interest in the I/O events taking place in those objects.

channel1.register(selector, SelectionKey.OP_ACCEPT);
channel2.register (selector, SelectionKey.OP_CONNECT);
channel3.register (selector, SelectionKey.OP_WRITE);

// register interest in more than one event
channel4.register(selector,SelectionKey.OP_READ|SelectionKey.OP_WRITE);

In this example, you will notice that we registered interest when a connection is “accept” ready. Channels fire certain events notifying their clients that they are ready for those specific events. For example, when a channel has successfully connected to another server it is “connect” ready. Any channel that is ready to accept connections from incoming servers is “accept” ready. When a channel has data to be read it is said to be “read” read. Finally, a channel that is ready for you to write data to it is “write” ready.

These four events are represented in the SelectionKey class as:

public static final int OP_ACCEPT;  // socket-accept operations
public static final int	OP_CONNECT; // socket-connect operations
public static final int OP_READ;    // read operations
public static final int OP_WRITE;   // write operations

You can use these four constants when you are registering your selector, using the SelectionKey class and the constant.

  • SelectionKey.OP_ACCEPT;
  • SelectionKey.OP_CONNECT;
  • SelectionKey.OP_READ;
  • SelectionKey.OP_WRITE;

Using selectedKeys() Method

int readyChannels = selector.select();
logger.info("Keys with ready channels....: " + readyChannels);

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

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

while(i.hasNext()) {
  SelectionKey key = i.next();

  if (key.isAcceptable()) {
    processAcceptable(key);   // connection accepted
  } else if (key.isConnectable()) {
    processConnectable(key);  // connection established
  } else if (key.isReadable()) {
    processReadable(key);     // ready for reading
  } else if (key.isWritable()) {
    processWritable(key);     // ready for writing
  }
}

NIO Selector Server (MySelectorServerExample.java)

In this Java NIO Tutorial, we will create our NIO Selector Server which will accept connections on port 9999 on our local machine. We do this by using the bind() method and pass in an InetSocketAddress instance which defines the server in the standard host:port. To make the server Asynchronous or Non-Blocking we will use the configureBlocking() method and set the parameter to false. We register our selector (for certain operations) so that when something we are interested in (connection request, perform read or write) happens on our channel the selector will notify us. The selector will create a set of keys. As we iterate through this set, we are looking for certain types:

  • isAcceptable() method to check is client is requesting a connection.
  • isReadable() method to process data when client has data to be read. Our method will read data from the channel into our buffer and output the contents onto the sreen.
package com.avaldes.tutorial;

import java.io.IOException;
import java.net.InetAddress;
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 MySelectorServerExample {
 private static final int BUFFER_SIZE = 1024;
 private static Selector selector = null;

 public static void main(String[] args) {
  logger("Starting MySelectorExample...");
  try {
   InetAddress hostIP = InetAddress.getLocalHost();
   int port = 9999;

   logger(String.format("Trying to accept connections on %s:%d...",
     hostIP.getHostAddress(), port));
   selector = Selector.open();
   ServerSocketChannel mySocket = ServerSocketChannel.open();
   ServerSocket serverSocket = mySocket.socket();
   InetSocketAddress address = new InetSocketAddress(hostIP, port);
   serverSocket.bind(address);

   mySocket.configureBlocking(false);
   int ops = mySocket.validOps();
   mySocket.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()) {
      processAcceptEvent(mySocket, key);
     } else if (key.isReadable()) {
      processReadEvent(key);
     }
     i.remove();
    }
   }
  } catch (IOException e) {
   logger(e.getMessage());
   e.printStackTrace();
  }
 }

 private static void processAcceptEvent(ServerSocketChannel mySocket,
                  SelectionKey key) throws IOException {

  logger("Connection Accepted...");

  // Accept the connection and make it non-blocking
  SocketChannel myClient = mySocket.accept();
  myClient.configureBlocking(false);

  // Register interest in reading this channel
  myClient.register(selector, SelectionKey.OP_READ);
 }

 private static void processReadEvent(SelectionKey key)
                      throws IOException {
  logger("Inside processReadEvent...");
  // create a ServerSocketChannel to read the request
  SocketChannel myClient = (SocketChannel) key.channel();

  // Set up out 1k buffer to read data into
  ByteBuffer myBuffer = ByteBuffer.allocate(BUFFER_SIZE);
  myClient.read(myBuffer);
  String data = new String(myBuffer.array()).trim();
  if (data.length() > 0) {
   logger(String.format("Message Received.....: %s\n", data));
   if (data.equalsIgnoreCase("*exit*")) {
    myClient.close();
    logger("Closing Server Connection...");
   }
  }
 }

 public static void logger(String msg) {
  System.out.println(msg);
 }
}

NIO Selector Client (MySelectorClientExample.java)

package com.avaldes.tutorial;

import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;

public class MySelectorClientExample {
 private static final int BUFFER_SIZE = 1024;
 private static String[] messages =
  {"The best way to predict the future is to create it.",
  "As you think, so shall you become.",
  "The noblest pleasure is the joy of understanding.",
  "Courage is grace under pressure.",
  "*exit*"};

  public static void main(String[] args) {

    logger("Starting MySelectorClientExample...");
    try {
      int port = 9999;
      InetAddress hostIP = InetAddress.getLocalHost();
      InetSocketAddress myAddress =
          new InetSocketAddress(hostIP, port);
      SocketChannel myClient = SocketChannel.open(myAddress);

      logger(String.format("Trying to connect to %s:%d...",
              myAddress.getHostName(), myAddress.getPort()));

      for (String msg: messages) {
        ByteBuffer myBuffer=ByteBuffer.allocate(BUFFER_SIZE);
        myBuffer.put(msg.getBytes());
        myBuffer.flip();
        int bytesWritten = myClient.write(myBuffer);
        logger(String
              .format("Sending Message...: %s\nbytesWritten...: %d",
                      msg, bytesWritten));
     }
      logger("Closing Client connection...");
      myClient.close();
    } catch (IOException e) {
      logger(e.getMessage());
      e.printStackTrace();
    }
  }

  public static void logger(String msg) {
    System.out.println(msg);
  }
}

NIO Server Output

Starting MySelectorExample...
Trying to accept connections on 192.168.1.100:9999...
Connection Accepted...
Inside processReadEvent...
Message Received..: The best way to predict the future is to create it.

Inside processReadEvent...
Message Received..: As you think, so shall you become.

Inside processReadEvent...
Message Received..: The noblest pleasure is the joy of understanding.

Inside processReadEvent...
Message Received..: Courage is grace under pressure.

Inside processReadEvent...
Message Received..: *exit*

Closing Server Connection...

NIO Client Output

Starting MySelectorClientExample...
Trying to connect to 192.168.1.100:9999...
Sending Message...: The best way to predict the future is to create it.
bytesWritten......: 51
Sending Message...: As you think, so shall you become.
bytesWritten......: 34
Sending Message...: The noblest pleasure is the joy of understanding.
bytesWritten......: 49
Sending Message...: Courage is grace under pressure.
bytesWritten......: 32
Sending Message...: *exit*
bytesWritten......: 6
Closing Client connection...

That’s It!

I hope you enjoyed this tutorial. It was certainly a lot of fun putting it together and testing it out. Please continue to share the love and like us so that we can continue bringing you quality tutorials. Happy Coding!!!

java_nio_selectors

Java NIO Related Tutorials

  • Java NIO Tutorial
    In this tutorial series we discuss the new features of Java NIO.
  • Java NIO Buffers
    This post covers NIO Buffers in more detail and provides practical examples of using buffers in real world applications.
  • Java NIO Channels
    This post covers NIO Channels in more detail and provides examples on network connections and Java I/O in relation to files.
  • Java NIO Selectors
    In this tutorial we learn how to use the Selector class from the java.io.channels package library to build High-Performance I/O client-server using NIO.
  • Java NIO File Channel
    In this tutorial we learn how to use the FileChannel class from the java.io.channels package library and provide working examples on all of the main methods.
  • Java NIO Socket Channel
    In this tutorial we learn how to use the SocketChannel and how it is used for reading/writing stream oriented data and using TCP connection based protocol.
  • Java NIO DatagramChannel Tutorial
    In this tutorial we learn how to use the DatagramChannel to allow developers to build high-performant data streaming applications that send and receive datagrams using a protocol called UDP.
  • Java NIO and NIO2 Path Tutorial
    This tutorial will introduce the Path interface and many of its methods. The Path interface was made available as part of the Java SE 7 release in the Java NIO 2 File API.

Please Share Us on Social Media

Facebooktwittergoogle_plusredditpinterestlinkedinmail

Leave a Reply

Your email address will not be published. Required fields are marked *