Dans ce TD 02, le mécanisme d'échange entre les clients et le serveur sera l'IPC Unix "File de Messages".
Ce mécanisme sera utilisé de la façon suivante :
les clients et le serveur utiliseront la même file de messages
ni les clients, ni le serveur n'effectueront de lectures inutiles dans la file, grâce à l'utilisation astucieuse du champ "type" des messages posés dans une file de messages Unix
Il faudra choisir un moyen d'identifier les requêtes des clients et les réponses du serveur vers les clients.
La difficulté principale vient du fait que tous les clients posent leur message de requête dans la même file et que le serveur pose tous les messages de réponse dans cette file également.
On aura donc n+1 process lisant des messages dans la même file : il leur faut un moyen pour ne lire QUE les messages qui leur sont destinés.
On va utiliser une particularité du fonctionnement de la file de messages Unix : on peut faire une lecture sur la file qui ne donne en réponse QUE les messages dont le champ "type" est égal à une valeur donnée.
Il suffit donc d'associer une valeur particulière de "type" pour les messages "requêtes", ainsi seul le serveur lira ces messages.
Mais il faut aussi associer une valeur de type différente pour chaque client.
La difficulté principale vient du choix d'une méthode permettant d'affecter un muméro de type différent à chaque client.
C'est un problème, classique dans le cadre d'applications distribuées, de nommage.
On peut distinguer deux façons de résoudre ce problème de nommage (ici d'affectation d'un numéro unique à chaque client).
Les méthodes externes ci-dessous sont données à titre d'information, on utilisera dans ce TD la méthode "interne", plus simple dans ce cas.
On construit un mécanisme indépendant de la file de messages et du serveur. Chaque nouveau client exécute :
demander un numéro de client unique
utiliser le serveur pour une ou plusieurs transactions
libérer le numéro de client
Le mécanisme d'allocation/distribution des numéros uniques doit être accédé de manière ATOMIQUE (section critique), afin d'assurer que deux clients ne prennent le même numéro.
Ce mécanisme peut être :
un fichier commun "connu" et accédé avec un verrou exclusif
un vecteur de bits conservés par le système (par exemple un ensemble de sémaphores sous unix)
utiliser simplement le pid du process (tous différents sur la même machine), mais cette méthode ne fonctionne pas si on utilise des threads
créer un "serveur de numéros" qui écoute sur un socket et est implémenté comme un serveur concourant (1 seul client connecté à la fois)
etc ...
On fait faire ce travail par le serveur lui-même : on lui ajoute une requête spéciale "demande de numéro client". quand il trouve une requête de ce type dans la file, le serveur choisit un numéro libre puis pose dans la file un message "nouveau client" contenant le numéro de client unique. Le client qui a posé la requête n'a qu'a lire dans la file le premier message disponible de type "nouveau client".
On fait faire ce travail par le serveur lui-même : on lui ajoute une requête spéciale "demande de numéro client". quand il trouve une requête de ce type dans la file, le serveur choisit un numéro libre puis pose dans la file un message "nouveau client" contenant le numéro de client unique. Le client qui a posé la requête n'a qu'a lire dans la file le premier message disponible de type "nouveau client".
Eh bien ! Il ne le sait pas, et PEU IMPORTE. Ce qui compte c'est qu'il va être le seul à obtenir ce numéro, puisque en le lisant, il retire le message de la file, personne d'autre ne l'aura. Ce qui compte ici, c'est l'UNICITÉ, l'ordre importe peu.
Nous disposons maintenant d'un moyen d'échange de messages entre les clients et le serveur.
Les messages posés dans une file de message ont la structure suivante (voir man msgop) :
struct msgbuf {
long mtype; /* message type, must be > 0 */
char mtext[1]; /* message data */
};
Voir aussi dans "man msgop" l'utilisation du paramètre "msgtyp" de l'appel système msgrcv(2).
L'élément "mtype" des messages va nous permettre de distinguer les différents types de destinataires possibles :
le serveur
les clients non identifiés
les clients identifiés
Ensuite, les clients pourront envoyer et recevoir du serveur des messages de différentes sortes, chacun correspondant à une des opérations prévues au cahier des charges du serveur.
Dans la dernière phrase on a employé le mot "sorte de" pour bien le distinguer de "type".
En effet, on manipule dans l'application deux "types" imbriqués :
le type au sens file_mess_IPC (c'est-à-dire au sens du procédé de transport du message) qui va nous permettre de distinguer le destinataire (soit le serveur, soir UN client particulier, soit un client non identifié)
le type au sens de l'application, par exemple :
message de type creer_panier
message de type lister_objets
message de type etat_objet, ...
Un message de type "etat_objet" au sens de l'application sera lui-même encapsulé dans un message de type (client->serveur) au sens de la file de messages.
En reprenant le cahier des charges du serveur, on peut lister tous les types de messages "application" suceptibles d'être échangés.
Ensuite, pour chaque type de message serveur <--> clients, il faut définir le contenu du message, c-à-d les data échangées. Par exemple :