TP9 – Révision
Pour faire les exercices, vous avez besoin de connaître le langage bash. Vous pouvez vous référer à l'annexe shell. Vous pouvez aussi trouver une liste d'astuces ici. Tous les exercices sont obligatoires, sauf les exercices notés « défi » ou « optionnel » qui sont optionnels. En particulier, les exercices notés « hors présentiel » sont supposés fait d'une séance sur la suivante.
Vous aurez aussi besoin des scripts P.sh et V.sh.
- revoir la plupart des notions étudiées dans le module.
Mini Facebook (∼ 3h)
Le but de cet exercice est de concevoir un mini serveur facebook.
La figure vous présente l'architecture globale
de l'application, n'hésitez pas à vous y référer tout au long de l'exercice
pour comprendre à quelle étape vous êtes.
Les différentes parties vous seront bien sûr expliquées au fur et à mesure.
Pour démarrer, il faut comprendre que
le script mini-facebook.sh met en œuvre le serveur
et qu'il utilise les scripts create.sh,
add-friend.sh, post-message.sh
et display-wall.sh.
Le client est mis en œuvre par le script client.sh,
et il communique avec le serveur via deux tubes nommés :
mini-facebook.pipe (partagé par tous les clients)
et clientid.pipe (un tube par client).
Mise en place des commandes
Dans un premier temps, nous allons créer l'ensemble des commandes permettant de manipuler l'état
du serveur.
Le serveur gère un ensemble d'utilisateurs. Un utilisateur d'identifiant $id possède
un répertoire $id contenant deux fichiers :
- un fichier nommé wall, contenant une suite de lignes. Chaque ligne est un message posté par un ami sous la forme identifiant-ami: message ;
- un fichier nommé friends, dans lequel chaque ligne contient l'identifiant d'un ami. Seul les amis d'un utilisateur peuvent poster des messages sur son mur.
- créer un utilisateur, c'est-à-dire créer le répertoire associé à un utilisateur et initialiser les fichiers de l'utilisateur,
- ajouter un ami à un utilisateur,
- poster un message sur le mur d'un ami,
- lire son mur.
Vous commencez par écrire un script create.sh id prenant un unique argument
$id et permettant de créer le répertoire et, dans cet ordre,
les fichiers wall puis friends de l'utilisateur d'identifiant
$id. En effectuant les actions dans cet ordre, vous pouvez noter que :
- si le répertoire $id existe, c'est que l'utilisateur est en cours de création,
- si le fichier $id/friends existe, c'est que l'utilisateur est entièrement créé.
- nok please provide an identifier si le script ne reçoit pas d'argument,
- nok user '$id' already exists si l'utilisateur est déjà en cours de création, c'est-à-dire si le répertoire $id existe déjà,
- ok sinon (n'oubliez pas d'afficher ok, cet affichage deviendra essentiel par la suite).
Seuls ces trois affichages sont autorisés.
Notez que chacun de ces affichages n'est constitué que d'une unique ligne.
Tout autre affichage finira
par engendrer des erreurs lorsque vous répondrez aux questions de la fin du TP.
On souhaite maintenant pouvoir ajouter un ami à la liste des amis d'un utilisateur
d'identifiant $id.
Votre script doit d'abord vérifier que l'utilisateur $id
existe, c'est-à-dire que le répertoire $id existe.
Ensuite, un ami, identifié par $friend, est aussi un utilisateur.
Avant d'ajouter l'utilisateur $friend à la liste d'amis
de l'utilisateur $id,
il faut donc vérifier que l'utilisateur $friend existe,
c'est-à-dire que le répertoire $friend existe.
Enfin, avant d'ajouter l'utilisateur $friend à la
liste d'amis de $id, il faut vérifier que l'utilisateur
$friend n'est pas déjà ami de l'utilisateur $id,
c'est-à-dire que la chaîne de caractère $friend n'est
pas présente dans le fichier d'amis $id/friends de $id.
Vous pourrez utiliser le code du retour de la commande grep pour détecter
la présence de la chaîne de caractères friend dans $id/friends.
Vous utiliserez par exemple if grep "^$friend$" "$id"/friends > /dev/null; then ... fi.
La redirection vers /dev/null est importante pour éviter de polluer
la sortie de add-friend.sh avec la sortie de grep,
ce qui deviendra essentiel par la suite.
Écrivez le script add-friend.sh id friend prenant les deux arguments
$id et $friend et ajoutant l'utilisateur
$friend à la liste d'amis de $id.
Votre script doit afficher sur le terminal :
- nok user '$id' does not exist si l'utilisateur $id n'a pas été entièrement créé, c'est-à-dire si le fichier $id/friends n'existe pas.
- nok user '$friend' does not exist si l'utilisateur $friend n'a pas été entièrement créé, c'est-à-dire si le fichier $friend/friends n'existe pas.
- nok user '$friend' is already a friend of '$id' si l'utilisateur $friend est déjà un ami de $id.
- ok sinon (n'oubliez pas cet affichage).
De la même façon, seuls ces affichages (sur une unique ligne) sont autorisés et il faut que
add-friend.sh ne puisse engendrer aucun autre affichage
(en particulier, pensez à rediriger la sortie de la commande grep vers /dev/null).
On souhaite maintenant pouvoir poster des messages. Écrivez un script post-message.sh sender receiver ...
prenant au moins trois arguments.
Les deux premiers arguments sont des utilisateurs :
$sender est l'émetteur du message et $receiver est le récepteur du message.
Les arguments suivant constituent le message.
Ajouter un message revient à ajouter la ligne $sender: ...message... dans le mur
de l'utilisateur $receiver, c'est-à-dire dans le fichier
$receiver/wall.
Votre script doit afficher sur le terminal :
- nok user '$sender' does not exist si l'utilisateur $sender n'a pas été entièrement créé (fichier $sender/friends n'existe pas),
- nok user '$receiver' does not exist si l'utilisateur $receiver n'a pas été entièrement crée,
- nok user '$sender' is not a friend of '$receiver' si l'utilisateur $sender n'est pas un ami de l'utilisateur $receiver ;
- ok sinon.
Comme précédemment, veillez à ne produire que les affichages (d'une unique ligne) décrits ici.
Pensez à utiliser la construction donnée avec grep à la question précédente.
On souhaite enfin pouvoir afficher le mur d'un utilisateur. Écrivez une commande display-wall.sh id
prenant l'identifiant $id en argument. Votre script doit afficher sur le terminal :
- nok user '$id' does not exist si $id n'a pas été entièrement créé ;
- la ligne start-of-file suivie du contenu du mur de $id suivi de end-of-file. Pensez à utiliser la commande cat pour afficher le contenu du mur.
Comme pour les questions précédentes, l'affichage généré par votre programme doit exactement
respecter les consignes.
Cette commande est la seule qui peut générer des affichages de plus d'une ligne, par exemple :
start-of-file
darth-vader: bonjour, tu veux être mon ami ?
C3P0: joli photo
yoda: grosse soirée thème Teletubbies chez Palatine jeudi soir, venez nombreux !
chewbacca: uuuarrrgg !
end-of-file
Mise en place du serveur
On souhaite maintenant mettre en place le serveur. Dans un premier temps, votre serveur
interprète des requêtes de façon interactive, c'est-à-dire qu'il lit des requêtes à partir
du terminal et les exécute.
Écrivez un script mini-facebook.sh. Ce script exécute une boucle infinie.
À chaque pas de la boucle, le script lit une requête à partir du terminal.
Une requête est toujours sous la forme req id args.
Pour lire la requête de l'utilisateur, il vous suffit d'utiliser la commande
read req id args.
En effet, si l'utilisateur rentre plus de deux mots, req
contiendra le premier mot, id le second mot, et args
tous les mots qui suivent.
Votre serveur doit ensuite être capable d'interpréter quatre requêtes différentes en utilisant
les quatre scripts que vous avez écrits dans la première partie :
- create id : crée l'utilisateur id ;
- add id friend : ajoute l'ami friend aux amis de id ;
- post sender receiver message : poste un message ;
- display id : affiche le mur de id.
À terme, votre serveur est utilisé en parallèle par de nombreux utilisateurs. On souhaite
donc rendre l'exécution des commandes parallèles. Il faut donc exécuter
les commandes en arrière plan. Malheureusement, vous pourriez
avoir des incohérences lorsque deux requêtes accédant aux fichiers ou répertoires
du même utilisateur s'exécutent en parallèle.
Il faut donc modifier tous vos
scripts pour éviter ces incohérences. Pour cela, vous devez
utiliser un verrou nommé $id.lock lorsque vous accédez aux fichiers ou
répertoires associés à l'utilisateur $id.
Modifiez mini-facebook.sh pour lancer les commandes en arrière plan
et modifiez les quatre fichiers de commandes pour assurer la cohérence.
Dans post-message.sh, les messages sont postés chez le récepteur et non
chez l'émetteur. Il faut donc protéger le répertoire du récepteur.
Mise en place des clients
On souhaite maintenant mettre en place des clients pour le serveur.
Écrivez un script client.sh id prenant en argument un identifiant $id.
Ce script est une première ébauche de client.
Il vérifie d'abord que l'argument a bien été donné.
Ensuite, votre client exécute une boucle infinie. À chaque pas de la boucle,
le client lit une requête sous la forme req args.
Si $req n'est pas vide, le programme affiche alors
sur l'écran la ligne
req id args, où $id est l'identifiant passé en argument de
client.sh.
On souhaite maintenant connecter le client et le serveur avec des tubes nommés.
Le serveur doit créer un tube nommé mini-facebook.pipe, alors
que le client doit créer un tube nommé $id.pipe, dans lequel
$id est l'identifiant passé en argument de client.sh.
De façon à éviter des ouvertures et fermetures de tube intempestives pouvant
mener à des blocages ou des erreurs lorsque le tube est fermé alors qu'il reste un interlocuteur,
vous devez impérativement utiliser des redirections avancées.
On vous demande donc d'ajouter la commande suivante après votre création
du tube :
où nom-du-tube est le nom du tube que vous venez de créer.
Modifiez vos scripts mini-facebook.sh et client.sh
en conséquence.
exec 3<> nom-du-tube
Les tubes sont juste crées dans cette question, ils seront utilisés plus tard
dans l'exercice (questions j et k).
Que ce soit pour le serveur ou le client, le tube nommé doit être détruit
lorsque le programme quitte. Actuellement, le programme quitte car vous
utilisez vraisemblablement la combinaison de touche control-c
pour envoyer un signal INT au processus. Dans le serveur
et le client, vous devez donc intercepter ce signal pour détruire
le tube et quitter le processus avec la commande exit 0.
Modifiez vos scripts mini-facebook.sh et client.sh
en conséquence.
Au lieu d'afficher req id args sur le terminal, le client doit
maintenant envoyer ses requêtes sur le tube du serveur. Le serveur, de son côté,
doit lire les requêtes à partir de son tube nommé au lieu de les lire à partir
du terminal.
Modifiez client.sh et mini-facebook.sh en conséquence.
Pour tester vos programmes, nous vous conseillons d'ouvrir deux terminaux,
l'un pour exécuter un client, l'autre pour exécuter le serveur.
Au lieu d'afficher les réponses dans son terminal, votre serveur doit maintenant
envoyer les réponses au client. Modifiez votre serveur pour que chacune des quatre
commandes écrivent dans le tube du client au lieu d'écrire sur le terminal.
Pensez à utiliser $cat $id.pipe pour vérifier que les données
envoyées par le serveur à l'utilisateur $id sont
bien écrites dans le tube associés à l'utilisateur $id.
Sur l'ensemble du code, vous n'avez à faire que quatre redirections.
Maintenant, on souhaite afficher le contenu du tube nommé chez le client.
Le serveur peut envoyer trois types de messages différents, identifiés
par le premier mot reçu :
- Le premier mot est égale à start-of-file. Dans ce cas, la réponse à afficher se trouve sur les lignes suivantes, jusqu'au end-of-file.
- Le premier mot est égal à ok. Dans ce cas, tout s'est bien passé et vous pouvez afficher un message comme Command successfully executed.
- Le premier mot est égal à nok. Dans ce cas, une erreur est apparue sur le serveur. Vous pouvez afficher un message comme Error: $msg, où $msg contient la fin de la ligne reçue (rappelez-vous que $msg stocke la fin de la ligne dans read ok msg).
Bravo, vous venez d'écrire votre premier serveur multiprocessus, bienvenue en système !