Non-blocking TCP Servers

Non-blocking TCP (1/3)

By default in Java, the method accept() of ServerSocketChannel is blocking: it blocks the execution of the thread until a connection can be accepted.
The methods read() and write() of the class SocketChannel have the same behaviour.

ServerSocketChannel and SocketChannel can be configured for accept(), read() and write() to be non-blocking.

 socket.configureBlocking(false);

Non-blocking TCP (2/3)

In non-blocking mode, the methods accept,read and write always terminate immediately.

  • accept returns null if there is no pending connection.
  • read returns 0 if no data is ready to be read.
  • write no longer guaranties that the entire work-zone of the buffer will be written: it can write part of it or even nothing.

Non-blocking TCP (3/3)

How to accept connexion on a ServerSocketChannel ssc configured in non-blocking mode ?

ServerSocketChannel ssc = ServerSocketChannel.open();
ssc.bind(new InetSocketAddress(7777));
ssc.configureBlocking(false);

Bad idea: perform an unconditionnal accept() ?

SocketChannel sc = ssc.accept(); // NON: sc may be null

Bad idea: accept while sc is null ?

SocketChannel sc;
do {
  sc=ssc.accept();
} while(sc==null);  // NO: Active waiting !

Good idea: use a selector to be notified of a pending connection

Selector

Mechanism used to monitor several types of channels for different types of operations. In particular, we can be notified of:

  • the presence of a pending connection (for accept() on a ServerSocketChannel),
  • the presence of data to be read (for read() on a SocketChannel),
  • or the possibility of writing data (for write() on a SocketChannel).

Therefore, for a non-blocking TCP server, the ServerSocketChannel and all the SocketChannel of the clients, can be monitored by the same selector.

Registering the ServerSocketChannel

At the start, there is only one ServerSocketChannel to monitor

Selector selector = Selector.open();

ServerSocketChannel ssc = ServerSocketChannel.open();
ssc.bind(new InetSocketAddress(7777));
ssc.configureBlocking(false);

// register to selector for accepts 
SelectionKey sKey = ssc.register(selector,SelectionKey.OP_ACCEPT);

Registration is done via the register() method on the channel with the actions we want to monitor:
OP_ACCEPT indicates that we want to be notified of pending connections.
It is the only operation available on ServerSocketChannels.

Registering the SocketChannel of a client

Once a client has connected to the server, we need to accept it and to register the SocketChannel with the selector.

SocketChannel sc = ssc.accept();
if (sc == null) {
  // We need to check if the accept has worked
  // remember that the selector only gives an hint
} else {
  sc.configureBlocking(false);
  SelectionKey cKey = sc.register(selector,SelectionKey.OP_READ);
}

A client SocketChannel is registered to the same selector, with the operation we want to monitor:
OP_READ to be notified when data can be read,
OP_WRITE to be notified when data can be written
OP_READ | OP_WRITE to be notified in both cases.

SelectionKey

Selectors use SelectionKeys to represent channels, either the ServerSocketChannel or the SocketChannel of the clients. The keys contain 3 pieces of information:

  • the channel associated with the key: SelectableChannel key.channel()
  • an int coding the operations we want to monitor on this channel: int key.interestOps()
    This can be modified using key.interestOps(int ops)
  • a attached object associated to the key (kind of a free HashMap): Object key.attachment()
    An object can be attached using key.attach(Object o)

Using the selector

Idea: replace the different blocking calls to accept(), read() or write() on the various channels by one call to select() on the selector and then successively treat the keys for which an operation is available.

The selector only provides a hint, so we need to check that the operation was successful.

Exemple (00/15)

Exemple (01/15)

Exemple (02/15)

Exemple (03/15)

Exemple (04/15)

Exemple (05/15)

Exemple (06/15)

Exemple (07/15)

Exemple (08/15)

Exemple (09/15)

Exemple (10/15)

Exemple (11/15)

Exemple (12/15)

Exemple (13/15)

Exemple (14/15)

Exemple (15/15)

Selection loop

Set selectedKeys = selector.selectedKeys();
while (!Thread.interrupted()) {
   selector.select();
   processSelectedKeys(); 
   selectedKeys.clear();
}                

processSelectedKeys

    for (SelectionKey key : selectedKeys) {
       if (key.isValid() && key.isAcceptable()) {
          doAccept(key);
       }
       if (key.isValid() && key.isWritable()) {
          doWrite(key);
       }
       if (key.isValid() && key.isReadable()) {
          doRead(key);
       }
    }           

Be carefull about the exceptions.
What does it mean to have an IOException in the doAccept()?
And in doWrite() or doRead()?

Boucle de sélection (depuis Java 11)

while (!Thread.interrupted()) {
   selector.select(this::treatKey);
}                
where
void treatKey(SelectionKey key) {
    if (key.isValid() && key.isAcceptable()) {
        doAccept(key);
    }
    if (key.isValid() && key.isWritable()) {
        doWrite(key);
    }
    if (key.isValid() && key.isReadable()) {
        doRead(key);
    }
}           

Careful with the exceptions ...

doAccept

private void doAccept(SelectionKey key) throws IOException {
   // only the ServerSocketChannel is registered in OP_ACCEPT 
   ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
   SocketChannel sc = ssc.accept();
   if (sc==null) 
      return; // the selector gave a bad hint
   sc.configureBlocking(false);
   sc.register(selector,SelectionKey.OP_READ);
}              

We can attach an object to the key of a channel (i.e., an attachement), this can be done during registration:

   sc.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(SIZE));

Comparing with blocking mode

In a blocking server, we had at least one thread per active connexion. In a non-blocking server, there is only one thread handling all connections.

It is possible to handle more connections at the price of a possibly diminished response time.