Introduction to Nonblocking Sockets



Creating a Nonblocking Server

The entities I introduced in the previous section have correspondent Java entities. Client and server are two Java applications. The socket channels are instances of the new SocketChannel class, which allow transferring data through the net. They can be seen as the new sockets for the Java programmer. The SocketChannel class is defined in the java.nio.channel package.

The selector is a Selector class object. Each instance of Selector can monitor more socket channels, and thus more connections. When something interesting happens on the channel (for example, a client attempting a connection or a read/write operation), the selector informs the application to process the request. The selector does that by creating the keys, which are instances of the SelectionKey class. Each key holds information about the application making the request and the type of the request. The type can be one of the following:

  • Attempting connection (by the client)
  • Accepting connection (by the server)
  • Reading operation
  • Writing operation

A general algorithm to write a nonblocking server might be this:

create SocketChannel;

create Selector

associate the SocketChannel to the Selector

for(;;) {

waiting events from the Selector;

event arrived; create keys;

for each key created by Selector {

check the type of request;

isAcceptable:

get the client SocketChannel;

associate that SocketChannel  to the Selector;

record it for read/write operations

continue;

isReadable:

get the client SocketChannel;

read from the socket;

continue;

isWriteable:

get the client SocketChannel;

write on the socket;

continue;

}

}

Basically, the server implementation consists of an infinite loop in which the selector waits for events and creates the keys. According to the key-types, an opportune operation is performed. There are four possible types for a key:

  • Acceptable: the associated client requests a connection.
  • Connectable: the server accepted the connection.
  • Readable: the server can read.
  • Writeable: the server can write.

Usually an acceptable key is created on the server side. In fact, this kind of key simply informs the server that a client required a connection. In this circumstance, as you can see by the algorithm, the server individuates the socket channel and associates this to the selector for read/write operations. From this moment, when the accepted client reads or writes something, the selector will create readable or writeable keys for that client. Hence, the server will intercept those keys and perform the right actions.

Now you are ready to write the server in Java, following the proposed algorithm. The creation of the socket channel, the selector, and the socket-selector registration can be made in this way:

// Create the server socket channel

ServerSocketChannel server = ServerSocketChannel.open();

// nonblocking I/O

server.configureBlocking(false);

// host-port 8000

server.socket().bind(new java.net.InetSocketAddress(host,8000));

System.out.println(“Server attivo porta 8000”);

// Create the selector

Selector selector = Selector.open();

// Recording server to selector (type OP_ACCEPT)

server.register(selector,SelectionKey.OP_ACCEPT);

The open static method creates an instance of SocketChannel. The configureBlocking(false) invocation sets the channel as nonblocking. The connection to the server is made by the bind method. The “host” string represents the IP address of the server, and 8000 is the communication port. To create the selector, you can invoke the open static method of the Selector class. Finally, the register method associates the selector to the socket channel.

The second parameter represents the type of the registration. In this case, we use OP_ACCEPT, which means the selector merely reports that a client attempts a connection to the server. Other possible options are: OP_CONNECT, which will be used by the client; OP_READ; and OP_WRITE.

The Java code for the infinite loop is the following:

// Infinite server loop

for(;;) {

// Waiting for events

selector.select();

// Get keys

Set keys = selector.selectedKeys();

Iterator i = keys.iterator();

// For each keys…

while(i.hasNext()) {

SelectionKey key = (SelectionKey) i.next();

// Remove the current key

i.remove();

// if isAccetable = true

// then a client required a connection

if (key.isAcceptable()) {

// get client socket channel

SocketChannel client = server.accept();

// Non Blocking I/O

client.configureBlocking(false);

// recording to the selector (reading)

client.register(selector, SelectionKey.OP_READ);

continue;

}

// if isReadable = true

// then the server is ready to read

if (key.isReadable()) {

SocketChannel client = (SocketChannel) key.channel();

// Read byte coming from the client

int BUFFER_SIZE = 32;

ByteBuffer buffer = ByteBuffer.allocate(BUFFER_SIZE);

try {

client.read(buffer);

} catch (Exception e) {

// client is no longer active

e.printStackTrace();

continue;

}

// Show bytes on the console

buffer.flip();

Charset charset=Charset.forName(“ISO-8859-1”);

CharsetDecoder decoder = charset.newDecoder();

CharBuffer charBuffer = decoder.decode(buffer);

System.out.print(charBuffer.toString());

continue;

}

}

}

The first line of the loop calls the select method, which blocks the execution and waits for events recorded on the selector. In the code fragment, the socket channel is represented by the server variable. Actually, server is not a SocketChannel object, but a ServerSocketChannel object. This, as well as SocketChannel, is a generalization of SelectableChannel, which is generally used by the server applications.

The event the selector waits for is a client attempting a connection. When this happens, the server application gets the keys created by the selector and for each key, it checks the type. As you may notice, when a key is processed, it has to be removed from the set keys by invoking the remove method. If the type of the key is acceptable (isAcceptable()=true), the server locates the client socket channel by invoking the accept method, sets it as nonblocking, and records it to the selector using the OP_READ option. We could also use the OP_WRITE or OP_READ | OP_WRITE options, but for simplicity, I implemented the server such that it can only read from a channel and cannot write.

The client socket channel is now recorded to the selector for reading operations. Consequently, when the client writes something on the socket channel, the selector will inform the server application that there is something to read. That happens by the creation of a readable key, thus isReadable()=true. At this point, the application reads the data from the socket channel by using a 32-byte ByteBuffer, decodes the bytes using the ISO-8859-1 encoding, and shows them on the server console.

1 comment

1 ping

  1. Excellent goods from you, man. I’ve understand your stuff previous to and you are just too excellent. I really like what you have acquired here, certainly like what you are stating and the way in which you say it. You make it enjoyable and you still care for to keep it smart. I can not wait to read far more from you. This is really a terrific web site.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d bloggers like this: