The blocking mode of socket programming is inefficient, but it is the essence of typical socket programming in Java. However, there are other techniques. such as employing asynchronous socket channels, to establish communication over sockets that can considerably succumb the inefficiency. This article delves into the concept of asynchronous socket channels and how they can be implemented in Java with a simple example.
Socket
In a typical socket programming situation, one that uses TCP and UDP sockets, the I/O operations work in a blocking and synchronous mode. This means that when a client socket’s thread invokes a read operation, it blocks the server until the data is available. This can also potentially block the writes if the underlying buffer is full. The makes the other client socket threads halt until the channel has been released. This type of channel blocking is clearly inefficient. To overcome the situation. what can be done instead is to wait until the thread finishes its job. The client socket can notify as soon as the data is available from the server; meanwhile, other threads can do something else until the data from the server arrives. Another way is to create separate threads to communicate if the server has a number of open sockets. In either case, this is a way around and not a proper solution. Java, however, provides a solution to this problem through SocketChannel, as we’ll see.
Socket Channel
A socket channel, on the other hand, is non-blocking way to establish communication with the server sockets. Here, we can have one thread that can communicate with a number of open connections at the same time. This is achieved by adding a bunch of SocketChannels to a Selector. The selector object sits in-between the client and the server. The client socket threads get the socket channel by looping on the selector’s select method. It can notify on the status of the socket channel whether it has been accepted, received data, or closed. This technique enables communication with multiple clients in a single thread without the overhead of maintaining multiple threads and synchronization. You can see this in Figure 1.
Figure 1: Socket channels
Asynchronous Socket Channel
The classes that support asynchronous socket channels are actually part of the Java NIO API library. The idea is to implement non-blocking asynchronous IO communication through sockets. Asynchronous IO leverages call-back code to be executed on IO completion and the idea of non-blocking refers to the IO operation which returns immediately, either with data, no data, or an error code. This means that, while reading from a non-blocking channel, there is no delay in returning the value or block the channel until something tangible is returned. There are two key classes associated with asynchronous socket operation. They are AsynchronousServerSocketChannel and AsynchronousSocketChannel; both are found in the package named java.nio.channels.
An object of the AsynchronousServerSocketChannel class designates a socket server that listens for an incoming client connection request. Interaction can begin only once the request is accepted. The class named AsynchronousSocketChannel is responsible for all the establishing interaction between the client and the server at both ends.
The main difference between a normal, aka synchronous socket connection, and an asynchronous socket connection is that the requester in the latter case is immediately notified after completion of each operation. This is not the case with the synchronous socket connection because, in this case, it blocks the channel until the request is complete.
Due the asynchronous nature of the socket channel, there is a handler that takes care of the success or failed status of the socket operation.
Creating an Asynchronous Socket Server
An instance of the server is created in the following manner:
AsynchronousServerSocketChannel server = AsynchronousServerSocketChannel.open()
The server must be bound to an address and port. It’s a localhost in our case, and the port number is any unused port number in the working machine.
server.bind(new InetSocketAddress("127.0.0.1", 1234));
We then can accept the connection as follows:
Future<AsynchronousSocketChannel> acceptCon = server.accept();
We can use the get() method to get the result from the Future object. Note that, in the case of the server, we have specified a timeout value with the get request. This makes the connection wait for a specified period of time and then close the connection automatically.
The client part is also very similar. Refer to the Java API documentation for details on the APIs used in the following program.
Putting It All Together
This is a quick and simple client and server code to illustrate the ideas already presented. The client asks a question and the server replies with an answer. That’s all.
Server.java
package org.mano.sampleapp; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.AsynchronousServerSocketChannel; import java.nio.channels.AsynchronousSocketChannel; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; public class Server { public static void main(String[] args){ try (AsynchronousServerSocketChannel server = AsynchronousServerSocketChannel.open()) { server.bind(new InetSocketAddress("127.0.0.1", 1234)); Future<AsynchronousSocketChannel> acceptCon = server.accept(); AsynchronousSocketChannel client = acceptCon.get(10, TimeUnit.SECONDS); if ((client!= null) && (client.isOpen())) { ByteBuffer buffer = ByteBuffer.allocate(1024); Future<Integer> readval = client.read(buffer); System.out.println("Received from client: " + new String(buffer.array()).trim()); readval.get(); buffer.flip(); String str= "I'm fine. Thank you!"; Future<Integer> writeVal = client.write( ByteBuffer.wrap(str.getBytes())); System.out.println("Writing back to client: " +str); writeVal.get(); buffer.clear(); } client.close(); } catch (Exception e) { e.printStackTrace(); } } }
Client.java
package org.mano.sampleapp; import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.AsynchronousSocketChannel; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; public class Client { public static void main(String[] args){ try (AsynchronousSocketChannel client = AsynchronousSocketChannel.open()) { Future<Void> result = client.connect( new InetSocketAddress("127.0.0.1", 1234)); result.get(); String str= "Hello! How are you?"; ByteBuffer buffer = ByteBuffer.wrap(str.getBytes()); Future<Integer> writeval = client.write(buffer); System.out.println("Writing to server: "+str); writeval.get(); buffer.flip(); Future<Integer> readval = client.read(buffer); System.out.println("Received from server: " +new String(buffer.array()).trim()); readval.get(); buffer.clear(); } catch (ExecutionException | IOException e) { e.printStackTrace(); } catch (InterruptedException e) { System.out.println("Disconnected from the server."); } } }
Output
Figure 2: The program during execution
Conclusion
The preceding program is a rudimentary implementation of the ideas I’ve presented. The server code can be rewritten using CompletionHandler rather than using Future. In fact, using CompletionHandler is a better approach. The idea is to show that the code used to implement an asynchronous socket channel is not very different from synchronous socket programming except that the techniques of communication are quite different. Interested readers can try their hand at re-implementing the program with CompletionHandler. Happy coding!