TÉLÉCOM SudParis 2ème
		année 
		 
		TP Noté CSC4508/M2 du 13/06/17
	    
	      (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 ${USER}_TPNote2017
  
	    Préparation
	    
	    	      cd votreRepertoireDeTravailPourCSC4508M2 	      wget http://www-inf.it-sudparis.eu/COURS/CSC4508/Current/Documents/ExoCoursSys/TPNote2017/WWW/tPNote2017.tgz 	      tar xvfz tPNote2017.tgz
	      mv TPNote2017 ${USER}_TPNote2017
	      cd ${USER}_TPNote2017
	     
	    
	    Question 1 : Gestion de comptes en banque (7.5 points)
	    
	      Dans le réperoire Q1, vous trouverez un programme
	      permettant de gérer des comptes en banque. Ce
	      programme stocke dans le
	      fichier comptes.dat le solde de tous les
	      comptes d'une banque. Le solde de chaque compte est
	      stocké sous la forme d'un entier (codé sur 4
	      octets).
	     
	    
	       Par exemple, le fichier peut contenir:
	     
	    AAAABBBBCCCCDDDDEEEE 
	    
		Ce qui signifie que le compte numéro 0 a pour solde la
	    valeur entière codée par les 4 octets AAAA. Le compte
	    numéro 1 a pour solde BBBB, le compte 2 a pour solde CCCC,
	    etc. 
	    Question 1.1
	    
	      Implémentez, dans le fichier Q1/Q1.1/libcompte.c, les fonctions suivantes:
	     
	    
	      int compte_init(const char* filename):
		cette fonction ouvre le fichier filename
		en mode lecture/écriture et retourne le file
		descriptor obtenu. Si le fichier n'existe pas, la
		fonction le crée. Vous pouvez également
		initialiser des variables dans cette fonction. En cas
		d'erreur lors d'un des traitements de la fonction, la
		fonction retourne la valeur -1.
	       
	      void compte_finalize(int fd): cette
		fonction ferme le fichier dont le file descriptor est
		passé en paramètre. Vous pouvez utiliser cette
		fonction pour terminer proprement le programme
		(libérer des buffers alloués dynamiquement, etc.)
	       
	      int ecrire_solde(int fd, int cpt_id, int new_solde):
		cette fonction écrit dans le fichier la valeur new_solde
		à l'emplacement du compte cpt_id. La fonction retourne -1
		en cas d'erreur, ou 0 sinon.
	       
	      int lire_solde(int fd, int cpt_id):
		cette fonction lit dans le fichier et retourne le
		solde du compte cpt_id. La fonction
		retourne -1 en cas d'erreur.
	       
	      int nb_compte(int fd): cette fonction
		calcule le nombre de comptes stockés dans le
		fichier. Elle retourne -1 en cas d'erreur.
	       
	     
	    
	      Pour chacune de ces fonctions, veillez à traiter
	      proprement les erreurs.
	     
	    
	      ---beginCorr (3.5 points) 
		programme corrigé 
		Barème:
		 
		    int compte_init(const char* filename)+void compte_finalize(int fd): 1 point  
		    int ecrire_solde(int fd, int cpt_id, int new_solde):1 point 
		    int lire_solde(int fd, int cpt_id): 1 point 
		    int nb_compte(int fd): 0.5 point 
		 
	      ---endCorr 
	    
	    Question 1.2
	    
		Le fichier libcompte.c implémente un
		ensemble de fonctions pour manipuler des comptes. Les
		fonctions que vous avez implémentées à la question
		précédentes servent de base pour deux fonctions utilisées par l'application:
	     
	    
		void transfert(int fd, int cpt_src, int cpt_dest, int montant):
		effectue un transfert d'argent entre deux comptes  
		int balance(int fd): parcoure
		l'ensemble des comptes et calcule la balance 
	     
	    
		L'implémentation actuelle n'est pas
		thread-safe. Identifiez les deux problèmes (des fonctions dans libcompte.c) qui
	      risquent de survenir si plusieurs threads utilisent
	      simultanément les fonctions implémentées dans libcompte.c.
	     
	    
	      Pour chaque problème, décrivez dans Q1/reponses_Q1.txt un
	      exemple d'exécution générant le problème.
	     
	    
	      ---beginCorr (2 points) 
	     
	      - Problème 1: on utilise lseek pour positionner
		le curseur où lire/écrire. Donc si le thread T1
		exécute lire_compte(cpt=1) pendant que T2 exécute
		ecrire_solde(cpt=2), on peut avoir l'exécution
		suivante:
		
		  - T1 positionne le curseur sur le compte 1
 
		  - T2 positionne le curseur sur le compte 2
 
		  - T1 lit la valeur où est positionné le curseur (donc la valeur du compte 2)
 
		 
	       
	      - 
		Problème 2: dans transfert(), on lit le solde
		de 2 comptes, on calcule le nouveau solde,
		puis on écrit le solde. Si le solde d'un des
		comptes est modifié entre temps, une des
		modifications est perdue.
	      
 
	     
	     
	    ---endCorr 
	    
	    Question 1.3
	    
	      Copiez le fichier Q1/Q1.1/libcompte.c
	      dans le répertoire Q1/Q1.3,
	      et modifiez l'implémentation de façon à corriger les
	      deux problèmes identifiés à la question précédente.
	     
	    
		---beginCorr (2 points) 
		programme corrigé 
		---endCorr 
	     
	    Question 2 : liste chaînée (7 points)
	    
	      Vous avez chargé un étudiant de développer un programme
	      remplissant une liste de valeurs aléatoire. Le programme
	      doit ensuite trier la liste, l'afficher, puis libérer la
	      mémoire allouée.
	     
	    
	      Malheureusement, l'étudiant a décidé d'arrêter le
	      travail pour se consacrer à sa carrière
	      de Youtubeur
	      spécialisé dans les Hand Spinner. Vous vous
	      retrouvez donc avec le programme
	      inachevé Q2/linked_list.c.
	     
	    Question 2.1
	    
		Bien que le programme compile sans warning, le
		programme génère une erreur de segmentation à l'exécution.
	     
	    
		Dans le réperteroire Q2/Q2.1, corrigez le
		programme pour qu'il s'exécute sans erreur.
	     
	    
		---beginCorr (1 points) 
		programme corrigé 
		---endCorr 
	     
	    Question 2.2
	    
		Les blocs mémoire alloués dans la fonction list_add ne
		sont jamais libérés. Cela peut poser problème car vous
		envisagez d'utiliser ce programme pour manipuler de
		nombreuses listes.
	     
	    
		Implémentez la fonction free_list dans le
		fichier Q2/Q2.2/linked_list.c. Cette
		fonction doit libérer les blocs mémoire d'une liste et
		remettre à NULL le pointeur list
		déclaré dans la fonction main.
	     
	    
		---beginCorr (3 points) 
		programme corrigé 
		Barème:
		 
		    - 2.5 point pour la libération de tous les blocs de la liste
 
		    - 0.5 point pour la réinitialisation de 
list 
		 
		---endCorr 
	    
	    Question 2.3
	    
		La dernière partie manquante dans le programme est la
		fonction sort_list. Cette fonction trie
		la liste suivant la valeur du champs value des
		noeuds.
	     
	    
		Implémentez la fonction sort_list dans le
		fichier Q2/Q2.3/linked_list.c.
	     
	    
		---beginCorr (3 points) 
		programme corrigé 
		---endCorr 
	     
	    Question 3 : Nouveaux exemples pour le cours
	      Éléments d'architecture Client-Serveur (13,5
	      points)
	    Pendant le cours Éléments d'architecture Client-Serveur,
	      nous avons étudié l'exemple d'un client qui communique,
	      via TCP, avec un serveur pour lui envoyer plusieurs requêtes. Le
	      serveur répond avec un message contenant la réponse ou
	      bien le fait que le client doit arrêter d'envoyer des
	      requêtes. Dans ce dernier cas, le client s'arrête. 
	    Dans cet exercice, nous étudions un client qui
	      communique, via UDP, avec un serveur. En utilisant UDP, nous pouvons
	      illustrer d'autres architectures Client-Serveur vues en cours. Ainsi,
	      le client et le serveur fournis dans le répertoire Q3.1 sont
	      basés sur une architecture dans laquelle le client fournit le
	      contexte de sa requête au serveur, en l'occurrence le nombre de
	      réponses que le serveur a faite(s) à ce client
	      jusqu'à présent. 
	     
	    # Travail préparatoire qui sert à toutes les compilations et servira à partir de la question 3.2 
$
	    cd Q3 
	    $ tar xzvf libredblack-1.3.tar.gz 
	    
		$ cd libredblack-1.3 
	    
		$ ./configure ; make 
	    
		$ cd ../.. 
	     
# Génération de la version initiale de l'application 
$ cd Q3/Q3.1 
	      $ make 
       
# Exécution d'un exemple 
	      $ ./serveurUDP 
	      # Dans une autre fenêtre 
	      $ ./clientUDP essai 
	    
	    Le serveur affiche : 
	    1-ieme client a traiter (localhost:45499) 
	      
	     
	    Le client affiche 
	    Client  essai : A trouve le serveur ! 
		Client  essai : Envoi de requetes vers le serveur... 
		Client  essai : Nouvelle reponse du serveur = " 1-ieme reponse au client" ==> OK 
		Client  essai : Nouvelle reponse du serveur = " 2-ieme reponse au client" ==> OK 
		... 
		Client  essai : Nouvelle reponse du serveur = " 9-ieme reponse au client" ==> OK 
		Client  essai : Nouvelle reponse du serveur = "10-ieme reponse au client" ==> OK 
		Client  essai : Envoi de requetes termine ! 
	      
	     
	    Question 3.1
	    Le serveur actuel est monotâche. Dans le
	      fichier Q3/reponses3.txt ou bien sur votre
	      copie, indiquez le principal inconvénient de
	      cette architecture.
	     
	     Pour pallier cet inconvénient, modifiez le code
	      de Q3.1/serveurUDP.c pour que le serveur dispose
	      d'un pool de 200 threads prêts à traiter une
	      requête client.
	     
	    ---beginCorr (3.5 points) 
	    Le principal inconvénient de cette architecture
	      mono-tâche est que, si une requête client prend du temps
	      à être traitée, les autres requêtes client
	      devront attendre que le traitement de cette requête soit
	      terminé.
	     
	    
	      serveurUDP_corrige_Q3.1.c
	     
	    
	      - 1 pts pour justification
 
	      - 2 pts pour les pthread_create et pthread_detach
 
	      - 0.5 pts pour le mutex évitant la race condition sur rangClient
 
	     
	    Des malus sont appliqués en fonction du code produit:
	    
	      - -0.5 en cas de code retour non testé
 
	      - -0.5 en cas de problème d'indentation
 
	      - -0.5 en cas de warning à la compilation
 
	     
	    ---endCorr 
	    Question 3.2
	    Le client fourni envoie actuellement le contexte de sa
	      requête au serveur,
	      i.e. nbReponsesAuClient le nombre de
	      réponses que le serveur a faite(s) à ce
	      client jusqu'ici.   Dans le
	      fichier Q3/reponses3.txt ou bien sur votre
	      copie, indiquez en quoi stocker le contexte au niveau du
	      client peut être un inconvénient.
	     
	    
	      Pour pallier cet inconvénient, nous allons
	      stocker le contexte au niveau du serveur. Pour retrouver
	      rapidement le contexte d'un client, nous allons le stocker
	      dans un arbre (binaire de recherche) Rouge-Noir manipulé avec la
	      bibliothèque libredblack
	      fournie dans Q3/libredblack-1.3.tar.gz.
	     
	    
	      Copiez Q3/Q3.1/serveurUDP.c dans le répertoire
	      Q3/Q3.2, puis : - Dans 
Q3/Q3.2/serveurUDP.c 
	      
		- Définissez une structure 
node_t constituée
		  d'une chaîne de caractères key et d'un entier
		  nbReponsesAuClient représentant le contexte d'une "connexion" entre un client et le serveur. 
		- Dans un terminal, tapez 
man -l Q3/libredblack-1.3/rbinit.3
		    pour accéder au man des
		    différentes fonctions de la
		    bibliothèque et à un
		    exemple. Lisez au moins le rôle des fonctions rbinit, rbsearch, rbfind
		  (qui seront utilisées dans cette question), rbwalk et rbdelete
		    (qui seront utilisées à la question 3.3).
		 
		- Déclarez la variable globale suivante : 
struct rbtree *rbtree; 
		- Implémentez la fonction 
int compare(const void *pa, const void *pb, const void *config) en 
		
		  - ignorant le paramètre 
config dans l'implémentation de compare,
		   
		  - renvoyant -1, 0 ou 1, selon que la valeur de la clé du nœud pointé par 
pa
		    est respectivement strictement inférieure, égale ou
		    strictement supérieure à la valeur de la clé du nœud pointé par pb  
		 
		- Dans 
main(), initialisez rbtree avec la fonction rbinit. 
		- Dans la fonction gestionRequete() :
 
		
		  - Calculez la clé associée à
		    l'émetteur de la requête en concaténant le contenu
		    de la variable 
host, la chaîne de caractère
		    ":" et le contenu de la variable service.
		   
		  - Cherchez cette clé dans 
rbtree avec la fonction
		    rbfind. Si rbfind ne trouve pas cette clé
		    dans rbtree, créez un nœud de
		    type node_t. Stockez-y la clé calculée
		    précédemment et initialisez nbReponsesAuClient
		    à 0. Puis, insérez le nœud dans rbtree avec
		    la fonction rbsearch.
		   
		  - Incrémentez 
nbReponsesAuClient de 1 : vous êtes
		    maintenant en mesure de répondre au client.
		   
		 
		- NB :
 
		
		  - Les fonctions de libredblack ne sont pas thread-safe. De ce fait,
		    quand nécessaire, veillez à protéger les accès
		    à 
rbtree en utilisant systématiquement une
		    exclusion mutuelle.
		   
		  - Il se peut que la ligne où vous appelez 
rbfind
		    vous génère le warning "warning: initialization discards ‘const’ qualifier from pointer target type"
		    ou "warning: assignment discards ‘const’ qualifier from pointer target type".
		    Dans ce cas, remplacez "rbfind(...)" par "(node_t *) rbfind(...)". 
		   
		 
	       
	      - Dans 
Q3/Q3.2/clientUDP.c, pour être sûr que
		votre serveur ne fonctionne plus grâce à des
		données de contexte envoyées par le client, remplacez la
		ligne "msgRequete.infoSup.nbReponsesAuClient = nbReponsesOK;" par la
		ligne "msgRequete.infoSup.nbReponsesAuClient = -42;"
	       
	      make, puis vérifiez le fonctionnement correct de votre serveur et de votre client. 
		NB : vous pouvez lancer plusieurs clients à la fois en tapant dans un terminal : for((i=1;i<=200;i+=1)); do (./clientUDP $i &);done 
	       
	     
	    
	      ---beginCorr (5 points) 
	     
	    
		Pour information : Nous avons préféré libredblack aux fonctions tsearch,
		tfind, tdelete, twalk et tdestroy
		qui permettent aussi de gérer un arbre binaire et qui, en plus, font
		partie de la bibliothèque C. En effet, il s'avère que ce lot de fonctions n'offre
		pas de fonction de suppression de nœud (cf. rbdelete) dont nous nous
		servirons à la question 3.3.Par ailleurs, il est probable (mais
		c'est à vérifier) que libredblack qui est basé sur l'algorithme
		rouge-noir est plus performant que ces fonctions de la librairie C,
		basées sur l'algorithme T de Knuth (cf. man tsearch).
	     
	    Lorsque le client envoie ses données de contexte au
	      serveur, il peut envoyer des données trafiquées de sorte
	      que les valeurs envoyées l'arrange. Par exemple, si les
	      données de contexte permettent de facturer le client, il peut
	      envoyer des données qui minimisent la facturation.
	     
	    
	      serveurUDP_corrige_Q3.2.c 
	     
	    - 1 pts pour justification
 
	       
              - 3 pts pour utilisation de libredblack
 
	      - 1 pts pour mutex autour des fonctions libredblack
 
	       
	     
	    Des malus sont appliqués en fonction du code produit:
	    
	      - -0.5 en cas de code retour non testé
 
	      - -0.5 en cas de problème d'indentation
 
	      - -0.5 en cas de warning à la compilation
 
	     
	    ---endCorr 
	    Question 3.3
	    
	      Dans le serveur codé précédemment, une fois qu'un
	      client a fini d'interagir avec le serveur, le nœud contenant ses données de
	      contexte reste éternellement dans rbtree.
	      L'objectif de cette question est d'écrire un thread ramasseNoeudsInutiles qui supprime les nœuds
	      inutiles de rbtree.
	     
	    
	      Le principe est le suivant :
	     
	    
	      - Nous ajoutons un entier 
lastNbReponsesAuClient à la
		structure node_t. Cet entier est initialisé
		à 0 lors de la création d'un nouveau nœud.
	       
	      - Toutes les 5 secondes, le thread 
ramasseNoeudsInutiles
		exécute la fonction rbwalk(rbtree, traitementNode, NULL)
		qui se charge d'appeler la fonction traitementCtxt()
		(décrite ci-dessous) pour chaque nœud stocké dans rbtree.
	       
	      - Nous codons la fonction 
traitementCtxt(const void *nodep, const VISIT unused1, const int unused2, void *unused3) selon l'algorithme suivant : 
	     
	    node_t *pnode = (node_t *) nodep; 
		si (which signale que ce noeud est une feuille ou bien que c'est la dernière visite à ce noeud) alors 
	          si (pnode->nbReponsesAuClient == pnode->lastNbReponsesAuClient) alors 
	              // Il n'y a plus de client qui utilise ce noeud : on peut le supprimer 
	              Appeler rbdelete pour supprimer ce noeud de rbtree 
	              Libérer ce noeud 
	          sinon 
	          
	          pnode->lastNbReponsesAuClient = mnode->nbReponsesAuClient 
	          
	      finsi 
		finsi 
	     
	    
	      Dans le fichier Q3/reponses3.txt ou bien sur votre copie, indiquez comment vous allez synchroniser ramasseNoeudsInutiles avec les threads gestionConnexion qui recherchent un contexte dans l'arbre rbtree ou bien insèrent un nouveau nœud. Justifiez votre proposition. 
	    
	      Copiez Q3/Q3.2/serveurUDP.c dans le répertoire Q3/Q3.3,
	      puis codez Q3/Q3.3/serveurUDP.c selon le principe présenté ci-dessus.
	     
	    
	      ---beginCorr (5 points) 
	     
	    serveurUDP_corrige_Q3.3.c 
	    
	    Pour synchroniser ramasseNoeudsInutiles avec les threads gestionConnexion,
	    nous pouvons choisir d'encadrer (void)rbwalk(rbtree, traitementCtxt, NULL); avec
	    une exclusion mutuelle basée sur le mutex
	    utilisé dans gestionConnexion. Si nous
	    faisons cela avec le corrigé de la question 3.2,
	    nous courons le risque que rbwalk s'exécute
	    et décide d'effacer un nœud, alors que gestionConnexion accède
	    à ce même nœud. C'est pourquoi, dans ce corrigé de la question 3.3,
	    pour maximiser le parallélisme des différents threads, nous choisissons
	    d'utiliser une synchronisation de type lecteurs-rédacteurs, où les
	    lecteurs sont les différents threads gestionConnexion dont
	    l'opération de "lecture" (en tant que lecteur) consiste à
	    accéder à un nœud (éventuellement en le
	    créant) pour incrémenter
	    nbReponsesAuclient, et où le rédacteur est le thread dont l'opération
	    de "rédaction" (en tant que rédacteur) est l'appel à rbwalk.
	     
	    
              - 2 pts pour justification
 
	       
              - 2 pts pour codage thread (avec le lecteurs-rédacteur pour encadrer l'appel à
		rbwalk)
 
	      - 1 pts pour le codage correct de 
traitementCtxt
	       
	     
	    Des malus sont appliqués en fonction du code produit:
	    
	      - -0.5 en cas de code retour non testé
 
	      - -0.5 en cas de problème d'indentation
 
	      - -0.5 en cas de warning à la compilation
 
	     
	    ---endCorr 
	    
	     |