:: Enseignements :: Master :: M1 :: 2015-2016 :: Programmation d'applications réseaux ::
![[LOGO]](http://igm.univ-mlv.fr/ens/resources/mlv.png) | Serveurs UDP |
Nous avons vus différents clients pour des protocoles permettant d'échanger des informations,
assez simples au début (un seul paquet), puis plus complexes (une succession de paquets dans
l'ordre) et nous avons identifié plusieurs techniques permettant de fiabliliser ces échanges,
soit avec des identifiants de paquet dans les données échangées, soit dans l'implémentation
du client pour permettre de limiter l'attente ou bien de conserver une structure avec les
paquets pas encore acquittés.
Nous allons maintenant nous intéresser à un cas dans lequel la notion de "session" est encore plus
importante, et pour lequel les paquets ne devront être traités qu'une seule fois par le serveur.
Cela doit nous permettre de rencontrer des problématiques qui sont propres aux serveurs UDP.
Mais nous allons commencer par un serveur UDP "tout simple".
Exercice 1 - Serveur de mise en majuscule
Sachant que votre client ClientIdBetterUpperCaseUDP doit maintenant fonctionner
correctement, ce petit exercice va simplement consister à écrire un serveur,
MyServerIdBetterUpperCaseUDP, qui puisse réaliser les opérations de bases qui étaient
jusqu'alors prises en charge par le jar ServerIdBetterUpperCaseUDP.jar.
Ce serveur est très simple dans son fonctionnement. Il doit mettre un place une DatagramChannel
sur le port d'écoute spécifié lors de son lancement, et "servir" successivement en boucle
chaque requête reçue en:
- recevant un paquet d'un client (il faut conserver l'adresse et le numéro
de port de ce client pour pouvoir lui répondre);
- décoder dans ce paquet l'identifiant du paquet (et le conserver) et la chaîne de caractère reçue.
On rappelle que le format d'un paquet est le suivant:
-
Un long en BigEndian 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);
-
Un int en BigEndian correspondant à la longueur du nom de l'encodage
écrit en ASCII;
-
Le nom de l'encodage en ASCII;
-
Les octets de la chaîne encodée avec cet encodage.
- Produire la chaîne en majuscule à partir de la chaîne reçue (String.toUpperCase() fait cela très bien).
- Renvoyer au client un paquet contenant l'identifiant du paquet et la chaîne en majuscule encodée dans notre protocole.
Dans un premier temps, vous pourrez utiliser systématiquement de l'UTF-8 pour encoder la réponse,
ce qui correspond à une version simplifiée du serveur ServerIdBetterUpperCaseUDP.jar qui lui choisissait
aléatoirement un jeu de caractère pour encoder la réponse.
Contrairement au client, 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 la DatagramChannel,
est un champ qui identifie un objet "serveur" et on fournira pour la classe
MyServerIdBetterUpperCaseUDP
- un constructeur, qui prend en argument le numéro de port d'écoute du serveur;
- une méthode serve() qui place le serveur en attente de réception de requête de clients;
- un main() qui utilise les arguments sur la ligne de commande pour créer une instance de serveur
et le démarrer en appelant serve.
Vous pouvez utiliser la trame de serveur ci-après comme point de départ:
MyServerIdBetterUpperCaseUDP.java
Testez votre serveur avec un de vos clients développés et testés lors des séances précédentes.
$ java fr.upem.net.udp.MyServerIdBetterUpperCaseUDP 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
ClientIdBetterUpperCase.jar en guise de client (implémentation burst).
Exercice 2 - Serveur longue somme de long
L'objectif est dans cet exercice de réaliser un service de somme
d'entiers longs. Le client dispose d'une liste
d'opérandes (de type long) et il veut les envoyer au serveur qui
doit en faire la somme et la renvoyer au client. Plusieurs points de vigilance
(chaque opérande ne doit être comptée qu'une seule fois par le serveur, les
opérandes de deux sommes distinctes ne doivent pas être interverties, le serveur
a besoin de savoir s'il a reçu toutes les opérandes ou non avant d'envoyer la somme...)
conduisent au protocole suivant.
Chaque paquet débute par un code d'opération codé sur un octet qui vaut:
-
OP = 1 si le paquet contient une opérande envoyée par le client au serveur.
Dans ce cas, le paquet a le format suivant:
byte long long long long
+---+-----------+-----------+-----------+-----------+
| 1 | sessionID | idPosOper | totalOper | opValue | // paquet OP
+---+-----------+-----------+-----------+-----------+
où
-
sessionID est un identifiant de session unique choisi par le client et
commun à tous les échanges liés à une somme donnée,
-
idPosOper est la position de cette opérande dans la somme,
qui l'identifie (valeur comprise dans l'intervalle [0 .. totalOper-1[)
-
totalOper est le nombre total d'opérandes de cette somme,
- et enfin opValue est la valeur de l'opérande en question.
-
ACK = 2 si le paquet contient l'accusé de réception envoyé
par le serveur en réponse à un paquet OP.
Dans ce cas, le paquet a le format suivant:
byte long long long
+---+-----------+-----------+
| 2 | sessionID | idPosOper | // paquet ACK
+---+-----------+-----------+
où
-
sessionID est l'identifiant de session
- et idPosOper est la position de l'opérande acquittée
-
RES = 3 si le paquet contient la somme de toutes les opérandes.
Le serveur envoie un paquet RES en réponse à n'importe quel
paquet OP d'un client dès lors qu'il a reçu toutes les opérandes de
cette somme (session) -- cet envoi a lieu en plus du paquet ACK
acquittant le paquet OP reçu.
Dans ce cas, le paquet RES a le format suivant:
byte long long
+---+-----------+-----------+
| 3 | sessionID | sum | // paquet RES
+---+-----------+-----------+
où
-
sessionID est l'identifiant de session
- et sum est la somme de toutes les opérandes
de cette session
Note: Tous les enliers
long sont transmis en Big Endian.
On souhaite écrire un serveur ServerLongSum pour ce service avec
un constructeur, une méthode serve() et un main.
Exercice 3 - Validation de l'exercice précédent
Pour vérifier que votre serveur se comporte bien comme attendu,
récupérer le client suivant
ValidatorLongSumUDP.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.upem.net.udp.ServerLongSumUDP 7777
$ java -jar UDPProxy.jar 6666 localhost 7777
$ java -jar ValidatorLongSumUDP.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
Exercice 4 - 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 pemettre 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
ClientFreeLongSum.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.upem.net.udp.ServerFreeLongSumUDP 7777
$ java -jar UDPProxy.jar 6666 localhost 7777
$ java -jar ClientFreeLongSumUDP.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.
© Université de Marne-la-Vallée