:: Enseignements :: Master :: M1 :: 2013-2014 :: Programmation d'applications réseaux ::
![[LOGO]](http://igm.univ-mlv.fr/ens/resources/mlv.png) |
Clients et serveurs TCP, implémentations de la concurrence
|
Exercice 1 - TCP Longue Somme de Long
Récupérez le serveur
TCPLongSumServer.jar, qui prend
en argument le nombre de clients qu'il est capable de servir simultanément, le port
sur lequel il attend les connexions de clients. Par exemple, lancez-le sur votre machine par
% java -jar TCPLongSumServer.jar 5 7777
Ce serveur lit des
long en BigEndian et une fois le flux (d'entrée)
fermé par le client, il écrit un long en BigEndian qui correspond à la somme
de tous les
long reçus.
Développez un client, TCPLongSumClient, qui prend en argument
l'adresse et le port du serveur. Ce client lit des long
au clavier qu'il envoie au serveur jusqu'à ce qu'il lise la valeur 0.
Lorsque cette valeur 0 est lue au clavier, le client ferme la sortie
du flux en direction du serveur et affiche la réponse qu'il reçoit en retour.
Vous utiliserez les méthodes longToByteArray et ByteArrayToLong des TD précédents.
Lancez un serveur TCPLongSumServer qui ne peut traiter qu'un seul client à la fois et lancez deux clients. Expliquez ce que vous observez.
Exercice 2 - Client TCP simple
Écrire un client TCP simple (
SimpleTCPClient) qui,
après s'être connecté à un serveur TCP, réalise de manière itérative
les opérations suivantes:
-
lit une ligne de texte (chaîne de caractères terminée par une fin de ligne)
depuis l'entrée standard;
-
écrit cette ligne de texte (dans un encodage pris en paramètre sur la ligne commande) sur la connexion TCP à destination du serveur en la terminant par une fin de ligne;
-
lit une ligne de texte (chaîne de caractères terminée par une fin de ligne)
depuis la connexion TCP (réponse en provenance du serveur);
-
affiche cette ligne de texte sur la sortie standard en la terminant par une fin de ligne.
Ce client doit être démarré en spécifiant le nom (ou l'adresse IP) du serveur et le port auquel
il doit se connecter ainsi que l'encodage qu'il doit utiliser
pour échanger les chaînes de caractères avec serveur. Par exemple:
% java fr.upem.tcp.SimpleTCPClient localhost 7777 UTF-8
ou encore
% java fr.upem.tcp.SimpleTCPClient gaspard.univ-mlv.fr 7 ASCII
Pour tester,
gaspard.univ-mlv.fr implémente sur son port 7 un service Echo,
mais vous pouvez également installer sur votre propre machine le petit serveur
TCPUpperCaseServer.jar, qui prend
en argument le nombre de client qu'il est capable de servir simultanément, le port
sur lequel il attend les connexions de clients et l'encodage des chaînes de caractères
qu'il utilise. Par exemple, pour tester votre client, lancez-le sur votre machine par
% java -jar TCPUpperCaseServer.jar 5 7777 utf-8
Ce serveur,
TCPUpperCaseServer.jar, accepte une option
-slow qui
simule un temps de traitement par le serveur.
En relancant ce serveur comme ceci:
% java -jar TCPUpperCaseServer.jar -slow 5 7777 utf-8
quels sont les problèmes que pose votre client
SimpleTCPClient?
Rencontrez vous les mêmes problèmes avec netcat (
nc localhost 7777)?
Développez un nouveau client, TCPClient, qui permet de palier ces inconvénients:
il doit permettre à tout moment d'envoyer une ligne au serveur, et simultanément
à tout moment d'afficher une ligne en provenance du serveur, et ce de manière décorrélée.
On veut que le client s'arrête lorsque la ligne lue se termine par un point.
Attention, on ne veut pas que le client s'arrête avant d'avoir lu toutes les données envoyées par le serveur.
Lorsque le serveur ferme la socket (s'il meurt par exemple), comment pouvez
vous arrêter d'écouter sur l'entrée standard (clavier) du client?
Exercice 3 - Serveur de mise en majuscules itératif
On souhaite maintenant écrire notre propre classe TCPUpperCaseServer qui
implante un serveur TCP qui renvoit les lignes aux clients après les avoir
mis en majuscule.
Ce serveur crée une socket serveur (objet de la classe
java.net.ServerSocket), sur un port spécifié sur la ligne de commande,
puis est "démarré". Il attend alors une connexion d'un client, via la méthode
accept() appelée sur l'objet ServerSocket.
Lorsqu'un client contacte le serveur, la méthode accept()
du serveur retourne un objet de la classe Socket.
Celle-ci est alors dite socket de service. Le serveur peut ainsi satisfaire
la ou les requêtes successives émises par le client sur la socket de service.
Cette connexion peut être fermée par le client, ou par le serveur s'il reçoit
une ligne se terminant par un point ('.').
Dans un premier temps, vous écrirez une méthode launchIterative()
qui implante un serveur itératif: lorsque les différentes requêtes du premier client
sont traitées, la socket de service peut être fermée, et une nouvelle
connexion peut être élue comme socket de service par un nouvel appel à accept()
sur l'objet ServerSocket. Le squelete de votre classe de serveur pourrait être
le suivant:
Le principe du serveur
TCPUpperCaseServer, pour le traitement des
requêtes d'une socket de service qu'il vient d'accepter, est le suivant :
-
Les données doivent être envoyées par le client ligne par ligne, et une
ligne terminée par un point ('.') termine la session.
Pour chaque ligne reçue par le client, le serveur la met en majuscules et la
renvoie au client (le serveur doit utiliser l'encodage de caractères
spécifié sur la ligne de commande, comme le jar executable du sujet précédent).
-
Si la connexion reste trop longtemps ouverte sans activité, elle est fermée par le
serveur.
Que se passe-t-il si deux clients tentent d'accéder au service simultanément?
Est-ce que le comportement est le même qu'en UDP?
Exercice 4 - Serveurs de mise en majuscules concurrents
On veut maintenant offrir d'autres méthodes que launchIterative(),
qui permettent à plusieurs clients d'être servis simultanément. Pour cela,
nous allons devoir créer plusieurs threads.
-
La première méthode, launchConcurrentOnDemand(), va simplement
créer et démarrer une nouvelle thread à chaque nouveau client accepté.
Pour contrôler ce qui se passe au niveau de la JVM, vous allez "monitorer"
votre application en utilisant jconsole, et démarrer des clients
de plus en plus nombreux qui se connectent à votre serveur.
Que pensez vous de cette solution?
-
La seconde méthode, launchConcurrentOnDemandBounded(), doit en
plus limiter le nombre de threads créées à un paramètre passé sur la
ligne de commande (maxClient). Autrement dit, avant de démarrer
une nouvelle thread pour traiter un nouveau client, il faut vérifier que
le nombre de threads ne dépasse pas ce paramètre. Par ailleurs, lorsqu'un
client est terminé de servir et que sa thread s'arrête, il faut penser à
décrémenter le nombre de threads actives...
Implémentez cette solution et vérifiez son comportement avec jconsole.
Que pensez vous de cette solution?
-
Proposez une méthode shutdown() dans la classe du serveur
qui permette d'arrêter "proprement" un serveur démarré, qu'il soit
ou non en cours de traitement de clients. Les clients devront voir
leur connexion fermée.
-
On souhaite maintenant que les threads qui servent les clients ne
soient pas créées et détruites à chaque client, mais réutilisées.
On veut ici une implémentation à nombre de thread fixes (et pré-démarrées):
implantez une méthode launchConcurrentFixedPrestartedPool() qui
utilise des threads pré-démarrées et qui attendent en concurrence de pouvoir réaliser
une acceptation de nouveau client. Pour éviter que cette attente soit active, il suffit de
les exclure mutuellement grâce à une synchronisation sur la ServerSocket
au moment de faire le accept().
Vérifiez son comportement avec jconsole en testant avec de
multiples clients.
Que deviennent les clients qui tentent de se connecter lorsque
maxClient sont actuellement en train d'être servis?
Que pensez vous de cette solution?
Dans chacun des cas ci-dessus, comment pourriez vous arrêter "proprement" le serveur?
Plus exactement, si server est une instance de serveur qui
tourne, pris en charge par une thread t, comment pourriez vous
faire server.shutdown() depuis une autre thread et que cela
contraigne le serveur à s'arrêter?
© Université de Marne-la-Vallée