TCP Servers

Foreword

In this series of exercices, you are going to write more and more sophisticated blocking TCP servers for the LongSum protocol.

LongSum protocol

In the LongSum protocol, a client can ask the server to perform sums of lists of long integers using the following TCP protocol:
  1. the client connects to the server;
  2. the client sends an INT (in big endian) giving the total number of operands to sum followed by each operand (a LONG in big endian);
  3. the client reads the answer from the server which is a LONG (in big endian) corresponding to the sum of the operands;
  4. the server does not close the connection which can be reused by the client to perform another sum by going back to step 2.

Iterative server

The first verion of the server, IterativeLongSumServer, simply accepts a client, treat its requests (i.e., there might be several sums) and when the client closes the connexion, the server accepts a new client and so on ...

To write this server, you will use the template IterativeLongSumServer.java.
The full treatment of the connexion with a client is performed by the method serve(SocketChannel client). Exceptions raised during the method serve are thrown and are handled in the launch method.

Write the code for the serve method.

Tests

You can test your server using the client ClientLongSum you wrote during the previous session.

% java fr.upem.net.tcp.IterativeLongSumServer 7777  
% java fr.upem.net.tcp.ClientLongSum localhost 7777

You must also test using the jar file ClientLongSumVerbose.jar, which connects to the server and sends 5 sums waiting in between sums a delay taken as a parameter on the command line.

% java -jar ClientLongSumVerbose.jar 7777  
% java fr.upem.net.tcp.ClientLongSum localhost 7777 100

Until now, you only tested your server with one client. Open 4 terminals. In one of them, launch your server and the 3 others launch de>ClientLongSumVerbose.jar with a large delay (for instance 5000) as in the image below:

Explain how 3 clients can connect simultaneously to your server despite the fact that your server only treats one client at the time.

On demand concurrent servers

We now want to allow several clients to simultaneously connect to the server. For this, we are going to create a new thread to treat each new client.

A first method, quite easy to implement, consists in simply creating a new thread each time a new client is accepted and use this thread to treat the client.

Using the same template as for IterativeLongSumServer, write a new class OnDemandConcurrentLongSumServer.

You can monitor the number of threads used by your server using jconsole. You should see that the number of threads increases with each new connected client.

The problem with this approach is that the number of threads started by the server is unbounded. This means that the server can crash under the load if too many clients connect at the same time.

We now want to write a class BoundedOnDemandConcurrentLongSumServer which ensures that the number of threads concurrently started is at most maxClient (an argument taken from the command line).

You can use the class Semaphore. The class Semaphore is thread-safe. An object of the class Semaphore is created with a certain amount of permits.
Semaphore semaphore = new Semaphore(nbPermits);
The semaphore.acquire() method tries to take a permits from the semaphore if there is one and otherwise it blocks until a permits becomes available. The semaphore.release() method put a permit back in the semaphore.

We write the class BoundedOnDemandConcurrentLongSumServer.

Concurrent server with pre-started threads

The main problem with the solution proposed in the previous exercise is that each time a client is accepted a new thread needs to be started before the client can be treated.
The idea we are going to explore in this exercise is to start (when the server is launched) a fixed number of threads which will each act as an iteratif serveur (cf. Exercice 1). The mutual exclusion between these worker threads is ensured by the call to serverSocketChannel.accept() (which is thread-safe).

Write a server FixedPrestartedLongSumServer which implements this idea. The server will take on the command line the number maxClientsof worker threads.


What happens to the clients who attempt to connect to your server when maxClient are already connected ?

Handling inactive clients

In this exercise, we are going to see how to handle inactive clients in a FixedPreStarted server. Indeed these clients are taking ressources from the server.

The problem with detecting inactive clients is that the method socketChannel.read and socketChannel.write are blocking. Hence if a worker thread calls socketChannel.read and the client does not write anything, the worker thread is blocked for ever.
Hence we will need to start a new thread (one for the server) which will have the task of disconnecting inactive clients.
There are two main problems to solve:

To solve these two problems, you can implement a class ThreadData which is threadsafe and which implements following two methods:

There will one ThreadData object for each worker thread. A unique thread while call the method closeIfInactive(int timeout) on each of the threadData objects every timeout milliseconds.

The easiest way to code the class ThreadData is to store the SocketChannel of the current client and a long corresponding to the time (obtained with System.currentTimeMillis()) of the last activity.

Starting with the class FixedPrestartedConcurrentLongSumServer, write a class FixedPrestartedConcurrentLongSumServerWithTimeout which properly handles inactive clients.

To test your code, you can use:

% java -jar ClientLongSumVerbose.jar localhost 7777 10000
% java -jar ClientLongSumVerbose.jar localhost 7777 1000
% java fr/upem/net/tcp/FixedPreStartedLongSumServerWithTimeout 7777
If the timeout of your server is of 2000 milliseconds, the first ClientLongSumVerbose should be disconnected by the server (and hence raise en IOException) but the second ClientLongSumVerbose should not be affected.

Console

On souhaite maintenant rajouter une console au serveur: c'est-à-dire la possibilité de lire des instructions au clavier.

Votre console devra reconnaître trois instructions:

Rajouter une console à votre serveur FixedPrestartedConcurrentLongSumServerWithTimeout de l'exercice précédent.