:: Enseignements :: ESIPE :: E4INFO :: 2010-2011 :: Applications réseaux ::
[LOGO]

Toronto... soon!


Le but de cet TP noté est d'écrire quelques classes simples permettant de vérifier que vous maitrisez la mise en oeuvre des mécanismes de bases pour l'émission et la réception en UDP et en TCP, avec les entrées sorties dites "classiques".
Ce TP noté doit se faire sous eclipse, dans un projet Java de nom TPNOTE que vous créerez dans le workspace de votre compte ($HOME) (qui est vierge dans l'environnement de TP noté), ou à défaut dans un répertoire de nom TPNOTE que vous pourrez créer à la racine de votre compte.
Tout document électronique autre que ce sujet est proscrit. Vous pouvez consulter la javadoc à travers eclipse.

Les deux exercices sont complètement indépendants et peuvent être traités dans l'ordre de votre choix.
Les questions qui ont une (*) ou deux (**) étoiles sont plus difficiles; vous pouvez les garder pour la fin...

Exercice 1 - Un serveur TCP pour tomber juste

  1. Pour commencer, on souhaite définir un serveur itératif (qui accepte les clients l'un après l'autre) très simple: il dispose d'un compteur (de type int) initialisé à 0 au démarrage du serveur. Lorsqu'un client se connecte à ce serveur, chaque octet b lu par le serveur en provenance du client (il les traite un par un même s'il en lit plusieurs à la fois) est considéré comme un entier non signé et est ajouté au compteur:
    • si la somme obtenue dans le compteur est inférieure à 255, rien d'autre ne se passe;
    • si la somme est supérieure ou égale à 255, le compteur (count) est affecté à la valeur (count+b)%255;
    • si la somme est exactement égale à 255 alors le serveur s'arrête.
    Rappel: pour voir un byte b comme un entier non signé, il faut le convertir en entier et ne conserver que les 8 bits de poids faible: (b & 0xFF).
    Pour l'instant, le serveur ne renvoie jamais rien au client, et passe au client suivant quand le client actuel a fermé sa connexion (itératif).
    Écrire dans un paquetage de nom fr.umlv.ir2 une classe IterativeSumTCPServer qui réalise ces exigences, avec une méthode main, un constructeur et une méthode launch. On peut démarrer ce serveur par une ligne de commande de la forme suivante (à gauche), tandis qu'on peut tester (grossièrement, comme à droite) ce serveur avec netcat, qui enverra sur la connexion le code ASCII (étant un sous-ensemble d'UTF-8 si vous n'utilisez pas de caractères exotiques) des caractères qu'on saisit au clavier ('A'=65, 'F'=70, retour chariot=10, etc...). Dans cet exemple, le premier client fait augmenter le compteur jusqu'à ce qu'il dépasse 255 et atteigne la valeur 15, puis 25. Ensuite, ce premier client se déconnecte (en fermant le flot par Ctrl-D) et un second se connecte et fait à son tour grimper le compteur jusqu'à ce qu'il atteigne exactement 255. A ce moment, le serveur s'arrête.
    $ java fr.umlv.ir2.IterativeSumTCPServer 7777        $ nc localhost 7777
                                                         AAA
    65
    130
    195
    205                                                  A
    15
    25                                                   Ctrl-D
                                                         $ nc localhost 7777
                                                         F
    95
    105
                                                         FF
    175
    245
    255
    $                                                    $ 
    
  2. Recopiez ce qui peut vous servir de code dans une nouvelle classe fr.umlv.ir2.ConcurrentSumTCPServer, dans laquelle vous ferez les modifications nécessaires permettant au serveur de gérer simultanément jusqu'à maxThread clients en concurrence (valeur qui sera passée sur la ligne de commande et en second argument du constructeur -- vous pouvez essayer de tester avec maxThread = 2). Vous prendrez garde à assurer la cohérence de la valeur du compteur quel que soit le nombre de clients simultanés.
  3. On veut maintenant écrire un client (plus pratique) qui puisse envoyer directement sous la forme d'octets les valeurs qui sont saisies au clavier. Écrire une classe fr.umlv.ir2.KbdToBytesTCPClient qui lit des entiers au clavier; pour chaque entier lu, s'il est positif ou nul et inférieur ou égal à 255, il l'envoie sous la forme d'un octet à destination du serveur; s'il est en dehors de cet intervalle, le client lève une exception.
  4. (*) Dans le cas où l'octet reçu par le serveur a fait atteindre ou dépasser la valeur 255 au compteur, on veut maintenant que le serveur envoie au client la valeur de ce compteur après l'application du modulo. Cette fois, on veut que le serveur envoie la valeur du compteur sous la forme d'une chaine de caractères en ASCII terminée par une fin de ligne. Recopiez ce qui peut vous servir comme code dans une nouvelle classe fr.umlv.ir2.VerboseConcurrentSumTCPServer qui réalise ces fonctionalités; vous pourrez les tester avec netcat, car a priori votre client fr.umlv.ir2.KbdToBytesTCPClient n'est pas prévu pour lire de réponse (c'est la question suivante).
  5. (**) Dans le même esprit, réutilisez le code nécessaire pour créer une nouvelle classe fr.umlv.ir2.VerboseKbdToBytesTCPClient qui permette au client de continuer à remplir son rôle, mais puisse également afficher à l'écran la valeur du compteur lorsque le serveur lui envoie.
    Pour bien faire, ce client devrait fermer la connexion avec le client et s'arrêter tout seul (ne plus écouter le serveur) si il lit une fin de flot au clavier (Ctrl-D), mais il devrait également s'arrêter tout seul (ne plus écouter le clavier) si le serveur lui a envoyé 0 avant de fermer la connexion. L'usage de solution brutales, du type System.exit(0) est à proscrire ici.

Exercice 2 - Un serveur UDP qui accuse... réception

  1. Dans un premier temps, on souhaite écrire un serveur UDP qui écoute, sur un port passé en argument sur la ligne de commande, les paquets que les clients peuvent lui envoyer. Ce serveur est démarré avec un paramètre correspondant au nombre maximum d'octets qu'il peut reçevoir dans chaque paquet. Lorsqu'il recoit un paquet d'un client, ce serveur:
    • considère que les octets qu'il a reçus représentent une chaîne de caractères encodée en iso-8859-15 et les affiche sur la console du serveur;
    • accuse réception au client en lui envoyant un paquet contenant la chaîne de caractères constituée du nombre d'octets reçus, d'un espace et de la chaîne "bytes received", le tout encodé en iso-8859-15
    Dans le paquetage de nom fr.umlv.ir2, écrire une classe AckUDPServer avec une méthode main, un constructeur et une méthode launch qui réalise le service spécifié ci-dessus. On peut démarrer ce serveur par une ligne de commande de la forme suivante (le premier argument est le port d'écoute, et le second est le nombre maximal d'octets pouvant être pris en compte dans un paquet reçu, ici volontairement faible):
    java fr.umlv.ir2.AckUDPServer 7777 10
    
    On peut tester ce serveur avec un client netcat (mais le résultat dépendra de l'encodage en vigueur sur le client). Par exemple voici quelques échanges (ce qui est en gras est ce qui est saisi sur la console du client) sur un client UTF-8:
    $ nc -u localhost 7777
    Champs-sur-Marne
    10 bytes receivedToronto        
    8 bytes received€lan
    7 bytes received
    
    le serveur affiche dans le même temps sur sa console les chaînes construites avec les données reçues :
    Champs-sur
    Toronto
    
    €lan
    
    
  2. Pour résoudre les problèmes d'encodage, on veut maintenant écrire une classe fr.umlv.ir2.UDPClient qui, pour chaque ligne de texte saisie sur l'entrée standard, tente d'envoyer la représentation de cette chaîne en iso-8859-15 à l'adresse et au numéro de port passés sur la ligne de commande.
    Ainsi, avec le même serveur que précédemment et les mêmes chaînes saisies, le client devrait maintenant donner
    java fr.umlv.ir2.UDPClient localhost 7777 
    Champs-sur-Marne
    10 bytes received
    Toronto
    7 bytes received
    €lan
    4 bytes received
    
    Les chaînes affichées sur le serveur sont maintenant les suivantes:
    Champs-sur
    Toronto
    €lan
    
  3. Quand client et serveur sont sur la même machine, il n'y a pas beaucoup de risque de perdre des paquets. En revanche, si le serveur était à Champs sur Marne et le client à Toronto, certains paquets pourraient arriver en retard ou se perdre, qu'ils aillent du client au serveur où qu'ils contiennent l'acquittement.
    Pour simuler ce comportement, reprennez le contenu de la classe du serveur AckUDPServer pour créer une nouvelle classe RandomUDPServer qui ne répond alatoirement qu'à une partie des paquets reçus (on pourra utiliser une valeur paramétrable dans la classe, qui provoquera la perte de 50% des paquets pour cet exercice).
    A la réception de chaque paquet, le serveur tirera un nombre aléatoire (en utilisant par exemple la méthode nextDouble() de la classe java.util.Random) et décidera en fonction du taux de perte paramétré :
    • soit de répondre au paquet (comme avant);
    • soit de ne pas répondre. Dans ce cas, il affichera un message sur la console du serveur disant qu'il jette le paquet (pour faciliter la compréhension de ce qui se passe), et n'enverra rien au client.
    Pour tester ce serveur, vous devrez sûrement commencer par utiliser netcat, car il est très probable que votre fr.umlv.ir2.UDPClient attende indéfiniement un acquittement que le serveur a décidé de jeter.
  4. (*) Recopiez le code du client dans une nouvelle classe fr.umlv.ir2.AckUDPClient, qui doit maintenant tenter d'identifier les paquets qui ont été perdus (ou plus exactement "jetés" par le serveur), afin de tenter de les ré-envoyer. On pourra fixer une limite (paramétrable) de 3 tentatives d'envoi sans accusé de réception avant d'abandonner la tentative d'envoi de cette ligne et de passer à la ligne suivante.
    $ java fr.umlv.ir2.AckUDPClient localhost 7777    $ java fr.umlv.ir2.RandomUDPServer 7777 10
    abc        
                                                      abc
    3 bytes received 
    def
                                                      def
                                                      Lost Packet
                                                      def
                                                      Lost Packet
                                                      def
    3 bytes received 
    ghi
                                                      ghi
    3 bytes received 
    jklm
                                                      jklm
                                                      Lost Packet
                                                      jklm
                                                      Lost Packet
                                                      jklm
                                                      Lost Packet
    Arbort:-(
    no  
                                                      no
    2 bytes received