Nous avions le choix entre C et Java pour le code de cette application.
Le langage C, plus bas niveau, nous a paru intéressant pour deux raisons~:
\begin{enumerate}
\item Il permet de savoir précisément ce que l'on fait et donc de contrôler d'avantage de choses.
Il est également plus dangereux, dans le sens ou il ne pardonne pas les erreurs de gestion de mémoire ou d'implémentation.
La gestion des erreurs a été une fin en soi.
\item D'un point de vue pédagogique, l'absence d'objets et de fonctions de très haut niveau nous a obligés à prendre en considération un grand nombre de détails nécessaires à l'implémentation d'une telle application.
Point d'entrée de tout programme C, la fonction \texttt{main} commence par vérifier les arguments passés en ligne de commande.
Ensuite, elle ignore les signaux d'interruption, puisqu'ils seront gérés dans les processus fils.
Puis un socket est créé pour la communication avec le serveur. \\
Le prompt est instancié dans un espace de mémoire partagée (voir~\ref{shared-memory}). \\
Le \texttt{main} crée alors un sémaphore nommé (voir~\ref{named-semaphore}).
Pour éviter que différents clients éventuellement lancés sur la même machine partagent le même sémaphore, l'identifiant du client est alors récupéré pour être utilisé dans ce nom. \\
C'est aussi le moment de récupérer le nom du client (qui par défaut vaut l'ID), pour l'affecter au prompt. \\
Il ne reste plus qu'à faire deux \texttt{fork} successifs, pour le \texttt{Listener} et pour le \texttt{Sender}.
Le \texttt{main} pourra alors terminer son exécution quand les deux processus auront terminé.
En fonction du processus retourné, l'autre est alors tué. \\
Avant de terminer l'exécution du \texttt{main}, une fonction est lancée pour quitter proprement le programme.
\paragraph{\texttt{Listener}}
Cette fonction regroupe toutes les actions réalisées par le processus fils correspondant au \texttt{Listener}.
On commence par la gestion des signaux d'interruption \texttt{SIGINT} et \texttt{SIGTERM}.
Le handler appelé à la réception de ces signaux est décrit par la fonction \texttt{interrupt} (voir plus bas). \\
Le reste de la fonction consiste en une boucle infinie.
On lit caractère par caractère ce qui arrive sur le socket créé dans \texttt{main}.
Pour cela, on commence par une lecture d'un caractère, sachant que la fonction est bloquante.
Dès que quelque chose est reçu, le sémaphore est bloqué (\texttt{sem\_wait}) et on démarre une boucle de lecture jusqu'à un caractère de fin.
Chaque caractère lu est analysé puis envoyé sur \texttt{stdout}.
Le caractère \texttt{EOT} entraîne une fermeture de session, et le caractère \texttt{ETX} indique une fin de flux, ce qui déclenche la mise à jour du prompt et son impression.
Le sémaphore est alors libéré (\texttt{sem\_post}) et la boucle infinie redémarre. \\
L'utilisation de deux appels distints à \texttt{recv} a été rendu nécessaire par l'utilisation du sémaphore.
Le premier, bloquant, doit être fait avant le \texttt{sem\_wait}, sinon le \texttt{Listener} s'approprie le sémaphore aussi longtemps qu'il en a l'occasion.
Le second appel à \texttt{recv} est rendu non bloquant pour permettre de libérer le sémaphore en fin de flux.
\paragraph{\texttt{Sender}}
Tout comme pour le \texttt{Listener}, la fonction \texttt{Sender} est exécutée par le processus fils correspondant au \texttt{Sender}. \\
La boucle infinie est ici beaucoup plus simple que celle du \texttt{Listener}.
On commence par bloquer le sémaphore pour pouvoir écrire le prompt, puis le libérer tout de suite après.
Cela peut paraître court, mais il s'agit essentiellement de ne pas interrompre l'écriture du \texttt{Listener}. \\
La suite consiste simplement à lire une ligne complète (terminant par `\verb+\n+') grâce à \texttt{fgets}, puis de l'envoyer au serveur grâce au socket.
\paragraph{\texttt{handle\_args}}
Cette fonction permet d'arrêter très tôt l'exécution de notre programme si les arguments passés sont incorrects.
Dans ce cas, une simple explication de l'invocation en ligne de commande est affichée~:
La fonction \texttt{handle\_args} permet également de renvoyer à \texttt{main} le port à utiliser pour le socket.
En effet, il est possible de fournir ce numéro de port en \texttt{CLI} ou de l'omettre pour utiliser le port par défaut.
\paragraph{\texttt{clean\_and\_close}}
Cette fonction est simplement appelée à la toute fin de la fonction \texttt{main} afin de permettre une fermeture propre de la session.
La gestion des signaux (ignorés par le processus père et gérés seulement dans les fils) permet de s'assurer de l'exécution de cette fonction de nettoyage dans tous les cas. \\
Elle ferme le sémaphore, informe le serveur de la déconnexion (\texttt{cmd\_bye}), fait un \texttt{munmap} pour libérer la mémoire partagée pour le prompt, puis ferme le socket.
\paragraph{\texttt{interrupt}}
À part afficher un message de debug lors de la réception des signaux \texttt{SIGINT} et \texttt{SIGTERM}, cette fonction ne fait pas grand chose.
Elle est surtout utile dans la mesure ou la structure \texttt{sigaction} permet de n'appeler ce handler que dans le processus fils, le père ignorant les signaux pour éviter de mettre fin à la session trop tôt.
Les fonctions préfixées par \texttt{cmd\_} correspondent à des commandes, ici des commandes internes (mais qui peuvent très bien être appelées par l'utilisateur). \\
La commande \texttt{cmd\_hello} est utilisée pour deux choses~:
\begin{enumerate}
\item Elle permet au client de recevoir son identifiant, utile pour la création du sémaphore.
\item Elle informe le serveur de l'arrivée du client, lui permettant ainsi de l'enregistrer dans sa base.
\end{enumerate}
\paragraph{\texttt{cmd\_bye}}
Cette commande consiste simplement à envoyer le caractère correspondant à la commande \texttt{CMD\_BYE} au serveur, lui permettant de libérer les informations de ce client dans sa base.
\paragraph{\texttt{cmd\_get\_name}}
La commande \texttt{cmd\_get\_name} demande au serveur de renvoyer le nom d'utilisateur du client.
Ce nom, qui peut être amené à changer pendant la session, est stocké et géré par le serveur. \\
Le client en a simplement besoin pour l'afficher dans le prompt.
Ce buffer contient les messages lus par le \texttt{ClientListener} et qui seront récupérés par le Dispatcher pour traiter les messages un à un.
\paragraph{\texttt{ClientPipe}}
Le \texttt{ClientPipe} est un pipe qui permet la communication entre le \texttt{Dispatcher} et les différents \texttt{ClientSender}.
Chaque instance de \texttt{ClientSender} a accès en lecture au pipe affecté à son client.
Le \texttt{Dispatcher}, quant à lui, utilise la liste globale des clients pour écrire dans le pipe du bon client.
\paragraph{Liste des clients (\texttt{struct client\_details})}
La liste des clients est une liste globale qui contient pour chaque client les informations le concernant.
Pour cela, un \texttt{struct} contient les éléments suivants pour représenter un client~:
\begin{itemize}
\item\texttt{id}~:
L'identifiant du client, qui doit être égal à l'index de la structure dans la liste.
Ceci permet d'accéder à un client particulier en indexant simplement son \texttt{id} et d'affecter rapidement un nouveau client à l'index libre le plus bas.
Instance de \texttt{ClientPipe} pour la communication entre le \texttt{Dispatcher} et le \texttt{ClientListener} de ce client.
\item\texttt{listener\_thread}~:
Le numéro du thread créé pour le \texttt{ClientListener}.
Il est utilisé pour permettre la déconnexion d'un client en particulier en mettant fin au thread.
\item\texttt{sender\_thread}~:
Le numéro du thread créé pour le \texttt{ClientSender}.
Il est utilisé pour permettre la déconnexion d'un client en particulier en mettant fin au thread.
\end{itemize}
La longueur de cette liste est prédéfinie grâce à la constante \texttt{MAX\_CLIENTS}.
\paragraph{Liste des groupes (\texttt{struct group\_details})}
La liste des groupes et aussi une liste globale du même type que la liste des clients.
Sa longueur est définie à \texttt{MAX\_GROUPS} et chaque \texttt{struct} la composant contient les éléments suivants~:
\begin{itemize}
\item\texttt{id}~:
L'identifiant du groupe, qui fonctionne de la même façon que pour la liste des clients.
\item\texttt{name}~:
Le nom affecté au groupe lors de sa création.
\item\texttt{clients}~:
Une liste de longueur \texttt{MAX\_CLIENTS}, qui est une table de vérité.
Chaque index dans cette liste identifie un client, et le booléen indique la présence ou non du client dans le groupe.
Ceci permet de ne garder aucune information de groupe dans la liste des clients, et de vérifier très rapidement si un client fait partie d'un groupe, sans parcourir de liste.
Pour cela, la liste des groupes est à parcourir une fois, en mettant le booléen du client à 0 dans la liste des clients de chaque groupe.
De plus, si un groupe contient le client à déconnecter, la fonction appelle \texttt{delete\_group\_if\_empty} pour supprimer le groupe s'il est nouvellement vide. \\
Puis, les threads \texttt{ClientListener} et \texttt{ClientSender} sont terminés. \\
Enfin, les champs du \texttt{struct} du client dans la liste des clients sont remis à leur valeur par défaut, après avoir fermé les file descriptors du socket et du \texttt{ClientPipe}.
\paragraph{\texttt{Dispatcher}}
La fonction \texttt{Dispatcher} est appelée par le thread \texttt{Dispatcher}. \\
Dans une boucle, les messages sont lus depuis le \texttt{MessageBuffer}.
Chaque message finit par `\verb+\n+', puisqu'il a été envoyé depuis la ligne de commande du client. \\
Commence alors le parsing.
Nous avons décidé de ne pas créer de fonction séparée pour parser les messages, car le message peut être parsé en plusieurs fois.
On commence donc par extraire l'identifiant du client émetteur.
Vient ensuite la commande.
Les commandes ont été définies par un seul caractère à chaque fois.
Le message arrivant étant découpé par espaces, le \texttt{Dispatcher} prend en compte seulement le premier caractère et ignore les autres.
Ceci permet d'être résilient aux fautes de frappes. \\
Le reste de la fonction consiste en un \texttt{switch}.
En fonction de la commande reçue, la fonction correspondante est appelé avec, si besoin, un traitement préliminaire. \\
Un exemple de parsing en plusieurs fois peut être trouvé pour la commande \texttt{cmd\_list}.
Une sous-commande \texttt{cmd\_list\_groups} existe, mais n'est évidemment pas vérifiée si l'on ne se situe pas dans la commande \texttt{cmd\_list}. \\
Le parsing pour la commande \texttt{cmd\_send\_message} est un peu plus complexe.
Il faut extraire le nom du destinataire, obtenir son identifiant, obtenir un groupe portant ce nom si aucun client n'a été trouvé, puis envoyer le message (au client ou au groupe, dépendant de ce qui a été trouvé). \\
Code correspondant au thread du \texttt{ClientListener}. \\
Nous avons un cas d'exemple de synchronisation~: dans la fonction \texttt{main}, le thread du \texttt{ClientListener} est démarré avant d'être ajouté au \texttt{struct} du client.
Cela veut dire qu'au démarrage de son exécution, le \texttt{ClientListener} peut ne pas encore avoir totalement accès aux éléments constituant le client.
Ces éléments sont à priori enregistrés avant le démarrage du thread, mais dans le cadre de la programmation défensive, une boucle permet au \texttt{ClientListener} d'attendre l'enregistrement complet du client avant de poursuivre son exécution. \\
Cette fonction est appelée par le thread du \texttt{ClientSender}. \\
La même boucle de synchronisation que pour le \texttt{ClientListener} a lieu.
Puis ce qui est lu dans le \texttt{ClientPipe} est envoyé au client via son socket.
\paragraph{\texttt{clean\_and\_close}}
Le serveur a tout comme le client une fonction pour quitter proprement le programme.
Elle est appelée par le handler lors de la capture des signaux \texttt{SIGINT} et \texttt{SIGTERM}. \\
Cette fonction commence par déconnecter tous les clients~: pour chacun d'entre eux, un message \texttt{EOT} est envoyé, puis la fonction \texttt{disconnect\_client} est appelée. \\
Le \texttt{MessageBuffer} et le mutex des \texttt{ClientListener} sont fermés, puis le thread du \texttt{Dispatcher} est tué.
Enfin, le socket du serveur est fermé pour mettre fin à la connexion.
\paragraph{\texttt{interrupt}}
Les signaux \texttt{SIGINT} et \texttt{SIGTERM} sont capturés pour permettre d'appeler la fonction \texttt{clean\_and\_close}.
Si le nom est déjà pris, la modification est refusée.
\paragraph{\texttt{cmd\_get\_name}}
Commande interne correspondant à la commande \texttt{cmd\_get\_name} du client, qui l'utilise pour modifier son prompt.
Cette fonction se contente donc d'envoyer au client son nom.
\paragraph{\texttt{cmd\_list\_clients}}
Envoie au client la liste des clients connectés. \\
Pour envoyer le message d'un coup tout en ajoutant les clients connectés au fur et à mesure du parcours de la liste des clients, une série de \texttt{malloc} est faite.
Un \texttt{char*} temporaire permet d'agrandir progressivement le message en libérant le \texttt{malloc} précédent.
\paragraph{\texttt{cmd\_list\_groups}}
Envoie au client la liste des groupes et les clients présents dans chaque groupe. \\
Cette fonction suit le même principe que la fonction \texttt{cmd\_list\_clients} avec la complexité supplémentaire d'ajouter une chaîne de \texttt{malloc} pour afficher chaque client au sein de chaque groupe.
\paragraph{\texttt{cmd\_broadcast}}
Pour broadcaster un message, il suffit de l'envoyer à chaque client connecté en ignorant l'émetteur.
\paragraph{\texttt{cmd\_send\_message}}
Fonction de base pour l'envoi d'un message.
Comme elle est utilisée par d'autres fonctions, la gestion de l'émetteur et du destinataire est laissée au \texttt{Dispatcher}. \\
La fonction \texttt{cmd\_send\_message} reçoit les identifiants émetteur et destinataire, ainsi que le message à envoyer.
Le message est également suffixé par le caractère \texttt{ETX} (\emph{end text}), pour signifier au \texttt{Listener} du client en face que le flux est terminé.
\paragraph{\texttt{send\_group\_message}}
Il ne s'agit en fait pas d'une commande à part entière, mais d'une fonction correspondant à une sous-commande de \texttt{cmd\_send\_message}.
Ceci est expliqué dans la fonction \texttt{Dispatcher}. \\
Semblablement à \texttt{cmd\_broadcast}, un message est envoyé à tous les clients d'un groupe.
Un protocole de communcation a été défini pour que le client et le serveur puissent correctement échanger les données.
Ainsi, le fichier header \texttt{common.h} contient des définitions de commandes qui correspondent chacune à un aspect fonctionnel du programme.
\subsection{Symbole de fin de transmission}
Pendant l'exécution du programme, le client lit sur la sortie standard jusqu'au premier retour à la ligne (`\verb+\n+').
Le serveur attend donc le `\verb+\n+' comme signe d'une fin de transmission. \\
Lors de l'initialisation de la session, le client et le serveur vont s'échanger quelques messages avec `\verb+\n+' comme caractère de fin de transmission.
Mais pour la suite des échanges, le `\verb+\n+' ne pourra pas être utilisé comme symbole de fin de transmission par le serveur, car le message à envoyer peut contenir des retours à la ligne (par exemple \texttt{cmd\_help}).
Le caractère \texttt{ETX} (\emph{end of text}), qui correspond au `\verb+\03+', sera alors utilisé pour les messages en provenance du serveur.
Maintenant que la session est initialisée, le client va simplement envoyer tel quel ce que l'utilisateur aura tapé.
C'est à l'utilisateur de taper les bonnes commandes.
Pour cela il y a la commande \texttt{CMD\_HELP} (h).
Les messages reçus par le serveur sont découpés grâces aux espaces, puis le premier caractère de la commande est analysé.
Ceci autorise l'utilisateur à faire certaines fautes de frappes~: \texttt{h}, \texttt{help} ou \texttt{hippopotame} donneront tous trois la commande \texttt{cmd\_help}.
Côté serveur, le \texttt{ClientListener} va transmettre le message au \texttt{MessageBuffer} après l'avoir préfixé de l'identifiant du client suivi d'un espace.
Le \texttt{Dispatcher} pourra alors parser le message qui doit être de la forme suivante~: