:: Enseignements :: ESIPE :: E4INFO :: 2010-2011 :: Applications réseaux ::
![[LOGO]](http://igm.univ-mlv.fr/ens/resources/mlv.png) |
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
-
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
$ $
-
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.
-
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.
- (*)
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).
- (**)
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
-
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
-
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
-
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.
- (*)
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
© Université de Marne-la-Vallée