Examen 2h sur machine

Consignes

L'examen est composé de trois exercices indépendants qui peuvent être traités dans l'ordre que vous voulez.

A titre indicatif, le premier exercice sera noté sur environ 9 points, le deuxième exercice 6 points et le troisième sur 5 points. Ce barème est fournit à titre indicatif et est susceptible d'évoluer. Il vous est donné pour vous permettre de répartir votre temps.


Rappel : vous devez créer un projet Java dont les sources seront stockées dans répertoire EXAM présent dans le home de votre session de TP noté (au lieu d'être dans le workspace d'Eclipse comme c'est le cas par défaut).

Vérifier bien que tous vos fichiers sont dans le répertoire EXAM. Tout ce qui n'est pas dans ce répertoire est perdu quand vous vous déconnectez (ou en cas de panne).

La Java doc est ici.

Client UDP bloquant Pokemon

Le but de l'exercice est de réaliser un client UDP bloquant pour protocole Pokemon décrit ci-dessous.

Le protocole Pokemon

Le protocole Pokemon permet de récupérer les caractéristiques d'un Pokemon comme la vitesse ou la force qui sont toutes des valeurs numériques. Les Pokemons peuvent avoir un nombre quelconque de caractéristiques. Pour demander les informations sur un Pokemon, on envoie un paquet contenant :
  • un INT en BigEndian qui donne le nombre d'octets du nom du Pokemon encodé en UTF8,
  • suivi des octets du nom du Pokemon encodé en UTF8.
Le paquet envoyé au serveur ne peut pas dépasser 1024 octets.

Le serveur répond avec un paquet contenant :
  • les octets du nom du Pokemon encodé en UTF8,
  • un octet valant 0,
  • suivi, pour chaque caractéristique :
    • des octets du nom de la caractéristique encodé en UTF8,
    • d'un octet valant 0,
    • d'un INT en BigEndian donnant la valeur de la cette caractéristique.
Le paquet reçu ne dépassera pas 2048 octets. On supposera, sans le vérifier, qu'aucun des noms des Pokemons et des noms de caractéristique ne contient d'octet valant 0 une fois encodé en UTF8.

Par exemple, si un client veut des informations sur le Pokemon "Sylveon" :

  00 00 00 07 53 79 6C 76 65 6F 6E
  

Comme ce pokemon a les caractéristique suivantes : Heroism=18, Sp€€d=20 et Charm=0, le serveur répondra avec le paquet suivant :

  53 79 6C 76 65 6F 6E 00 48 65 72 6F 69 73 6D 00 00 00 00 12 53 70 E2 82 AC E2 82 AC 64 00 00 00 00 14 43 68 61 72 6D 00 00 00 00 00
  
qui se décompose comme suit :
    53 79 6C 76 65 6F 6E // Sylveon en UTF8
    00                   // octet valant 0
    48 65 72 6F 69 73 6D  // Heroism en UTF8
    00                    // octet valant 0
    00 00 00 12           // 18 : Int en BigEndian
    53 70 E2 82 AC E2 82 AC 64 // Sp€€d en UTF8
    00                    // octet valent 0
    00 00 00 14           // 20 : Int en BigEndian
    43 68 61 72 6D        // Charm en UTF8
    00                    // octet valant 0
    00 00 00 00           // 0 : Int en BigEndian
  

Dans un premier temps, on va supposer qu'aucun paquet ne peut être perdu ou retardé et vous implémenterez un client qui fonctionne dans ce cas là pour le protocole Pokemon. Ce client lira une liste de noms de Pokemons dans un fichier et demandera, pour chacun, ses caractéristiques à un serveur implémentant le protocole Pokemon. Il écrira les résultats dans un fichier. Si le paquet correspondant à un Pokemon dépasse la taille maximum du protocole Pokemon, ce Pokemon est simplement ignoré.

Plus précisément, le client prendra en paramètres sur la ligne de commande les informations suivantes :

  • le chemin filein vers un fichier contenant une liste de Pokemons. Il y a un nom de Pokemon par ligne ;
  • le chemin fileout du fichier dans lequel le client écrira les résultats. Une ligne de ce fichier contiendra le nom du pokemon puis les différentes caractéristiques et leurs valeurs ;
  • l'adresse du serveur Pokemon utilisé ;
  • le numéro de port du serveur Pokemon utilisé.

Dans une classe ClientPokemon, écrire un client UDP bloquant comme décrit ci-dessus.
Vous pouvez utiliser la trame ci-après comme point de départ : ClientPokemon.java. La trame contient le code qui parse le fichier d'entrée en une List<String>. Elle contient un record Pokemon(String name, Map<String,Integer> characteristics) et le code nécessaire pour écrire une List<Pokemon> dans un fichier au format demandé.

Vous testerez votre client en utilisant le serveur fourni ServerPokemon.jar qui se démarre en fournissant le numéro de port d'écoute :

% java -jar ServerPokemon.jar 7777

Vous pouvez testez votre client avec cette liste de Pokemons : pokemons.txt.

Si vous lancez votre client avec cette liste :

% java fr.uge.net.udp.exam2223.ex1.ClientPokemon pokemons.txt pokemons-out.txt localhost 7777

vous devriez obtenir une fichier pokemons-out.txt contenant :

Charizard;Gluttony:12;Heroism:7;Stealth:20
Pikachu;Heroism:19;Gluttony:17
Gardevoir;Heroism:12;Str€ngth:2;Gluttony:1;Mon$y:20;Stealth:11
Sylveon;Sp€€d:20;Heroism:18;Charm:0
Lucario;Loneliness:20
Gengar;Charm:16;Heroism:1;Gluttony:2;Mon$y:10;Stealth:15
Umbreon;Charm:3;Str€ngth:8;Sp€€d:3;Heroism:11;Stealth:11
Garchomp;Str€ngth:2
Mimikyu;Mon$y:12;Stealth:3;Charm:10;Str€ngth:4
Rayquaza;Gluttony:2;Mon$y:18;Sp€€d:20;Str€ngth:2;Heroism:20;Stealth:16
Greninja;Mon$y:18;Stealth:17;Sp€€d:15;Str€ngth:17

Les caractéristiques peuvent apparaître sur la ligne dans un ordre différent. Ce n'est pas grave du moment que les valeurs sont les mêmes.

ClientPokemon tolérant aux pertes de paquets

On veut maintenant prendre en compte le fait que des paquets UDP peuvent être perdus entre le client et le serveur, dans un sens comme dans l'autre. Les identifiants de requêtes vont donc devoir être utilisés par le client pour identifier à quelle requête correspond un paquet reçu.

L'attente en réception d'un paquet ne doit pas dépasser TIMEOUT millisecondes. Attention, la contrainte est moins forte que pour les TPs, nous n'exigeons pas qu'en cas de perte ou de retard, la question soit reposée exactement après TIMEOUT millisecondes. Le TIMEOUT sera une constante de votre client qui vaudra 300.

Recopiez votre classe ClientPokemon dans une nouvelle classe ClientPokemonFull que vous allez faire évoluer pour tenir compte des pertes de paquets.

Récupérez le jar UDPProxy.jar et démarrez-le entre votre client et le serveur. Dans trois fenêtres de terminal différentes, exécutez :

% java -jar ServerPokemon.jar 4545 
$ java -jar UDPProxy.jar 7777 localhost 4545 -no-swap
$ java fr.uge.net.udp.exam2223.ex1.ClientPokemonFull pokemons.txt pokemons-out.txt localhost 7777

et vérifiez que le fichier pokemons-out.txt produit est correct.

Serveur bloquant UDP Chat

Dans cet exercice, on cherche à réaliser un serveur bloquant pour le protocole Chat décrit ci-après. Le protocole fournit un service extrêmement rudimentaire de messagerie par UDP.

Protocole Chat

Dans le protocole Chat, les clients choisissent un pseudonyme et envoient par le serveur des messages à d'autres clients en donnant leur pseudonyme.

Plus précisément,les clients envoient des paquets qui contiennent 3 chaînes de caractères :

  +--------------------------------------------------------------------+
  | login_sender (STRING) | login_receiver (STRING) | message (STRING) |
  +--------------------------------------------------------------------+
  

Chaque chaîne str est encodée de la manière suivante :

  • un INT en BigEndian qui donne le nombre d'octets de la chaîne str encodée en UTF8,
  • suivi des octets de la chaîne str encodée en UTF8.

Le chaîne login_sender correspond au pseudonyme de l’expéditeur, login_receiver correspond au pseudonyme du destinataire et la chaîne message est le message que l'on veut envoyer.

La taille maximale d'un paquet ne peut pas excéder 2048 octets.

Le serveur va associer aux adresses des clients des identifiants de la manière suivante. Lorsque le serveur reçoit un paquet d'un client, si c'est la première fois que le serveur reçoit un paquet d'un client, il attribue à ce client l'identifiant login_sender qui se trouve dans le paquet.
Sinon, si cet identifiant est déjà attribué à un autre client, il ignore le paquet (s'il ne contient pas le même pseudonyme dans le champs login_sender.
Enfin, s'il a un client dont l'identifiant est login_receiver, il lui envoie le paquet contenant le login_sender de l'expéditeur suivi du message :

  +------------------------------------------+
  | login_sender (STRING) | message (STRING) |
  +------------------------------------------+
  

Par exemple supposons que le serveur vient de démarrer, il n'a reçu aucun paquet. Un client qui prend le pseudonyme Alice veut envoyer au client Bob le message Hello€. Il envoie au serveur le paquet :

    00 00 00 05 41 6C 69 63 65 00 00 00 03 42 6F 62 00 00 00 08 48 65 6C 6C 6F E2 82 AC
  
qui se décompose comme suit :
    00 00 00 05 // 5 : la taille de la chaîne Alice encodée en UTF-8
    41 6C 69 63 65 // la chaîne Alice encodée en UTF-8 
    00 00 00 03 // 3 : la taille de la chaîne Bob encodée en UTF-8
    42 6F 62 // la chaîne Bob encodée en UTF-8 
    00 00 00 08 // 8 : la taille de la chaîne Hello€ encodée en UTF-8
    48 65 6C 6C 6F E2 82 AC // la chaîne Hello€ encodée en UTF-8
  

Quand le serveur reçoit ce paquet, il va associer l'adresse d'Alice et le pseudonyme Alice. Comme il ne connaît pas de client pour le pseudonyme Bob, il ignore le paquet.

Maintenant un client ayant le pseudonyme Bob envoie le message Hello pour Alice, en envoyant le paquet suivant au serveur :

    00 00 00 03 42 6F 62 00 00 00 05 41 6C 69 63 65 00 00 00 05 48 65 6C 6C 6F
  
qui se décompose comme suit :
    00 00 00 03 // 3 : la taille de la chaîne Bob encodée en UTF-8
    42 6F 62 // la chaîne Bob encodée en UTF-8 
    00 00 00 05 // 5 : la taille de la chaîne Alice encodée en UTF-8
    41 6C 69 63 65 // la chaîne Alice encodée en UTF-8 
    00 00 00 05 //  5 : la taille de la chaîne Hello encodée en UTF-8
    48 65 6C 6C 6F // la chaîne Hello encodée en UTF-8
  
Le serveur va associer l'adresse de Bob et le pseudonyme Bob. Il va ensuite renvoyer à l'adresse d'Alice (qu'il connaît), le message suivant :
    00 00 00 03 42 6F 62 00 00 00 05 48 65 6C 6C 6F
  
qui se décompose comme suit :
    00 00 00 03 // 3 : la taille de la chaîne Bob encodée en UTF-8
    42 6F 62 // la chaîne Bob encodée en UTF-8 
    00 00 00 05 //  5 : la taille de la chaîne Hello encodée en UTF-8
    48 65 6C 6C 6F // la chaîne Hello encodée en UTF-8
  

Dans une classe ServerChat, écrire un serveur UDP bloquant pour le protocole Chat.
Vous pouvez utiliser la trame ci-après comme point de départ : ServerChat.java.

Si vous lancez votre serveur comme ci-dessous :

% java fr.uge.net.udp.exam2223.ex2.ServerChat 7777

Vous pouvez le tester avec le client ClientChatUDP.jar. Ce client prend en paramètre l'adresse du serveur et le pseudonyme du client.

Comme dans l'exemple ci-dessus, commencez par envoyer un message pour Bob venant d'Alice puis dans un autre terminal, envoyez un message de Bob à Alice. Vous devriez observer un comportement du type suivant.

% java -jar ClientChatUDP.jar localhost 7777 Alice
Please enter the message you want to send in the format loginReceiver:message
Bob:Hello€
févr. 07, 2023 9:34:50 AM fr.upem.net.udp.exam2223.ex2.ClientChatUDP listener
INFO: Received 16 bytes from /127.0.0.1:7777
févr. 07, 2023 9:34:50 AM fr.upem.net.udp.exam2223.ex2.ClientChatUDP getUTF8String
INFO: Size of the STRING : 3
févr. 07, 2023 9:34:50 AM fr.upem.net.udp.exam2223.ex2.ClientChatUDP getUTF8String
INFO: Extracted string : Bob
févr. 07, 2023 9:34:50 AM fr.upem.net.udp.exam2223.ex2.ClientChatUDP getUTF8String
INFO: Size of the STRING : 5
févr. 07, 2023 9:34:50 AM fr.upem.net.udp.exam2223.ex2.ClientChatUDP getUTF8String
INFO: Extracted string : Hello
Receive message "Hello" from Bob
% java -jar ClientChatUDP.jar localhost 7777 Bob
Please enter the message you want to send in the format loginReceiver:message
Alice:Hello

Serveur Slice non-bloquant

Dans cet exercice, vous devez écrire un serveur UDP en mode non-bloquant qui implémente le protocole très simple décrit ci-après.

Protocole Slice

Le client envoie un paquet qui contient entre 1 et 128 long en BigEndian. Quand le serveur reçoit un de ces paquets, il renvoie plusieurs paquets contenant chacun un des longs en BigEndian contenus dans le paquet qu'il a reçu.

Si le serveur reçoit le paquet :

    +-------------------------------------------------+
    | 00 00 00 00 00 00 00 05 00 00 00 00 00 00 00 03 |
    +-------------------------------------------------+
qui contient les longs 5 et 3...

... le serveur enverra au client les deux paquets suivants :

    +-------------------------+
    | 00 00 00 00 00 00 00 05 |
    +-------------------------+

    +-------------------------+
    | 00 00 00 00 00 00 00 03 |
    +-------------------------+
qui correspondent aux deux longs envoyés.

À partir du squelette de la classe ServerSlice.java, écrivez un serveur en mode non-bloquant pour ce protocole.

Pour tester votre serveur, vous pouvez utiliser le client ClientSlice.jar. Ce client prend en arguments l'adresse et le port du serveur. Il demande d'entrer au clavier les longs à envoyer au serveur séparés par des points virgules. Il affiche les réponses reçues par le serveur.

Par exemple, si votre serveur est lancé sur le port 7777 :

$java fr.uge.net.udp.exam2223.ex3.ServerSlice 7777 

Vous pouvez le tester depuis un autre terminal (en gras, ce que l'utilisateur saisit sur l'entrée standard) :

$java -jar ClientSlice.jar localhost 7777
Please enter your packet in the format long1;long2;...;longN:
5;3
Sending the packet for [5, 3]
Please enter your packet in the format long1;long2;...;longN:
févr. 06, 2023 2:14:14 PM fr.upem.net.udp.exam2223.ex3.ClientSlice listener
INFO: Received 8 bytes from /127.0.0.1:7777
Received packet [5]
févr. 06, 2023 2:14:14 PM fr.upem.net.udp.exam2223.ex3.ClientSlice listener
INFO: Received 8 bytes from /127.0.0.1:7777
Received packet [3]