JaVa
   

Creating a Non-Blocking Server

Now that we know how to create a normal blocking server using channels, let's look at the great part of NIO, the non-blocking server. Here is the complete code for a non-blocking server and a slightly modified version of the previous client code, which also returns a message to the server on retrieval of a message:

Code Listing 18-4: NonBlockingServer.java
import java.nio.*;
import java.nio.channels.*;
import java.io.*;
import java.net.*;
import java.util.*;
public class NonBlockingServer
{
 public static void main(String[] args)
 {
 try
 {
 Selector selector = Selector.open();
 ServerSocketChannel serverSocketChannel =
 ServerSocketChannel.open();
 serverSocketChannel.configureBlocking(false);
 ServerSocket serverSocket = serverSocketChannel.socket();
 serverSocket.bind(new InetSocketAddress(9000));
 System.out.println("Non-blocking Server created on port
 9000");
 serverSocketChannel.register(selector,
 SelectionKey.OP_ACCEPT);
 System.out.println("Waiting for client connections...");
 int amountToProcess = 0;
 while(true)
 {
 amountToProcess = selector.selectNow();
 if(amountToProcess > 0)
 {
 try
 {
 Set keys = selector.selectedKeys();
 Iterator iterator = keys.iterator();
 while(iterator.hasNext())
 {
 SelectionKey selectionKey =
 (SelectionKey) iterator.next();
 iterator.remove(); // remove the key
 int operation = selectionKey
 .interestOps();
 if((SelectionKey.OP_ACCEPT & operation)
 != 0)
 {
 // Accept the connection...
 ServerSocketChannel channel =
 (ServerSocketChannel) selectionKey.channel();
 SocketChannel socket =
 channel.accept();
 socket.configureBlocking(false);
 // register for a writing operation
 socket.register(selector,
 SelectionKey.OP_WRITE);
 System.out.println("Client
 Connected...");
 }
 else if((SelectionKey.OP_READ &
 operation) != 0)
 {
 // Attempt to read...
 System.out.println("About to read
 from client...");
 SocketChannel socket =
 (SocketChannel) selectionKey
 .channel();
 // get the message from the client...
 ByteBuffer incomingLengthInBytes =
 ByteBuffer.allocate(4); // size of an 'int'
 socket.read(incomingLengthInBytes);
 incomingLengthInBytes.rewind();
 int incomingLength =
 incomingLengthInBytes.getInt();
 System.out.println("Got Incoming
 Length as: "+incomingLength+"
 bytes");
 // now allocate the correct size for
 // the message...
 ByteBuffer incomingData = ByteBuffer
 .allocate(incomingLength);
 socket.read(incomingData);
 incomingData.rewind();
 String string = incomingData
 .asCharBuffer().toString();
 // Finally print received message...
 System.out.println("Received:
 "+string);
 // terminate the connection...
 socket.close();
 }
 else if((SelectionKey.OP_WRITE &
 operation) != 0)
 {
 // Attempt to write...
 System.out.println("Now going to
 write to client...");
 SocketChannel socket =
 (SocketChannel) selectionKey
 .channel();
 socket.register(selector,
 SelectionKey.OP_READ);
 String stringToSend = "This is a
 message";
 int length = stringToSend.length() * 2;
 ByteBuffer lengthInBytes =
 ByteBuffer.allocate(4); // 4 = size of a 'int'
 ByteBuffer dataToSend =
 ByteBuffer.allocate(length);
 lengthInBytes.putInt(length);
 lengthInBytes.rewind();
 dataToSend.asCharBuffer()
 .put(stringToSend);
 ByteBuffer sendArray[] =
 {lengthInBytes, dataToSend};
 socket.write(sendArray);
 //socket.close();
 System.out.println("Sent Message to
 Client...");
 }
 }
 }
 catch(IOException e)
 {
 System.out.println(e);
 }
 }
 }
 }
 catch(IOException e)
 {
 System.out.println(e);
 }
 }
}



Java End example
Code Listing 18-5: NonBlockingClient.java
import java.nio.*;
import java.nio.channels.*;
import java.io.*;
import java.net.*;
public class NonBlockingClient
{
 public static void main(String[] args)
 {
 try
 {
 SocketChannel socketChannel = SocketChannel.open();
 socketChannel.connect(new InetSocketAddress("127.0.0.1",
 9000));
 // wait for the message from the server...
 ByteBuffer incomingLengthInBytes =
 ByteBuffer.allocate(4); // size of an 'int'
 socketChannel.read(incomingLengthInBytes);
 incomingLengthInBytes.rewind();
 int incomingLength = incomingLengthInBytes.getInt();
 System.out.println("Got Incoming Length as:
 "+incomingLength+" bytes");
 // now allocate the correct size for the message...
 ByteBuffer incomingData =
 ByteBuffer.allocate(incomingLength);
 socketChannel.read(incomingData);
 incomingData.rewind();
 String string = incomingData.asCharBuffer().toString();
 // Finally print the received message...
 System.out.println("Received: "+string);
 // Send a message back to the server...
 String replyMessage = "Message Received - Thank you!";
 int length = replyMessage.length() * 2;
 ByteBuffer replyLength = ByteBuffer.allocate(4);
 replyLength.putInt(length);
 replyLength.rewind();
 ByteBuffer replyText = ByteBuffer.allocate(length);
 replyText.asCharBuffer().put(replyMessage);
 ByteBuffer toSend[] = {replyLength, replyText};
 socketChannel.write(toSend);
 }
 catch(IOException e)
 {
 System.out.println(e);
 }
 } }


Java End example

So when we run the non-blocking server, followed by the new client, we can expect the following output in the console:

Java Click To expand
Screenshot-4: Non-blocking server (after client has been executed) Java Click To expand
Screenshot-5: Client

Let's look at how we have changed the server to make it non-blocking. The first change is that we have added the java.util package to our import statements at the start. Then, after we declare our main class and main method, we create a Selector object by calling the static open method of the Selector class, using the following line of code:

Selector selector = Selector.open();


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 have an operation performed upon them. Next we create our ServerSocketChannel, as we did for the blocking server, but this time, after we create it we call the configureBlocking method, passing in false to tell the channel not to block. This can be seen in the following two lines of code:

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


Then we create the server, as we did in the previous example, using the following two lines of code:

ServerSocket serverSocket = serverSocketChannel.socket();
serverSocket.bind(new InetSocketAddress(9000));


However, this time, 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. We do this by calling the register method, which can be seen here:

serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);


This registers the serverSocketChannel to accept incoming connections (basically, to listen for incoming clients wanted to connect to the server). Next we create an integer called amountToProcess that will store the number of channels that currently require an operation to be performed upon them. This can be seen here:

int amountToProcess = 0;


Now we enter an infinite while loop and then call the selectNow method of the selector, which selects a set of keys that are ready to have an operation performed upon them and returns an integer value of how many were selected. This can be seen here:

while(true)
{
 amountToProcess = selector.selectNow();


Note the selectNow method will attempt to select, but if nothing is currently ready, it will continue (i.e., not block). In addition to this method, there is also a select(long) method, which will wait the amount of milliseconds you specify for keys to be selected before it continues. Finally, there is the select method with no parameters, which will block until there is at least one key ready for an operation. So we now know how many keys there are to be processed, as the value is stored in amountToProcess. We can now check if this value is greater than zero so we know whether any keys are available. This can be seen here:

if(amountToProcess > 0)
{


We can then get the selected keys (i.e., the ones that require an operation to be performed) by calling the selectedKeys() method of the selector, which then returns the keys as a set. This can be seen in the following line of code:

Set keys = selector.selectedKeys();


From this Set, we can then obtain an iterator by calling the iterator method, allowing us to easily cycle through all the keys in the set. This can be seen here:

Iterator iterator = keys.iterator();
while(iterator.hasNext())
{


So for each key, we get the key by calling the next method of the iterator and typecast it to type SelectionKey. Following that, we remove it from the iterator. This can be seen in the following two lines of code:

SelectionKey selectionKey = (SelectionKey) iterator.next();
iterator.remove();


We can then find out which operations the key is interested in by calling the interestOps method of the selectionKey object, which can be seen in the following line of code:

int operation = selectionKey.interestOps();


Then, once we have the operation in the integer variable (called operation), we can compare this to the defined operations (which are static final members of the SelectionKey class). First, we check to see if it is an accept operation (OP_ACCEPT). This can be seen here:

if((SelectionKey.OP_ACCEPT & operation) > 0)
{


If this is true, we get the channel from the selectionKey by calling the channel method. Then we typecast this to be a ServerSocketChannel, as this is the only channel that is registered to accept connections. This process can be seen in the following line of code:

ServerSocketChannel channel = (ServerSocketChannel)
 selectionKey.channel();


We can then call the accept method of the channel, which will accept the incoming connection and return a reference to a socket channel that we store in a reference called socket. This can be seen here:

SocketChannel socket = channel.accept();


We then make this socket non-blocking by calling the configureBlocking method, as we did for the server socket. This can be seen in the following line of code:

socket.configureBlocking(false);


Then, for this example, we will register the socket for a write operation (OP_WRITE), since we first want the server to write a string to the client, as we did in the previous example. So here is the line of code required to do this:

socket.register(selector, SelectionKey.OP_WRITE);


As you can see, we first pass in the selector, followed by the operation for which we wish to register the SocketChannel (in this case, the OP_WRITE operation). Next we check for a read operation being required (which we will look at in a moment). Then we move on to the write operation. We check the operation variable first using the following if statement:

else if((SelectionKey.OP_WRITE & operation) != 0)
{


Then, once we know it's a write operation, we obtain the SocketChannel by invoking the channel method of the SelectionKey, which can be seen in the following line of code:

SocketChannel socket = (SocketChannel) selectionKey.channel();


Next we register the channel for a read operation, as once the client has received the message, it will then send a "thank you" message back to the server. So we do this with the following line of code:

socket.register(selector, SelectionKey.OP_READ);


We then send the data in exactly the same way as we did in the blocking server. The client then receives and displays the message from the server in the same way as it did for the previous blocking example; however, this time we also send a message back to the server from the client, using the following block of code defined in the new client:

// Send a message back to the server...
String replyMessage = "Message Received - Thank you!";
int length = replyMessage.length() * 2;
ByteBuffer replyLength = ByteBuffer.allocate(4);
replyLength.putInt(length);
replyLength.rewind();
ByteBuffer replyText = ByteBuffer.allocate(length);
replyText.asCharBuffer().put(replyMessage);
ByteBuffer toSend[] = {replyLength, replyText};
socketChannel.write(toSend);


This is done in exactly the same way as we sent the message from the server.

When this message is sent to the server, it will read in the message and display it to the screen—using the same code as we used to read in the message on the client—because the channel has been registered with a read operation. Then, finally, it closes the connection to the client by means of the close method of the SocketChannel object.

JaVa
   
Comments