Messagerie instantanée(12 heures)

Pour cette première partie, vous compléterez le code du dépôt ChatClient.

Client de messagerie

Ici, nous construirons un client de messagerie instantanée.

Le dépôt ChatClient que nous dupliquerons contient déjà deux classes :

Ces deux classes devront être complétées pour parvenir à un client fonctionnel.

Connexion / déconnexion

La classe centrale du client, Chat, devra être en mesure de dialoguer avec l'interface graphique qui sera bâtie autour d'elle.

La communication se fera à travers le mécanisme désormais bien connu des signaux et des slots.

Dans ce premier exercice, nous mettrons en place deux signaux, connected et disconnected, qui seront émis lorsque le socket intégré à la classe sera respectivement connecté ou déconnecté.

Ce socket, de la classe QTcpSocket, nous permettra de communiquer avec le monde extérieur.

Processeurs

Le protocole de communication adopté par le serveur repose sur trois règles simples :

  1. le client et le serveur peuvent envoyer des messages ou des commandes ;
  2. les commandes client débutent par le caractère / ;
  3. les commandes serveur débutent par #.
Opération Chaîne envoyée au serveur Réponse du serveur
Choisir un pseudonyme Première chaîne envoyée au serveur après ouverture de la connexion. En cas de réussite (pseudonyme valide et libre) :
  • #alias pseudonyme
  • #list pseudo1 pseudo2 …
  • #connected pseudonyme
    (diffusé à tous les autres utilisateurs connectés)
En cas d'échec :
  • #error invalid_alias
Envoyer un message Chaîne ne commençant pas par / Diffusion du message à tous les utilisateurs connectés
(y compris l'émetteur initial)
Envoyer un message privé /private destinataire message En cas de réussite :
  • #private émetteur message
    (envoyé au destinataire)
En cas d'échec :
  • #error missing_argument ou
  • #error invalid_recipient
Obtenir la liste des utilisateurs connectés /list #list pseudo1 pseudo2 …
Changer de pseudonyme /alias nouveau-pseudo En cas de réussite (pseudonyme valide et libre) :
  • #alias nouveau-pseudo
  • #renamed ancien-pseudo nouveau-pseudo
    (diffusé à tous les utilisateurs connectés)
En cas d'échec :
  • #error invalid_alias
Se déconnecter /quit
  • Fermeture de la connexion
  • #disconnected pseudo
    (diffusé à tous les utilisateurs restants)
Se tromper de commande /grrrr #error invalid_command

Ce protocole est également disponible ici.

Une méthode de traitement dédiée à un type de commande précis est appelée "processeur".

La boucle de lecture mise en place dans le constructeur Chat est chargée d'analyser les réponses du serveur. Si le premier mot d'une réponse correspond à une commande connue, le reste du message sera traité par le processeur adéquat. Sinon, la réponse sera traitée comme un message normal.

La recherche du processeur adéquat s'effectuera au sein du tableau associatif Chat::PROCESSORS.

N'hésitez pas à consulter la documentation associée au patron de classe std::map.

L'invocation d'une méthode à partir d'un objet et d'un pointeur sur membre est évoquée ici et .

Interface graphique

La classe ChatWindow est également incomplète.

Dans un premier temps, nous nous occuperons de la zone de saisie des messages.

Nous l'intégrerons dans la mise en page de l'interface graphique puis nous permettrons l'envoi de message. Ceux-ci seront émis dès que l'utilisateur appuiera sur la touche "entrée". Cet événement, matérialisé par le signal returnPressed, déclenchera l'envoi du texte présent dans la zone puis son effacement.

Dans un second temps, nous connecterons les signaux connected et disconnected émis par Chat.

En particulier, à chaque connexion, l'utilisateur devra saisir le pseudonyme qui servira à l'identifier auprès des autres utilisateurs. Cette saisie s'effectuera via QInputDialog::getText. Si le pseudonyme choisi est disponible, le serveur lui retournera un message validant ce choix ainsi que la liste des utilisateurs connectés. Sinon, un message d'erreur précédera une déconnexion pure et simple.

Commandes

Nous ajouterons ensuite à la classe Chat les processeurs et signaux manquants.

Commande Nom du signal
#alias user_alias
#connected user_connected
#disconnected user_disconnected
#renamed user_renamed
#list user_list
#private user_private

Le processeur de la commande serveur #error nous servira d'exemple pour les autres commandes.

Ensuite, dans ChatWindow, chaque signal sera connecté à une lambda-expression chargée de traiter les informations entrantes.

Pour l'instant, ces informations seront simplement retranscrites à l'écran sous forme textuelle.

Utilisateurs connectés

Pour cette seconde partie, vous compléterez le code du dépôt ChatServer.

Serveur de messagerie

Contrairement au client, le serveur n'utilise pas les bibliothèques Qt.

Il s'agit ici de code C++ standard, auquel est associé la biliothèque open source Asio.

Cette bibliothèque est dite header only car elle n'a pas besoin d'être compilée ; il suffit d'inclure ses en-têtes dans son code pour pouvoir exploiter ses fonctionnalités.

La version 1.12.2 est disponible localement : /home/bib/aassif/asio-1.12.2/.

Référez-vous à la documentation officielle.

Le projet ChatServer ne dispose pas de fichier .pro car il repose sur un Makefile.

Il se compile en ligne de commande en lançant make et génère l'exécutable server.

Pointeurs intelligents

Cet exercice repose également sur std::shared_ptr.

Comme nous faisons appel aux fonctions asynchrones de la bibliothèque Asio, nous devons veiller à ne jamais détruire un objet que nous avons associé à une lambda-expression sous peine de voir notre serveur interrompre brutalement son exécution.

En C++, la façon la plus simple de pallier ce problème est d'avoir recours à des pointeurs intelligents.

Ces pointeurs peuvent être de différentes natures : uniques, partagés ou faibles.

En l'occurrence, nous utilisons des pointeurs partagés dont l'allocation passe par une fonction dédiée, std::make_shared, et dont la destruction n'est déclenchée qu'une fois le dernier objet y faisant référence détruit.

Squelette du serveur

Pour en revenir au serveur, celui-ci est modélisé par une classe Server déclarée et définie dans un même fichier server.hpp.

Cette classe abrite une classe interne Client qui représente un client vu du serveur.

Pour la raison évoquée précédemment, les clients seront manipulés via des pointeurs intelligents.

La classe Server repose sur trois attributs d'instances privés :

Elle incoropore aussi un attribut de classe, Server::PROCESSORS, similaire à Chat::PROCESSORS.

Son code, largement incomplet, requiert de nombreuses modifications :

La classe Client déclare, elle, cinq champs privés :

Son code est quasiment complet : seule la méthode start doit être revue.

Le fonctionnement de cette méthode est linéaire mais néanmoins chargé. Elle doit  :

  1. recevoir le pseudonyme d'un client,
  2. le valider auprès de lui avec la commande #alias,
  3. lui envoyer la liste des utilisateurs connectés (#list),
  4. avertir tous les utilisateurs de l'arrivée d'un nouveau participant (#connected),
  5. déclencher l'attente de la prochaine commande.

Les méthodes find, broadcast et process_list seront utilisées dans la méthode start du client.

Travail à réaliser

HTML5