Skip to content

oflasfar/2048-Bits

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

77 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Rapport SAÉ — Back to 2048

LASFAR Omar Farouk ROCHE Raphael ERDAL Muhamed

Ce que nous avons changé par rapport au sujet initial

Le sujet initial reposait sur un unique processus main qui capturait les entrées clavier et les transmettait à un processus 2048 via un pipe nommé. Nous avons fait évoluer cette architecture vers un modèle client-serveur pour supporter plusieurs parties simultanées.

Le processus main est devenu le client : il conserve son rôle de capture clavier et d'affichage, mais communique désormais avec un serveur centralisé plutôt qu'avec son propre processus de jeu. Le processus 2048 est devenu ce serveur, qui gère la logique de toutes les parties en parallèle.

Choix d'architecture

Communication client <=> serveur

Tous les clients partagent un uique pipe nommé pour envoyer leurs commandes au serveur. Pour que le serveur sache à qui répondre, chaque message embarque le PID du client :

typedef struct {
    pid_t client_pid;
    char  move;
} client_message_t;

Pour la réponse, chaque client crée à son démarrage son propre pipe de retour /tmp/2048_<PID client>. Le serveur y écrit la grille mise à jour après chaque mouvement. Ce choix évite d'avoir à gérer un pipe par client côté serveur.

Côté client : deux processus

Le client se fork en deux processus :

  • Le père capture les entrées clavier et les envoie au serveur.
  • Le fils lit en boucle sur le pipe de retour et rafraîchit l'affichage.

Cette séparation permet de ne pas bloquer la lecture clavier pendant l'affichage et vice-versa.

Côté serveur : quatre threads

Le serveur tourne avec quatre threads permanents :

Thread Rôle
thread_main (boucle principale) Lit le pipe nommé, identifie le client, dispatche le mouvement
thread_move Applique le déplacement sur la grille
thread_goal Vérifie victoire/défaite, renvoie la grille au client
thread_admin Écoute q au clavier pour arrêter le serveur proprement

Mémoire partagée et synchronisation

Les données de toutes les sessions sont stockées dans un segment de mémoire partagée, alloué au démarrage selon le nombre de joueurs passé en argument (./serveur nb_joueurs). Chaque slot contient la grille, le PID du client, le dernier mouvement et un champ state.

Ce champ state pilote la coordination entre les threads comme un automate :

state = 0  →  thread_main écrit le mouvement  →  state = 1
state = 1  →  thread_move applique le coup     →  state = 2
state = 2  →  thread_goal vérifie et répond    →  state = 0

L'accès à la mémoire partagée est protégé par mutex unique. Trois variables de condition (cond_move, cond_goal, cond_main) permettent à chaque thread de dormir jusqu'à ce que du travail lui soit destiné, sans attente active.

Arrêt propre

Quand le serveur reçoit q, il envoie SIGUSR1 à tous les clients encore connectés. Chaque client intercepte ce signal pour arrêter son fils d'affichage, nettoyer son pipe de retour et quitter sans laisser de ressources orphelines. SIGPIPE est ignoré côté client et serveur pour éviter les crashes lors de fermetures inattendues de pipes.

Difficultés rencontrées

  • Le problème du Game Over et de la lecture bloquante : Si le jeu se terminait, le processus d'affichage ou le client pouvait rester bloqué indéfiniment en attente d'une lecture. La solution a été d'utiliser l'option O_NONBLOCK sur les tubes et de bien manipuler les signaux de fermeture.
  • L'erreur fatale SIGPIPE : Lorsqu'un client quittait la partie et que le serveur tentait d'écrire la nouvelle grille dans son pipe devenu inexistant, le serveur crashait entièrement. Il a fallu trouver comment ignorer explicitement le signal SIGPIPE.
  • La synchronisation des threads sans Deadlock : Passer d'une boucle d'attente active (while) à une synchronisation fine avec pthread_cond_t a été complexe. Il fallait s'assurer que les threads Move et Goal se réveillent dans le bon ordre sans bloquer le Mutex indéfiniment.

Fonctionnalités (Ce qui marche)

  • Le multijoueur simultané : Le serveur gère plusieurs parties en même temps sans aucun lag ni mélange de grilles.
  • Zéro gaspillage CPU : Grâce aux variables de condition, les threads s'endorment à 0% d'utilisation processeur lorsqu'ils attendent un coup.
  • La gestion des déconnexions brutales (Ghost Buster) : Le serveur vérifie l'état des clients avec kill(pid, 0). Si un client ferme brutalement son terminal, sa place mémoire est automatiquement libérée.
  • Fermeture sans fuite mémoire : Le nettoyage de la mémoire partagée (IPC_RMID) et des tubes est systématique. Valgrind affiche 0 erreur et 0 fuite.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages