The non-blocking Java socket introduced in Java 2 Standard Edition 1.4 provides networking between applications without blocking processes using sockets. But what is a non-blocking socket, in what contexts can it be useful and how does it work?
What is a non-blocking socket?
A non-blocking socket allows I / O operations on a channel without blocking processes that use it. This means that we can use one thread to handle multiple simultaneous connections and get “asynchronous high-performance” read / write operations (some people may not agree with this)
Well, in what context can this be useful?
Suppose you want to deploy a server that accepts different client connections. Suppose also that you want the server to be able to process several requests at the same time. Using the traditional method, you have two options for developing such a server:
- Implement a multi-threaded server that manually processes the stream for each connection.
- Using an external third-party module.
Both solutions work, but by taking the first, you must develop a complete solution for managing flows with the attendant concurrency and conflict issues. The second solution makes the application dependent on an external module other than the JDK, and you may need to adapt the library to your needs. Using a non-blocking socket, you can implement a non-blocking server without directly managing flows or accessing external modules.
How does it work?
Before delving into the details, there are a few terms that you need to understand:
- In NIO-based implementations, instead of writing data to output streams and reading data from input streams, we read and write data from buffers. A buffer can be defined as temporary storage.
- A channel transfers most of the data to and from buffers. It can also be considered as an endpoint for communication.
- Readiness selection is a concept that refers to "the ability to select a socket that will not block when reading or writing data."
Java NIO has a Selector class that allows a single thread to check I / O events on multiple channels. How is this possible? Well, the selector can check the channel’s “readiness” for events such as a client trying to establish a connection, or a read / write operation. This means that each instance of Selector can control more socket channels and therefore more connections. Now, when something happens on the channel (an event occurs), selector informs the application to process the request . selector does this by creating event keys (or selection keys) that are instances of the SelectionKey class. Each key contains information about who makes the request and what type of request , as shown in Figure 1.
Figure 1: Block diagram
Main implementation
The server implementation consists of an infinite loop in which the selector listens for events and creates event keys. There are four possible key types:
- Acceptable: The associated client requests a connection.
- Connectable: The server accepted the connection.
- Reads: the server can read.
- Writable: the server can write.
Usually acceptable keys are created on the server side. In fact, this type of key simply tells the server that the client needs a connection, then the server individualizes the socket channel and associates it with a selector for read / write operations. After that, when the received client reads or writes something, the selector will create readable or writeable keys for it.
Now you are ready to write a server in Java, following the proposed algorithm. Creating a socket channel, selector and registering a selector socket can be done as follows:
final String HOSTNAME = "127.0.0.1"; final int PORT = 8511;
First we create an instance of SocketChannel with ServerSocketChannel.open() method. Then calling configureBlocking(false) sets this channel as non-blocking . Connection to the server is performed by the serverChannel.socket().bind() method. HOSTNAME represents the IP address of the server, and PORT represents the communication port. Finally, call the Selector.open() method to instantiate the selector and register it in the channel and registration type. In this example, the registration type is OP_ACCEPT , which means that the selector simply reports that the client is trying to connect to the server. Other possible options: OP_CONNECT , which will be used by the client; OP_READ and OP_WRITE .
Now we need to process these requests using an infinite loop. A simple way is as follows:
You can find the source of implementation here.
NOTE: asynchronous server
As an alternative to a non-blocking implementation, we can deploy an asynchronous server. For example, you can use the AsynchronousServerSocketChannel class, which provides an asynchronous channel for streaming listening sockets.
To use it, first execute its static open() method and then bind() it to a specific port . Then you execute its accept() method, passing it a class that implements the CompletionHandler interface. Most often, this handler is created as an anonymous inner class.
From this AsynchronousServerSocketChannel object, you call accept() to tell it to start listening for connections by passing it its own instance of CompletionHandler . When we call accept() , it returns immediately. Note that this differs from the traditional locking approach; while the accept() method blocks until a client connects to it , the AsynchronousServerSocketChannel accept() method processes it for you.
Here is an example:
public class NioSocketServer { public NioSocketServer() { try {
You can find the full code here.