Département INFormatique 
  CSC4508/M2 : Concepts des systèmes d'exploitation et mise en œuvre sous Unix


    Évaluation



TÉLÉCOM SudParis 2ème année

TP Noté CSC4508/M2 du 19/05/11

(Corrigés)

Modalités

Durée : 3 heures

Tous documents autorisés.

Les questions 1, 2 et 3 sont indépendantes les unes des autres. Aussi, n'hésitez pas à lire tout le sujet avant de commencer pour déterminer l'ordre dans lequel vous souhaitez traiter les questions.

Le barème est donné à titre indicatif des poids entre les différentes questions.

La « livraison » de votre travail en fin de TP noté se fera par remontée sous Moodle (rubrique « TP noté de 3 heures ») du fichier d'extension tgz constitué de la manière suivante :
cd votreRepertoireDeTravailPourCSC4508M2
tar cvfz $USER.tgz TPNote2011
NB: Si vous êtes plus à l'aise en écrivant vos réponses sur une copie plutôt qu'en les tapant sur ordinateur, vous pouvez rendre également une copie papier à l'enseignant qui vous surveille. En fin d'épreuve, même si vous ne rendez pas de copie, passez voir l'enseignant qui vous surveille pour lui signaler explicitement que vous ne rendez pas de copie.

Préparation

cd votreRepertoireDeTravailPourCSC4508M2
cp ~simatic/Cours/CSC4508/tPNote2011.tgz .
tar xvfz tPNote2011.tgz
cd TPNote2011

Question 1 : Questions de cours (4,5 points)

Pour chacune des questions ci-dessous, répondez dans le fichier Q1/reponse1.txt ou bien sur votre copie (dans ce dernier cas, indiquez dans le fichier Q1/reponse1.txt « Réponse sur copie »).

Question 1.1 :  Mémoire parent / mémoire enfant (3 points)

On considère le code suivant (dans lequel toute la gestion des codes d'erreur a été omise pour gagner en lisibilité) :












int d1;
int *pd2;
int *pd3;

int main(){
pd2 = malloc(sizeof(int));

shm_id = shmget(SHMKEY, sizeof(int), 0700|IPC_CREAT));
pd3 = (int *)shmat(shm_id, NULL, 0));

fork();

// Section de code concernée par les questions ci-dessous
// ------------------------------------------------------
...
}

Pour chacune des questions suivantes, vous justifierez brièvement.

Après le fork, la variable d1 est-elle partagée entre le parent et l'enfant qu'il a créé ? Dit autrement, après le fork, si le parent change la valeur de d1, l'enfant voit-il ce changement ?

Après le fork, la zone de données pointée par pd2 est-elle partagée entre le parent et l'enfant qu'il a créé ? Dit autrement, si le parent change la valeur de *pd2, l'enfant voit-il ce changement ?

Même question pour la zone de donnée pointée par pd3.
---beginCorr
Barème : 1 point par proposition (0,5 pour la justesse de la réponse et 0,5 pour la justification), soit un total de 3 points.

d1 et *pd2 ne sont pas partagées. En effet, la mémoire du parent est complètement clonée pour constituer la mémoire de l'enfant. Et ces deux zones mémoire sont étanches.
*pd3 est partagée, puisqu'elle est stockée en mémoire partagée.
---endCorr

Question 1.2 : Libevent et sémaphore (1,5 points)

Le cours « Architectures Client-Serveur » étudie la librairie libevent. Nous avons vu, pendant ce cours, que l'exemple de code utilisant libevent était limité par le nombre de descripteurs de fichiers simultanément ouverts par le processus serveur. Nous avons vu également qu'utiliser un sémaphore au niveau de ce processus serveur pour limiter le nombre de clients simultanés (et donc ne pas atteindre cette limite du nombre de descripteurs de fichiers) n'était pas une bonne idée.

Réexpliquez pourquoi l'utilisation d'un sémaphore n'est pas une bonne idée.
---beginCorr
Barème : 1,5 point pour l'explication du problème
Au niveau du serveur, utiliser un sémaphore selon un paradigme de synchronisation de type Cohorte n'est pas une bonne idée. En effet, imaginons que libevent réveille notre serveur pour lui signaler qu'un nouveau client veut se connecter. Si le serveur fait un P(sémaphore) et que le sémaphore est à 0 parce que le serveur a atteint le nombre maximum de clients simultanés, le serveur se bloque sur le P(). Par conséquent, il bloque le thread libevent qui exécute la fonction event_dispatch() et qui est donc chargé de prendre en compte la réception des événements en provenance de clients. De ce fait, lorsqu'un client déjà en relation avec le serveur signale au serveur qu'il a fini de travailler avec lui, libevent ne peut pas traiter cette information (il est coincé dans le traitement du P()). Ainsi, le thread libevent ne peut pas exécuter le code qui permet de faire V(sémaphore). Le serveur est donc bloqué pour toujours.

Pour information (ce n'était pas demandé dans ce TP noté), pour résoudre le problème, il ne faut pas employer de sémaphore, mais une variable contenant le nombre de connexions que le serveur peut encore accepter. Cette variable est initialisée au nombre maximum de connexions que le serveur peut accepter. À chaque fois que le serveur traite la connexion d'un nouveau client, il décrémente cette variable. Si cette variable arrive à 0, le serveur désactive la prise en compte de l'événement ArrivéeNouveauClient au niveau de libevent (il faut donc retirer l'attribut EV_PERSIST au niveau de l'instruction event_set(&eventTubePrincipal, fdR, EV_READ|EV_PERSIST, callbackTubePrincipal, NULL); et ne pas faire d'instruction event_add(&eventTubeprincipal, NULL); dans la fonction callbackTubePrincipal()) Ainsi les nouveaux clients qui veulent se connecter voient leur demande de connexion mises en attente par libevent. Quand un client déjà connecté a fini de travailler avec le serveur, ce dernier incrémente la variable et signale à libevent qu'il accepte désormais de prendre à nouveau en compte les événements ArrivéeNouveauClient.
---endCorr

Question 2 : La Trace du Hourra (8,5 points)

Pour chacune des questions ci-dessous :
  • Répondez dans le fichier Q2/reponse2.txt ou bien sur votre copie (dans ce dernier cas, indiquez dans le fichier Q2/reponse2.txt « Réponse sur copie »).
  • Le langage algorithmique utilisé n'a pas besoin de respecter strictement la syntaxe apprise en première année. L'essentiel est qu'il soit lisible par le correcteur et sans ambiguïté.
Au Parc d'attractions Astérix, « La Trace du Hourraanbsp;» est une montagne russe avec 3 trains qui circulent simultanément sur un circuit. Cet exercice a pour objectif d'écrire les algorithmes de synchronisation qui permettraient de gérer automatiquement cette montagne russe.

La figure 1 modélise cette montagne russe. Elle est constituée de 4 zones:
  • zoneE est la zone d'Embarquement où les passagers montent et descendent d'un train. zoneE peut contenir au maximum 2 trains.
  • zoneA est la zone d'Attente juste avant de pouvoir entrer dans zoneE. Elle ne peut contenir qu'un train.
  • zoneH est la zone où un train monte avant de se lancer dans sa descente infernale (c'est donc la zone des Hurlements où les gens crient « Help, je veux descendre ! Non, pas comme çaaaaaaaa... »). Elle ne peut contenir qu'un train.
  • zoneG est la zone de Garage du train. Les trains y sont parqués avant d'être injectés dans le circuit zoneE-zoneH-zoneA. NB : dans cet exercice, on suppose qu'une fois qu'un train est sorti de zoneG, il n'y retourne plus jamais.
Schéma de la montagne russe "La Trace du Hourra"
Figure 1 : Modélisation de la montagne russe « La Trace du Hourra »

Quand cette attraction fonctionne :
  • Un train ne peut quitter zoneG que s'il y a de la place dans zoneE.
  • Un train ne peut quitter zoneE que si zoneH est vide.
  • Un train ne peut quitter zoneH que si zoneA est vide.
  • Un train ne peut quitter zoneA que s'il y a de la place dans zoneE.

Question 2.1 : Paradigmes de synchronisation des trains sur la Trace du Hourra (1,5 point)

Indiquez tous les paradigmes de synchronisation présents dans cette montagne russe.
---beginCorr
Barème : 0,5 point par paradigme * 3 paradigmes = 1,5 point

L'énoncé indique que, quand cette attraction fonctionne, il y a 4 conditions de sortie de zone. Ces 4 conditions de sortie se ramènent à 3 conditions d'entrée dans un zone :
  • Qu'il vienne de zoneG ou zoneA, un train ne peut rentrer dans zoneE que si l'une des 2 places de zoneE est libre. C'est donc un paradigme de type Cohorte.
  • Un train ne peut quitter zoneE que si zoneH est vide, donc s'il a la garantie qu'il sera seul dans cette zone. C'est donc un paradigme de type Exclusion mutuelle.
  • Un train ne peut quitter zoneH que si zoneA est vide, donc s'il a la garantie qu'il sera seul dans cette zone. C'est donc un paradigme de type Exclusion mutuelle.
---endCorr

Question 2.2 : Algorithme de synchronisation des trains sur la Trace du Hourra (5 points)

Chaque train est modélisé par un processus exécutant l'algorithme codeTrain() suivant :
procédure codeTrain()
// à compléter, si besoin, avec du code de synchronisation
codeLiéAlaPrésenceDansZoneG();
// à compléter, si besoin, avec du code de synchronisation
Tant que VRAI faire
// à compléter, si besoin, avec du code de synchronisation
codeLiéAlaPrésenceDansZoneE();
// à compléter, si besoin, avec du code de synchronisation
codeLiéAlaPrésenceDansZoneH();
// à compléter, si besoin, avec du code de synchronisation
codeLiéAlaPrésenceDansZoneA();
// à compléter, si besoin, avec du code de synchronisation
Fin Tant que
Fin Procédure

À l'aide de sémaphores (dont vous indiquerez les valeurs initiales), d'éventuelles variables additionnelles (dont vous indiquerez les valeurs initiales), et d'opérations P() et V(), complétez l'algorithme de codeTrain().
---beginCorr

Barème :
  • 4 points répartis en 1 point par contrainte correctement traitée (d'un point de vue synchronisation) :
    • Un train ne peut quitter zoneG que s'il y a de la place dans zoneE.
    • Un train ne peut quitter zoneE que si zoneH est vide.
    • Un train ne peut quitter zoneH que si zoneA est vide.
    • Un train ne peut quitter zoneA que s'il y a de la place dans zoneE.
  • 1 point pour la notion de vientDeZoneG
Semaphore zoneE initialisé à 2;
Semaphore zoneA initialisé à 1;
Semaphore zoneH initialisé à 1;

Procédure codeTrain()
codeLiéAlaPrésenceDansZoneG();
P(zoneE);
Tant que VRAI faire
codeLiéAlaPrésenceDansZoneE();
P(zoneH)
V(zoneE);
codeLiéAlaPrésenceDansZoneH();
P(zoneA);
V(zoneH);
codeLiéAlaPrésenceDansZoneA1();
P(zoneE);
V(zoneA);
Fin Tant que
Fin Procédure

La solution suivante (moins performante que la précédente puisqu'on fait systématiquement un test à chaque boucle) s'appuie sur un booléen vientDeZoneG:
Semaphore zoneE initialisé à 2;
Semaphore zoneA initialisé à 1;
Semaphore zoneH initialisé à 1;

Procédure codeTrain()
Booléen vientDeZoneG = VRAI
codeLiéAlaPrésenceDansZoneG();
Tant que VRAI faire
P(zoneE);
Si vientDeZoneG == VRAI Alors
// Ce test est dû au fait que lorsqu'on lance un
// processus train pour la première fois, le train

// sort du garage où il était stocké pour aller
// directement dans la zone
d'embarquement (il ne
// sort donc pas de la zone A).

vientDeZoneG = FAUX
Sinon
V(zoneA);
Fin Si
codeLiéAlaPrésenceDansZoneE();
P(zoneH)
V(zoneE);
codeLiéAlaPrésenceDansZoneH();
P(zoneA);
V(zoneH);
codeLiéAlaPrésenceDansZoneA1();
Fin Tant que
Fin Procédure
---endCorr

Question 2.3 : Algorithme d'embarquement et de débarquement des passagers (2 points)

NB : cette question est plus difficile. Nous vous recommandons de ne l'aborder qu'en fin de TP noté.

Quand un train entre dans zoneE, il stationne sur la voie voie0 si celle-ci est libre, et sur la voie voie1 sinon. Ensuite, ce train autorise ses passagers à débarquer. Une fois qu'il est vide, le train autorise les usagers en attente d'embarquement sur cette voie à embarquer. Quand le train est plein, il quitte zoneE.

Par ailleurs, quand un usager de l'attraction entre dans zoneE, il commence par choisir la voie (voie0 ou voie1) où il souhaite embarquer. Une fois embarqué dans un train, il attend d'être autorisé à débarquer du train. Voici son algorithme:
Procédure codeUsager()
Entier numéroTrain
voieChoisiePourEmbarquer = nombreAléatoireEntre_0_et_1
numéroTrain = embarquerDansTrain(voieChoisiePourEmbarquer);
débarquerDeTrain(numéroTrain);
Fin Procédure

Sachant qu'un train peut contenir au maximum N passagers, écrivez les algorithmes de embarquerDansTrain(), débarquerDeTrain() et codeLiéAlaPrésenceDansZoneE() évoqué à la question 2.1. Vous utiliserez des sémaphores (dont vous indiquerez les valeurs initiales), d'éventuelles variables additionnelles (dont vous indiquerez les valeurs initiales), et d'opérations P() et V(). Si vous en avez besoin (parce que, par exemple, du code de synchronisation de train écrit à la questionQ2.1 doit se retrouver dans codeLiéAlaPrésenceDansZoneE()), écrivez une nouvelle version de codeTrain().
---beginCorr
Barème :
  • 2 points répartis en 0,5 point par algorithme à écrire

Structure Voie
// Variable indiquant si la voie est libre (cas VRAI) ou non (cas FAUX)
Booléen libre = VRAI
// numéro du train actuellement stocké sur la voie (cas où libre==VRAI)
Entier numTrain
// Variable pour la gestion de l'attente, par les usagers d'une voie,
// de pouvoir embarquer dans le train qui s'arrêtera sur cette voie
// (comme on ne sait pas quel est le train qui s'arrêtera sur la voie,
// ce sémaphore ne peut être qu'au niveau de la voie).
Sémaphore semEmbarquement initialisé à 0
Fin Structure
Voie voie[NB_VOIES]

// Gestion de la concurrence d'accès au tableau voie[]
Sémaphore mutexVoie initialisé à 1 // Permet de gérer la concurrence d'accès au tableau voie[]

Structure Train
// Variable pour la gestion de l'attente, par les passagers, de
// pouvoir débarquer du train
Sémaphore semDebarquement initialisé à 0

// Variables pour la gestion de l'attente, par le train, que tous
// les passagers aient embarque dans le train
Sémaphore mutexNbPassagersEmbarques initialisé à 1

Entier nbPassagersEmbarques = 0
Sémaphore semPassagersTousEmbarques = 0
// Variables pour la gestion de l'attente, par le train, que tous
// les passagers aient debarque d'un train
Sémaphore mutexNbPassagersDebarques initialisé à 1
Entier nbPassagersDebarques = 0
Sémaphore semPassagersTousDebarques = 0
Booléen vientDeZoneG = VRAI
Fin Structure
Train train[NB_TRAINS]

Procédure codeLiéAlaPrésenceDansZoneE()
// Recherche d'une voie libre
P(mutexVoie)
Si voie[0].libre == VRAI alors
maVoie = 0
Sinon
maVoie = 1
Fin Si
voie[maVoie].libre = FAUX
voie[maVoie].numTrain = numTrainDuProcessusActuel
V(mutexVoie)

// Le train autorise ses passagers actuels à débarquer
Si train[numTrainDuProcessusActuel].vientDeZoneG == VRAI Alors
// Ce test est dû au fait que lorsqu'on lance un processus train pour la première fois,
// le train sort du garage où il était stocké pour aller directement dans la zone
// d'embarquement (il ne sort donc pas de la zone A) et il n'a pas de passagers.
train[numTrainDuProcessusActuel].vientDeZoneG = FAUX
Sinon
// Le train ne venant pas de zoneG, il contient des passagers. On les débarque.
Répéter N fois
P(voie[maVoie].semDebarquement)
Fin Répéter
Fin Si

// Le train attend que ses passagers actuels aient tous débarqué
P(train[semPassagersTousDebarques].semPassagersTousDebarques)

// Le train autorise N passagers en attente sur sa voie à embarquer
Répéter N fois
P(voie[maVoie].semEmbarquement)
Fin Répéter

// Le train attend que tous les passagers aient embarqué
P(train[semPassagersTousDebarques].semPassagersTousEmbarques)

// Le train quitte zoneE
P(zoneH)
P(mutexVoie)
voie[maVoie].libre = VRAI
V(mutexVoie)
V(zoneE)

Fin Procédure

Fonction embarquerDansTrain(donnée voieChoisiePourEmbarquer) retourne entier
Entier numTrain
// On attend de pouvoir embarquer dans un train
P(voie[voieChoisiePourEmbarquer].semEmbarquement)

// On signale au train si on est le dernier passager à embarquer
numTrain = voie[voieChoisiePourEmbarquer].numTrain
P(train[numTrain].mutexNbPassagersEmbarques)
train[numTrain].nbPassagersEmbarques += 1
Si train[numTrain].nbPassagersEmbarques >= N Alors
train[numTrain].nbPassagersEmbarques = 0
P(train[numTrain].
semPassagersTousEmbarques)
Fin Si
V(train[numTrain].mutexNbPassagersEmbarques)
retourne numTrain
Fin Fonction

Procédure débarquerDeTrain(Donnée : Entier numTrain)
Entier numVoie
// On attend que le train nous autorise à débarquer
P(train[numTrain].semDebarquement)
// On signale au train si on est le dernier passager à débarquer
P(train[numTrain].mutexNbPassagersDebarques)
train[numTrain].nbPassagersDebarques += 1
Si train[numTrain].nbPassagersDebarques >= N Alors
train[numTrain].nbPassagersDebarques = 0
P(train[numTrain].
semPassagersTousDebarques)
Fin Si
V(train[numTrain].mutexNbPassagersDebarques)
Fin procédure

codeTrain() ne doit évoluer que si on a utilisé un booléen vientDeZoneG à la question Q2.1. En effet, dans ce cas, le V(zoneA) doit être intégré à codeLiéAlaPrésenceDansZoneG().
---endCorr

Question 3 : Multi-threading pour un serveur de liste de M. et Mme (7 points)

NB : Pour certaines des questions ci-dessous, vous êtes conviés à répondre dans le fichier Q3/reponse3.txt . Si vous préférez répondre sur votre copie, indiquez dans le fichier Q3/reponse3.txt « Réponse sur copie ».

Dans cette question, on considère un serveur de liste de M. et Mme. Il permet à un processus client de récupérer la liste des M. et Mme stocké dans un fichier mEtMme.txt géré par un processus serveur.

Préliminaire

Avant de rentrer dans le vif du sujet, faites fonctionner cette application :
  • dans un terminal (noté TermS dans la suite), positionnez-vous dans le répertoire Q3, tapez make pour compiler, puis ./serveur
  • dans un autre terminal (noté TermC dans la suite), positionnez-vous dans le répertoire Q3, tapez ./client -v nomClient
    Le client affiche :
Client nomClient : Connexion au serveur...
Client nomClient : Connecte !
Client nomClient : Recuperation liste des M. et Mme...
Client
nomClient : Nouveau M. et Mme = ABA   ont un fils :    Bart
...
Client nomClient : Nouveau M. et Mme = ZOUDANLCOU              ont une fille   Debby
Client nomClient : Recuperation terminee !

  • pendant ce temps, le serveur affiche dans TermS :
1-ieme client a traiter (point d'access client = ./serveur-client.3565)

Pour accomplir ce travail, le client se connecte via un tube nommé client-serveur commun à tous les clients du serveur. Puis, le client envoie un message CONNEXION indiquant le tube nommé serveur-client.pidClient sur lequel le client attend des réponses du serveur. Le serveur crée un tube nommé client-serveur.rangClient (qui sera spécifique aux échanges entre ce client et le serveur) et renvoie le message CONNEXION_OK (sur le tube serveur-client.pidClient) avec le nom du tube client-serveur.rangClient. Le client affiche « Connecte ! »
Ensuite, le client envoie, via le tube client-serveur.rangClient, un premier message REQUETE.  Le serveur répond, via le tube serveur-client.pidClient, le message REQUETE_OK avec le premier M. et Mme du fichier mEtMme.txt. Si le client a été lancé avec l'option -v, il affiche le M. et Mme reçu.
Le client envoie alors un deuxième message REQUETE auquel le serveur répond avec le M. et Mme suivant du fichier.
Et ainsi de suite jusqu'à ce que le serveur renvoie au client le message REQUETE_KO pour signifier qu'il a envoyé au client tous les M. et Mme du fichier. Le client affiche alors « Recuperation terminee ! »
Les tubes serveur-client.pidClient et client-serveur.rangClient sont alors détruits. Le client s'arrête.

Travail à faire

Le serveur est actuellement mono-activité, de sorte qu'il ne peut traiter qu'un client à la fois.
Ainsi, si vous tapez dans TermC: for ((i=1;i<=3;i+=1)); do (./client $i &);done
vous obtenez l'affichage :
$ for ((i=1;i<=3;i+=1)); do (./client $i &);done
Client      1 : Connexion au serveur...
Client      1 : Connecte !
Client      1 : Recuperation liste des M. et Mme...
Client      2 : Connexion au serveur...
Client      3 : Connexion au serveur...
Client      1 : Recuperation terminee !
Client      2 : Connecte !
# Le serveur a enfin répondu à la demande de connexion
# de Client 2, puisqu'il a fini de traiter la demande
# de liste faite par Client 1

Client      2 : Recuperation liste des M. et Mme...
Client      4 : Connexion au serveur...
Client      2 : Recuperation terminee !
Client      3 : Connecte !
# Le serveur a enfin répondu à la demande de connexion
# de Client 3, puisqu'il a fini de traiter la demande
# de liste faite par Client 2

traiter la demande de liste du Client 1
Client      3 : Recuperation liste des M. et Mme...
Client      3 : Recuperation terminee !

Ajoutez du multi-threading à Q3/serveur.c de sorte que le serveur puisse gérer plusieurs clients simultanément. Vous veillerez notamment à :
  1. protéger la section de code comprise rangClient++; printf(); f = fopen(); if (f == NULL); sprintf(nomTube, "%s.%d", NOM_POINT_ACCES_SERV,rangClient); par une exclusion mutuelle;
  2. expliquer, dans le fichier Q3/reponse3.txt, la raison pour laquelle il faut mettre cette exclusion mutuelle.
Vérifier que votre serveur gère désormais bien 3 clients en parallèle en tapant dans TermC: for ((i=1;i<=3;i+=1)); do (./client $i &);done
Vous devez obtenir un affichage montrant qu'il y a désormais parallélisme entre les clients comme, par exemple, dans la trace suivante :
$ for ((i=1;i<=3;i+=1)); do (./client $i &);done
Client      1 : Connexion au serveur...
Client      2 : Connexion au serveur...
Client      1 : Connecte !
Client      1 : Recuperation liste des M. et Mme...
Client      2 : Connecte !
Client      2 : Recuperation liste des M. et Mme...
Client      3 : Connexion au serveur...
Client      3 : Connecte !
Client      3 : Recuperation liste des M. et Mme...
Client      2 : Recuperation terminee !
Client      1 : Recuperation terminee !
Client      3 : Recuperation terminee !

Pour information, au delà de 299 clients simultanés, votre serveur s'arrête pour une raison qui sort du cadre de ce TP noté.
---beginCorr
Barème :
  • Création correcte des threads : 1,5 point
  • Terminaison correcte du thread (et le fait qu'il est détaché du thread principal pour mourir de sa belle mort) : 1 point (0,5 pour pthread_exit et 0,5 pour pthread_detach)
  • Gestion correcte de la mémoire contenant le message initial : 1,5 point (0,5 point pour l'allocation, 0,5 pour le passage en paramètre pendant la création du thread, et 0,5 point pour la libération)
  • Test sur code retour de pthread_create et de malloc : 1 point (0,5 point pour chaque) auquel peuvent se retirer (on peut donc avoir une note négative pour cette rubrique) 0,5 pour une indentation moche et 0,5 point pour une compilation avec Warning.
  • Définition du mutex pour l'exclusion mutuelle et positionnement correct du P et du V : 1 point
  • Justification de l'exclusion mutuelle : 1 point
Justification de l'exclusion mutuelle : dans la section de code rangClient++; printf(); f = fopen(); if (f == NULL); sprintf(nomTube, "%s.%d", NOM_POINT_ACCES_SERV,rangClient); , 2 threads peuvent exécuter presque simultanément rangClient++. De ce ce fait, quand il exécutent sprintf(nomTube, "%s.%d", NOM_POINT_ACCES_SERV,rangClient), ils peuvent potentiellement utiliser la même valeur de rangClient. Donc, le serveur utilisera le même tube nommé pour communiquer avec 2 clients différents. Pour éviter ce problème, il faut protéger cette section de code par une exclusion mutuelle.

Voir fichier serveur.Q3.corrige.c
---endCorr



Page mise à jour le 20 mai 2011