Serveurs UDP

Serveur de mise en majuscules

Le but de cet exercice est de réaliser un serveur pour le protocole IdUpperCase.

Protocole IdUpperCase
Pour chaque chaîne de caractères à envoyer au serveur pour la mise en majuscules, la représentation utilisée sera la suivante :
  • un long en Big Endian identifiant la requête (chaque requête a un identifiant unique, qui est également utilisé dans la représentation de la réponse à cette requête) ;
  • les octets de la chaîne encodée en UTF-8.
Les réponses du serveur utilisent le même format.
Les paquets échangés dans ce protocole ont une taille maximale de 1024 octets.

Contrairement aux premiers clients, que l'on implémentait jusqu'ici dans le main() d'une classe, on va pour le serveur considérer que son port d'attachement local, via le DatagramChannel, est un champ qui identifie un objet "serveur" et on fournira pour la classe ServerIdUpperCaseUDP :

Vous pouvez utiliser la trame de serveur ci-après comme point de départ : ServerIdUpperCaseUDP.java

Écrivez la classe ServerIdUpperCaseUDP.java qui prend simplement en paramètre le port d'attachement du serveur.

Testez votre serveur avec un de vos clients développés et testés lors des séances précédentes.

$ java fr.uge.net.udp.ServerIdUpperCaseUDP 4545
Vous pouvez tester d'abord sans proxy, puis à travers le proxy.

Si vous avez encore des problèmes avec votre client, vous pouvez utiliser le ClientIdUpperCaseUDP.jar en guise de client (implémentation burst).

Serveur : longue somme de int

L'objectif est dans cet exercice de réaliser un service de somme d'entiers ints. Le client dispose d'une liste d'opérandes (de type int) qu'il doit envoyer, dans des paquets différents, au serveur qui doit en faire la somme (de type long) et la renvoyer au client.

Bien sûr, il s'agit d'un protocole statefull et le serveur devra mémoriser de quel client (adresse IP + port) il reçoit les informations, afin de ne pas "mélanger" les opérandes de plusieurs clients.

De plus, plusieurs points de vigilance nous conduisent à mettre en place un protocole particulier :

Voici donc les différents paquets de notre protocole : chaque paquet débute par un code d'opération codé sur un octet qui vaut :

Note : Tous les entiers int et long sont transmis en Big Endian. La somme est un long, attention aux possibles overflows.

Prenons l'exemple d'un client Charlie qui veut envoyer les entiers ints (opérandes) 10000, 2000 et 300000 au serveur Serge. Supposons que l'identifiant de cette somme soit 55555.
Charlie envoie donc ses 3 opérandes, dans 3 paquets distincts :

+-----+-----------+-----------+-----------+-----------+
|  1  |   55555   |     0     |     3     |    10000  |  // C -> S OP 0
+-----+-----------+-----------+-----------+-----------+
   OP   sessionID   idPosOper   totalOper    opValue

+-----+-----------+-----------+-----------+-----------+
|  1  |   55555   |     1     |     3     |     2000  |  // C -> S OP 1
+-----+-----------+-----------+-----------+-----------+
   OP   sessionID   idPosOper   totalOper    opValue

+-----+-----------+-----------+-----------+-----------+
|  1  |   55555   |     2     |     3     |   300000  |  // C -> S OP 2
+-----+-----------+-----------+-----------+-----------+
   OP   sessionID   idPosOper   totalOper    opValue
Supposons que Serge ne reçoit correctement que le paquet 1 et le paquet 2 (le paquet 0 est perdu !) ; il acquitte les opérandes reçues :
+-----+-----------+-----------+
|  2  |   55555   |     1     |  // S -> C ACK 1
+-----+-----------+-----------+
  ACK   sessionID   idPosOper  

+-----+-----------+-----------+
|  2  |   55555   |     2     |  // S -> C ACK 2
+-----+-----------+-----------+
  ACK   sessionID   idPosOper  
Mais l'acquittement 2 est perdu et Charlie ne reçoit que l'acquittement du paquet 1. Au bout d'un temps d'attente fixé, Charlie décide de ré-envoyer les opérandes qui n'ont pas été acquittées, soit 0 et 2 :
+-----+-----------+-----------+-----------+-----------+
|  1  |   55555   |     0     |     3     |    10000  |  // C -> S OP 0
+-----+-----------+-----------+-----------+-----------+
   OP   sessionID   idPosOper   totalOper    opValue

+-----+-----------+-----------+-----------+-----------+
|  1  |   55555   |     2     |     3     |   300000  |  // C -> S OP 2
+-----+-----------+-----------+-----------+-----------+
   OP   sessionID   idPosOper   totalOper    opValue
Si Serge reçoit cette fois les deux paquets, il ne "comptabilise" dans sa somme que le premier, puisque le second a le même idPosOper qu'un paquet déjà reçu (il a été reçu en double), mais il acquitte tout de même chaque paquet reçu.
+-----+-----------+-----------+
|  2  |   55555   |     0     |  // S -> C ACK 0
+-----+-----------+-----------+
  ACK   sessionID   idPosOper  

+-----+-----------+-----------+
|  2  |   55555   |     2     |  // S -> C ACK 2
+-----+-----------+-----------+
  ACK   sessionID   idPosOper  
et de plus, comme il a obtenu tous (3) les opérandes nécessaires (d'après totalOper), il peut renvoyer la somme à Charlie :
+----+-----------+-----------+
|  3 | sessionID |   312400  |  // S -> C RES 312400
+----+-----------+-----------+
  RES  sessionID      sum
Si Charlie reçoit les deux acquittements, il a la certitude que toutes ses opérandes sont à la disposition de Serge, mais même s'il ne recevait pas l'acquittement 0, qui pourrait à nouveau se perdre, il peut arrêter immédiatement toute activité dès qu'il reçoit le paquet RES, puisque celui-ci contient sa réponse ultime.
Si en revanche il a reçu tous les acquittements mais pas le résultat, il peut se douter que c'est le paquet RES qui s'est perdu : il devra donc au bout d'un certain temps ré-envoyer une opérande (n'importe laquelle), pour provoquer de Serge le ré-envoi du résultat...

On souhaite écrire un serveur ServerIntSumUDP pour ce service avec un constructeur, une méthode serve() et un main().

Test du serveur

Pour vérifier que votre serveur se comporte bien comme attendu, récupérer le client suivant ValidatorIntSumUDP.jar. Ce client teste le serveur en simulant plusieurs clients.

Dans trois consoles (shell) différentes, simultanément visibles à l'écran, exécutez les trois applications suivantes :

    $ java fr.uge.net.udp.ServerIntSumUDP 7777
    $ java -jar UDPProxy.jar 6666 localhost 7777
    $ java -jar ValidatorIntSumUDP.jar localhost 6666

Si tout se passe correctement, vous devez avoir l'affichage suivant :

    Trying 5 queries one after the other.
    Query 1 succeeded!
    Query 2 succeeded!
    Query 3 succeeded!
    Query 4 succeeded!
    Query 5 succeeded!
    Trying 5 queries from the same client at the same time.
    Test passed
    Trying 5 clients at the same time.
    Test passed

Libération des ressources sur le serveur

Dans l'implémentation actuelle de vos serveurs, les données relatives à toutes les requêtes sont conservées indéfiniment. Cela pose un problème car tout au long de l'activité du serveur la mémoire qu'il utilise ne cesse de croître.

Question : Pourquoi ne peut-on pas libérer les ressources liées à une session avec un client après avoir envoyé le paquet contenant le résultat ?

Une première solution pour aider à libérer les ressources consiste à modifier le protocole pour permettre au client de signaler au serveur qu'il peut effacer les données relatives à une session. Pour cela on introduit deux nouveaux types de paquets.

Modifiez votre serveur pour qu'il puisse gérer ce protocole étendu. Pour tester, récupérer le client suivant ClientFreeIntSumUDP.jar. Ce client teste le serveur en simulant plusieurs clients.

Dans trois consoles (shell) différentes, simultanément visibles à l'écran, exécutez les trois applications suivantes :

    $ java fr.uge.net.udp.ServerFreeIntSumUDP 7777
    $ java -jar UDPProxy.jar 6666 localhost 7777
    $ java -jar ClientFreeIntSumUDP.jar localhost 6666

Cette mesure n'est à elle seule pas suffisante. On ne doit jamais supposer que les clients respectent le protocole. Il faut donc en plus donner une durée de vie limitée à chaque session. Mettez en place cette solution sur votre serveur.