Communications entre processus distants
Mise en oeuvre des sockets
(Interfaces de programmation C et Java)
B. Dupouy
On enverra le(s) fichier(s) source(s) traitant l'exercice et le makefile
permettant leur bonne utilisation à :
dupouy@enst
ou à
gadret@enst
Le texte du message d'envoi doit contenir la chaîne TPsock
SOMMAIRE
Pour accéder à la documentation Java :
Documentation sockets au format pdf : sockets(500 Koctets)
On remarquera sur le schéma suivant :
- sin_port, le numéro de port,
- sin_addr, le(s) adresses(s) Internet de la machine, numéro
attribué par l'administrateur système (un par carte Ethernet sur
la machine)
- le numéro Ethernet de la carte, numéro unique attribué
(matériel) par le constructeur,
Le modéle client/serveur est le canevas
sur lequel sont construites de nombreuses applications
distribuées.
Dans ce modéle, le serveur se contente d'attendre
les demandes de connexion des clients distants sur un port qualifié de port d'écoute.
La gestion de la
communication avec chaque client est prise en charge par un
nouveau processus fils.
Le serveur, libéré
de toutes les tâches de communication, se remet en attente
de demande de connexions émanant d'autres clients.
Schéma de principe.
Après fin de l'attente sur accept, le serveur
créé un processus fils, ce dernier fermera le socket
d'écoute, tandis que le serveur fermera celui de communications :
Le serveur se libère ainsi de la gestion des communications pour se
consacrer à l'écoute des demandes de connexions. Le nombre
de ces demandes en attente est borné par listen , le
nombre
de communications en parallèle étant, lui, limité
par le nombre de processus que le serveur peut créer :
Remarque :
-
la fonction socket ne fait qu'allouer des structures de
données, mais n'affecte pas de numéro de port. Ce numéro
de port est attribué de façon explicite lors du bind, ou de
façon transparente lors du connect.
Ce serveur affiche à l'écran les messages
émis par les clients en indiquant avec lequel de ses
fils ils communiquent.
A faire pour le serveur :
-
Cliquez
ici pour obtenir le programme serveur.
-
Après l'avoir modifié, vous vérifierez son fonctionnement en l'interrogeant
à l'aide de la commande telnet.
Si le serveur est arrêté et relancé peu
de temps après, il ne peut pas reprendre le même port
d'écoute. Pour pouvoir réutiliser immédiatement
ce port il faut utiliser la fonction
setsockopt
(après socket et avant bind) :
setsockopt(..., SOL_SOCKET, SO_REUSEADDR, ... , ... )
ATTENTION :
Sur une machine "System V" (par exemple, sur Solaris), ajouter les options
suivantes lors de la compilation :
-lsocket (pour charger la bibliothèque des sockets)
-lnsl (pour charger les utilitaires htons...)
A faire pour le client :
-
Cliquez
ici .
pour obtenir le programme client.
-
Vérifier que ce client transmet bien au serveur les informations
qu'on lui a données en utilisant le clavier.
-
On peut aussi rediriger stdin (on suppose que le fichier exécutable
s'appelle Client_TCP) :
:
ls -il | Client_TCP ... ...
cat *.c | Client_TCP ... ...
man ls | Client_TCP ... ...
A faire :
-
Faire interroger simultanément le serveur par plusieurs clients
situés sur des machines différentes, ceci pour en vérifier
le fonctionnement parallèle
-
Dans l'exemple proposé pour le serveur, le signal SIGCHLD
n'est pas
géré. La commande ps permettra
de voir les zombies résultant de la terminaison des
processus fils du serveur.
-
Utiliser la commande netstat :
-
sur Solaris : netstat -P tcp -f inet
- sur Linux-Fedora : netstat -t --inet
pour tracer les communications entre
clients et serveur. Observer les autres communications entrantes et
sortantes.
La fonction select permet à une application
de se mettre à
l'écoute sur
plusieurs sources
simultanément, c'est à dire de multiplexer les
entrées-sorties.
Nous allons étudier le fonctionnement d'un serveur
utilisant select. Il attendra des entrées depuis le clavier et
depuis quelques ports UDP.
Pour avoir les détails du mécanisme de select,
utiliser la commande :
man -s 3c select
Pour comprendre le mécanisme de cet appel système, on va
compléter le programme du client et du
serveur dont on donne les squelettes ci-dessous.
Comme le serveur TCP pécédent, ce serveur UDP
se contente d'afficher à l'écran les messages
émis par des clients UDP.
Cliquez ici pour consulter
le serveur
Ce client envoie périodiquement des datagrammes vers
un destinataire dont on lui a passé l'adresse IP et le
numéro de port sur la ligne de commande.
Cliquez ici pour consulter
le client
Travail à faire :
-
Compléter les programmes serveur et client : voir les lignes
où se trouvent des .... .
-
Lancer le serveur, vérifier qu'il fait bien l'écho
de ce qui est frappé au clavier
-
Lancer un, puis plusieurs clients et vérifier que le serveur
a bien reçu les datagrammes émis par les clients,
Le serveur que nous allons construire attend des entrées depuis
:
-
le clavier,
-
un ou plusieurs ports TCP,
Voici son fonctionnement :
- si on frappe un texte au clavier, il est affiché dans la
fenêtre où tourne le serveur,
-
si il reçoit des messages sur l'un des ports TCP, il les
affiche à l'écran et les renvoie à
l'émetteur avec un texte d'identification.
L'utilisation des threads s'impose pour gérer les attentes
simultanées sur ces différentes sources d'information.
Récupérer les trois fichiers :
Serv_Mux.java,
Serv_Clav.java,
Serv_TCP.java.
Serv_Mux.java permet de lancer plusieurs threads :
- le premier de ces threads scrute le clavier, son code est
dans Serv_Clav.java,
- d'autres, dont le nombre est donné sur la ligne de
commande, vont écouter sur des ports TCP
(code dans Serv_TCP.java),
- les derniers, de type Thread_Standard
(dans Serv_Mux.java),
se terminent au bout d'un temps aléatoire. Ils ne sont pas utiles
pour la gestion des communications, ils seront le prétexte à
un exercice de synchronisation (cf. ici ).
Travail à faire :
- Lancer Serv_Mux, constater qu'il fait bien l'écho de ce qu'on
lui entre au clavier.
- Voir les threads de type Thread_Standard
se terminer les uns apres les autres.
- Interroger maintenant le serveur avec un client telnet
(telnet nom-de-machine port) lancé
depuis
une autre fenêtre et ...
... vérifier que le serveur ne répond pas !!!
- Compléter le code de Serv_TCP pour qu'il réponde
à un client telnet.
Les lignes à modifier sont repérées par :
// MODIF A FAIRE
- Interroger ce serveur modifié avec un client telnet
: il doit répondre par un écho du texte donné
à
telnet.
On va maintenant ajouter une écoute sur un port UDP.
Pour ce faire, on utilisera ce serveur
UDP qui attend des messages sur le port 6666 et renvoie
la date à l'émetteur.
Travail à faire :
-
Modifier Serv_Mux.java pour mettre ce serveur UDP dans le
tableau des threads créés.
-
Relancer Serv_Mux modifié. Il écoute ainsi sur le clavier, des ports TCP et un port UDP.
-
Pour tester le serveur UDP, utiliser le client UDP écrit en C
de l'exercice du paragraphe III.2 .
Lancé depuis une autre machine,
on lui fait envoyer des datagrammes sur le port 6666.
-
Arrêter Serv_Mux.
-
Modifier ainsi le serveur UDP :
Il renvoie toujours le datagramme reçu sur la machine émettrice,
mais sur le port 12000.
-
Vérifier le bon fonctionnement de l'application :
-
Relancer Serv_Mux ainsi modifié
-
Lancer alors le serveur C utilisant select, sur la même
machine que le client UDP, en lui demandant d'attendre sur le port
12000.
-
On doit constater que le message envoyé par le client UDP-C
réveille le serveur UDP-Java qui envoie alors la date
vers le serveur UDP-C sur le port 12000.
Exemple de traces.
Trace produite par le client UDP-C :
Envoi de datagramme (Message emis par pid 7592 sur bajazet.enst.fr) vers ribouldingue
Trace produite par le serveur UDP-Java :
Serv_UDP-6666 va emettre : Tue Oct 02 16:45:29 GMT+0:00 2001 (taille : 33)
Trace produite par le serveur UDP-C :
(33) Tue Oct 02 16:45:39 GMT+0:00 2001
Remarque :
Ce programme Serv_Mux.java réalise l'équivalent d
e la commande select.
Dans le serveur TCP écrit en Java, on souhaite être averti
de la terminaison d'un thread standard dès qu'elle se produit.
Or, ce
n'est pas le cas puisque join impose un ordre sur les réeceptions
d'événement de fin des threads :
vous avez sans doute constaté que main n'imprime
pas de message lors de la fin d'un thread standard.
Pour résoudre le problème, on va créer un outil
de synchronisation qui propose les méthodes Attendre
et Debloquer (qui utilisent respectivement wait et
notify :
-
Attendre est appelée par main pour attendre
la fin d'un thread standard quelconque;
-
Debloquer est appelée par un thread standard pour
réveiller main.
Informations :
-
Voir le TP Java concurrence
ici .
-
Vous trouverez
ici (500 Koctets)
une documentation au format pdf (API POSIX puis API Java).
A faire :
-
D'après l'exemple sur wait et
notify figurant en fin de la documentation pdf,
créer l'objet de synchronisation et ajouter
les appels à ses méthodes dans main et les threads
standards.
-
Vérifier que main est bien débloqué
par la fin d'un thread standard dès qu'elle arrive.
©(Copyright)
dupouy@inf.enst.fr