TCP non bloquant (1/3)

Non-blocking TCP Adder

In this exercise, we want to write a non-blocking TCP server which sums integers. The server will have only one thread but will handle multiple clients concurrently.

The SumOneShot Protocol
The client sends two int (4 byte) in Big Endian and then closes the connexion for writing. The server responds with a single int in Big Endian, which is the sum of the two integers sent by the client, and then closes the connexion.
For instance, if the client sends 7 and 10, the server will respond with 17.

Starting from ServerSumOneShot.java, write a non-blocking server ServerSumOneShot for the protocole SumOneShot.
It is not necessary to check if the client has closed the connexion after sending the two int: after sending the response, the server simply closes the connexion.

  1. For this exercise, you will store in the attachement field of the SelectionKey a ByteBuffer with capacity 2*Integer.BYTES (this buffer is created and attached when the client is accepted).
  2. Recall that the method soceketChannel.write (as well as the socketChannel.read) is non-blocking and that there is no guaranteee that the entire work-zone of the ByteBuffer will be written in just one call. Check your code to see what would happen if the call soceketChannel.write to give the answer to the client writes only 2 bytes out of the 4 bytes. This is unlikely but it could happen and your code should handle it.
  3. In the method processSelectedKeys, how should you handle the exceptions by the methods doRead, doWrite and doAccept ?

To test your code, use the jar ClientSumOneShot.jar which reads two integers from the keyboard, sends them to the server and prints the answer from the server.

% java fr.upem.net.tcp.nonblocking.ServerSumOneShot 7777
% java -jar ClientSumOneShot.jar localhost 7777
Connected to server.
Enter two integers:
1
2
Waiting for server's reply ...
Server replied : 3

By using the -bug option of ClientSumOneShot.jar, you can simulate a client which closes its connexion before sending the two ints. In this case your server should close the connexion because the client does not respect the protocol, but should continue to accept connections.

% java fr.upem.net.tcp.nonblocking.ServerSumOneShot 7777
% java -jar ClientSumOneShot.jar localhost 7777 -bug
Connected to server.
Enter two integers:
1
2
Closing the write-connection without sending the second INT.
Waiting for server's reply ...
Server closed the connection before sending the answer (This is the normal behavior).

We now change the protocol to allow a client to send multiple sums over the same connection.

Protocole Sum
The client sends two int (4 bytes each) in Big Endian. The server responds with an int (4 bytes) in Big Endian equal to the sum of the two ints sent by the client. These exchanges go on until the client closes the connexion for writing.

Copy you server from the previous question and adapt the code to produce a server ServerSum for this new protocol.

You can test your server with the following jar ClientSum.jar.
% java fr.upem.net.tcp.nonblocking.ServerSum 7777
% java -jar ClientTestServerMulti.jar localhost 7777
    

If everything works as expected, you should see the following output:
Client 0 connected to server.
Client 1 connected to server.
Client 2 connected to server.
Client 3 connected to server.
Client 4 connected to server.
Client 1 finished sending its requests.
Client 2 finished sending its requests.
Client 3 finished sending its requests.
Client 0 finished sending its requests.
Client 4 finished sending its requests.
Client 1 finished receiving the answers.
Client 0 finished receiving the answers.
Client 4 finished receiving the answers.
Client 3 finished receiving the answers.
Client 2 finished receiving the answers.    
 
This server is not very efficient as it only reads 8 bytes at a time. We will see in exercise 3 how to write a more efficient server which can read a large number of requests at the same time.

Echo Server

The goal of this execrise is to write a server for the protocol Echo. We will take the occasion to better structure our non-blocking servers using what we call a Context.

Protocole Echo
In the protocol Echo, the server sends back to the client all of the byte sent by the latter until the client closes the connection for writing.

We would like our server to be able to be have simultaneously OP_READ as well as OP_WRITE as operations of interest. In order to do this, we will attach an object of class Context to each SelectionKey correspoding to a SocketChannel of a client.

static private class Context {

  final private SelectionKey key;
  final private SocketChannel sc;
  final private ByteBuffer bb = ByteBuffer.allocate(BUFFER_SIZE);
  private boolean closed = false;

  private Context(SelectionKey key){
      this.key = key;
      this.sc = (SocketChannel) key.channel();
  }

  private void updateInterestOps() {
      // TODO
  }

  
  private void doRead() throws IOException {
      // TODO
  }

  private void doWrite() throws IOException {
      // TODO
  }

}

The principle of the class Context is that it contains all of the information regarding the associated SocketChannel, i.e., associated with the corresponding client. The class will always contain the SocketChannel and the SelectionKey associated. In the case of a server for the prtocol Echo, the class will also contain a ByteBuffer and a boolean. The boolean is used to remember whether the client has closed the connection for writing. The class Context will implement the methods doRead and doWrite as well as updateInterestOps. The method updateInterestOps will be called by doRead and doWrite, and will be the sole responible for updating the interestOps of the SelectionKey. In order to update the value of interestOps, you will just consider the state of the ByteBuffer as well as the boolean.

Convention: For the methods doRead, doWrite and updateInterestOps we will suppose that the ByteBuffer is in write-mode before and after the call to the method.

Starting from the draft ServerEcho.java, write a server for the protocol Echo in non-blocking mode.

Once your server seems to be working, test it with the jar ClientEchoSlow.jar, which loops the byte 1 and, in another thread, displays the number of bytes received. The client never stops.

The significnace of this client is that it reads "slowly", and so it slows down the rythm of the writing of the server (due to the flow control done by TCP). Consulting the CPU resources (top in the command line) consumed by your server, you should see that they are very limited. If the server consumes lots of CPU, it could be due to the fact that it tries to write when in fact it is not possible (so active waiting); the exact opposite of the principle of non-blocking I/O.

% java fr.upem.net.tcp.nonblocking.ServerEcho 7777
% java -jar ClientEchoSlow.jar localhost 7777