Création d'un système de chat en Pascal

Cet article a pour but de vous apprendre à créer un système de chat entièrement en Pascal. Il fait suite au
Défi Pascal 2010 : Création d'un système de chat.

2 commentaires Donner une note à l'article (5)

Article lu   fois.

L'auteur

Profil ProSite personnel

Liens sociaux

Viadeo Twitter Facebook Share on Google+   

Introduction

Dans ce tutoriel, vous apprendrez à créer un système de chat en Pascal, permettant à plusieurs personnes de communiquer entre elles. Pour ce tutoriel, les bases de la Programmation Orientée Objet (POO) ainsi que la connaissance des threads sont nécessaires.

1. Présentation

1-A. Analyse du problème

Le but recherché est de pouvoir échanger du texte entre plusieurs ordinateurs. Pour cela, nous avons le choix entre deux protocoles de communication principaux, le TCP et l'UDP.

UDP pour User Datagram Protocol est un protocole de communication simple : il ne nécessite pas de procédure de connexion avant l'envoi des données, on dit qu'il fonctionne en mode "non connecté". Son principal atout est la rapidité de transmission, mais il ne garantit pas la réception du paquet.

TCP pour Transmission Control Protocol est un protocole de communication fiable : il fonctionne en mode connecté, c'est-à-dire que l'envoi de données doit être précédé d'une phase de connexion avec l'ordinateur distant. Ce protocole garantit donc la réception du message, mais il est bien plus lent que UDP. C'est celui que nous allons utiliser.

1-B. Architecture

L'architecture du chat peut prendre deux formes :

  • un ordinateur joue le rôle de serveur, il reçoit et envoie tous les messages aux différents clients ;
  • chaque ordinateur est à la fois serveur et client, c'est-à-dire que chaque ordinateur s'occupe d'envoyer à tous les clients chaque message.

Par souci de simplicité, nous utiliserons la première configuration.

1-C. Outils et composants

Nous allons maintenant choisir un composant qui gère le protocole TCP. J'ai choisi la bibliothèque de composants Indy 10, qui propose deux composants simples à utiliser, IdTCPServer et IdTCPClient.

2. Fonctionnement des composants TCP Indy

Du côté serveur comme du côté client, il est nécessaire d'utiliser un thread qui aura pour rôle de vérifier régulièrement la réception des messages.

Le composant IdTCPServer crée automatiquement un thread (un IdContext) propre à chaque client lors de sa connexion. Ce thread appelle l'évènement OnConnect, puis l'évènement OnExecute en boucle. Cet évènement OnExecute nous permettra de vérifier régulièrement la réception d'un message. Lors de la déconnexion, l'évènement OnDisconnect est appelé. Pour communiquer avec un client particulier, il suffit de récupérer son IdContext dans la liste des IdContext du composant.

Le composant IdTCPClient ne crée pas de thread, il sera donc nécessaire de le faire nous-mêmes. Pour commencer l'échange, il faut lancer la procédure de connexion, et si aucune exception n'est levée, nous pourrons continuer l'échange. Notre thread aura pour rôle d'appeler en boucle la méthode servant à la lecture des messages.

Comme les évènements sont appelés à partir du thread, il faut, si nécessaire, utiliser les méthodes de synchronisation pour accéder aux objets ou aux éléments de la VCL (sous Delphi) ou LCL (sous Lazarus).

3. Mise en place du chat

Pour faire communiquer les composants, nous utiliserons les méthodes ReadLn et WriteLn de la classe IOHandler, donc les messages seront de simples chaines de caractères.

3-A. Serveur

Nous allons créer un serveur basique. À la réception d'un message, le serveur le renverra à tous les clients. Nous allons donc créer une fiche avec seulement un composant IdTCPServer dessus.

Mettre la propriété Active du composant à true, et DefaultPort à 3333 (par exemple)

Les évènements OnConnection et OnExecute
Sélectionnez
procedure TFServeur.IdTCPServer1Connect(AContext: TIdContext);
begin
  //Envoi d'un message au client qui vient de se connecter
  AContext.Connection.IOHandler.WriteLn(' --- Bienvenue sur le serveur --- ');
end;
 
procedure TFServeur.IdTCPServer1Execute(AContext: TIdContext);
var Recept : string;
    ListeContext : TList;
    i : integer;
begin
  //Dès qu'un message est disponible, nous enregistrons l'IP du client et le message dans la variable
  Recept:=AContext.Binding.PeerIP+' : '+AContext.Connection.IOHandler.ReadLn;
 
  //Nous récupérons la liste des IdContext qui correspond à la liste des clients
  ListeContext:=IdTCPServer1.Contexts.LockList;
 
  //Pour chaque client, on envoie le message précédemment stocké dans la variable Recept
  for i:=0 to ListeContext.Count-1 do
    TIdContext(ListeContext.Items[i]).Connection.IOHandler.WriteLn(Recept);
 
  //Comme nous avons appelé LockList, nous devons débloquer la liste.
  IdTCPServer1.Contexts.UnlockList;
 
  //À partir d'ici, le composant va rappeler l'évènement OnExecute, après avoir vérifié que la connexion est toujours active
end;

3-B. Client

Pour le client, nous avons besoin d'une fiche un peu plus complète :

Image non disponible
Exemple d'interface pour le client


Le champ Hôte permettra la saisie de l'adresse IP de l'ordinateur hôte. Notre fiche s'appelle FClient, et les composants sont :

Liste des composants
Sélectionnez
    IdTCPClient1: TidTCPClient;         //Serveur TCP
    EMessage: TEdit;                    //Zone de saisie des messages
    BConnect: TButton;                  //Bouton de connexion
    BDisconnect: TButton;               //Bouton de déconnexion
    BSend: TButton;                     //Bouton d'envoi des messages
    MChat: TMemo;                       //Zone d'affichage des messages
    EHost: TLabeledEdit;                //Zone de saisie de l'adresse IP hôte


Comme nous avons dit plus haut, il nous faut créer un thread qui s'occupera de lire les nouveaux messages. Voici sa définition :

Définition du thread de réception
Sélectionnez
type
  //Le type de l'évènement OnReception
  TReceptEvent = procedure (Sender : TObject; Recept : string) of object;
 
  TReceptionthread = class(TThread)
  private
    FRecept : string;
    FIdClient : TIdTCPClient;
    FOnReception : TReceptEvent;
  protected
    procedure Execute; override;
    procedure DoOnReception;
  public
    constructor Create (CreateSuspended : boolean; AIdTCPClient : TIdTCPClient);
 
    //L'évènement OnReception
    property OnReception : TReceptEvent read FOnReception write FOnReception;
  end;


Le code associé à cet objet est :

Code du thread de réception
Sélectionnez
constructor TReceptionThread.Create (CreateSuspended : boolean; AIdTCPClient : TIdTCPClient);
begin
  inherited Create(CreateSuspended);
  FIdClient:=AIdTCPClient;
end;
 
procedure TReceptionThread.DoOnReception;
begin
  //Si l'évènement OnReception a été assigné, alors on l'exécute en lui fournissant les bons paramètres
  if Assigned(FOnReception) then
    FOnReception(Self,FRecept);
end;
 
procedure TReceptionThread.Execute;
begin
  //On exécute en boucle jusqu'à ce que le thread se termine
  while not Terminated do
  begin
    FRecept:='';
 
    //On utilise une structure Try Except qui permet de détecter la déconnexion du client.
    //Si le client se déconnecte, ReadLn provoque une exception, et donc le thread se terminera.
    try
      FRecept:=FIdClient.IOHandler.ReadLn;
    except
      Terminate;
    end;
 
    if FRecept<>'' then
    //On synchronise l'évènement OnReception pour pouvoir utiliser les éléments de la VCL
      Synchronize(DoOnReception);
  end;
end;


Nous allons maintenant coder la fiche pour qu'elle réponde à nos attentes : après avoir cliqué sur Connecter, l'utilisateur pourra taper du texte dans le champ associé et l'envoyer grâce au bouton Envoyer. Voici le code associé à la fiche, n'oubliez pas d'ajouter les définitions suivantes dans l'objet FClient.

Définition dans l'objet FClient
Sélectionnez
FReceptionThread : TReceptionThread;
procedure ReceptionMessage (Sender : TObject; Recept : string);


Code de la fiche FClient
Sélectionnez
procedure TFClient.BConnectClick(Sender: TObject);
begin
  //Nous vérifions si l'adresse IP saisie est valide, dans le cas contraire, nous en informons l'utilisateur
  if not GStack.IsIP(EHost.Text) then
    ShowMessage('L''adresse IP saisie est invalide.')
  else
  begin
    try
      //Nous nous connectons. Si cette ligne déclenche une exception, la suite ne sera pas exécutée.
      //Le port saisi ici doit être le même que celui du serveur.
      IdTCPClient1.Connect(EHost.Text,3333);
 
      //Nous créons notre thread destiné à gérer la réception des messages.
      FReceptionThread:=TReceptionthread.Create(false,IdTCPClient1);
      // Nous lui affectons aussi son évènement OnReception.
      FReceptionThread.OnReception:=ReceptionMessage;
 
      //Nous activons les composants de notre fiche
      BDisconnect.Enabled:=true;
      BConnect.Enabled:=false;
      BSend.Enabled:=true;
      EMessage.Enabled:=true;
      MChat.Enabled:=true;
    except
      ShowMessage('Connexion avec le serveur impossible.');
    end;
  end;
end;
 
procedure TFClient.BDisconnectClick(Sender: TObject);
begin
  //procédure de déconnexion
  IdTCPClient1.Disconnect;
 
  //Désactivation des composants de la fiche
  BDisconnect.Enabled:=false;
  BConnect.Enabled:=true;
  BSend.Enabled:=false;
  EMessage.Enabled:=false;
  MChat.Enabled:=false;
end;
 
procedure TFClient.BSendClick(Sender: TObject);
begin
  //Envoi du texte de l'édit
  IdTCPClient1.IOHandler.WriteLn(EMessage.Text);
  EMessage.Clear;
end;
 
procedure TFClient.ReceptionMessage (Sender : TObject; Recept : string);
begin
  //Évènement OnReception de notre objet TReceptionThread
  //Si nous n'avions pas utilisé la méthode Synchronize, l'utilisation du Mémo Mchat pourrait poser des problèmes.
 
  MChat.Lines.Add(Recept);
end;


Nous venons de créer un chat basique qui permet d'échanger du texte entre plusieurs ordinateurs.


Télécharger les sources du Système de Chat v1 (Miroir)

4. Tests et utilisation

Il est maintenant temps de tester notre système. Pour cela, il ne nous est pas nécessaire d'avoir deux ordinateurs connectés en réseau : un seul suffit. Il vous suffit de choisir comme adresse hôte l'adresse « 127.0.0.1 ». C'est une adresse de bouclage (localhost) qui correspond à l'ordinateur local. Elle permet de simuler un réseau sur un seul ordinateur.

Pour déboguer le serveur, il vous suffit de l'ouvrir avec votre compilateur, et d'exécuter en même temps le client. De même, si vous voulez déboguer le client, ouvrez-le dans votre compilateur et exécutez le serveur à part.

Pour tester votre système sur un réseau local, il vous suffit d'exécuter un serveur sur un ordinateur, et d'exécuter les clients sur tous les autres ordinateurs. L'adresse IP hôte à saisir devra être celle du serveur.

Sous Windows, pour obtenir l'adresse IP de votre ordinateur, ouvrez une invite de commande et saisissez « ipconfig ». Vous n'avez plus qu'à lire l'adresse IPv4 de la carte réseau qui correspond à la connexion utilisée (Réseau sans fil si vous êtes connecté par Wi-fi, Réseau local sinon).
Sous Linux, l'adresse IP s'obtient en saisissant la commande « ifconfig » dans la console.

5. Amélioration du système et création d'un protocole de communication

Notre système fonctionne, mais il est très limité. Il se contente d'envoyer du texte entre plusieurs utilisateurs. Il serait intéressant, par exemple, de savoir qui est réellement connecté grâce à des pseudos, ainsi qu'une liste des personnes connectées. Nous devrons donc différencier les messages « texte » (envoyés par les utilisateurs, destinés à être affichés) des messages « techniques » (destinés à effectuer une action précise, par exemple, la connexion d'un nouveau client doit ajouter une ligne à la liste des personnes connectées).

Il apparaît un problème : nous devons être capables d'envoyer dans un seul et même message plusieurs informations. C'est pour cela que nous allons créer un protocole de communication qui nous permettra de gérer tout cela facilement.

5-A. Le protocole de communication

Nous allons donc définir un protocole de communication personnel, qui nous permettra d'envoyer plus d'informations qu'un simple texte. Autrement dit, nous allons nous imposer une structure pour les chaines de caractères, qui nous permettra de stocker plusieurs informations à la fois, mais aussi de différencier les messages purement techniques d'avec les messages « texte ». De plus, pour éviter d'envoyer dans chaque message le pseudonyme de l'émetteur, nous allons leur attribuer un identifiant unique (ID) lors de leur connexion. Cet identifiant sera simplement un entier, qui sera envoyé sur trois caractères dans les messages.

Voici un exemple de protocole que nous pouvons utiliser :

Types de messages du serveur vers le client

Repère Structure Signification Exemple
* * + ID + Contenu Message « texte » '*001Salut'
O O + ID Connexion établie 'O001'
N N + Raison Connexion échouée 'NPseudo déjà utilisé'
C C + ID + Pseudo Annonce de connexion d'un membre 'C001Mick605'
D D + ID Annonce de déconnexion d'un membre 'D001'


Types de messages du client vers le serveur

Repère Structure Signification Exemple
* * + ID + Contenu Message « texte » '*001Salut'
C C + Pseudo Demande de connexion au serveur 'CMick605'


Ainsi, chacune de ces structures de message sera analysée de manière différente, côté client aussi bien que côté serveur. Le premier caractère d'un message nous permet directement de différencier la procédure à suivre. Le découpage des messages et la récupération des informations seront simples, car nous savons que les ID sont composés de trois caractères.

5-B. Connexion d'un client

La connexion d'un client est un peu spéciale : le serveur doit vérifier plusieurs paramètres comme par exemple, si une place est disponible, ou que le pseudonyme n'est pas déjà utilisé par un autre client. De plus, le serveur doit fournir au client son identifiant ainsi que la liste des personnes connectées. Voici un schéma de la procédure de connexion d'un client.

Image non disponible

Chaque flèche représente l'envoi d'un message d'un ordinateur à l'autre. Dans chaque cadre jaune est noté un exemple de message pouvant être envoyé, en utilisant le protocole vu en 5.A.

5-C. Analyse et décomposition des messages

Maintenant que nous avons défini notre protocole, il nous faut créer les méthodes permettant de décomposer et d'analyser les chaines de caractères reçues, autant pour le serveur que pour le client.

5-C-1. Côté client

Nous modifions notre fiche pour y ajouter deux composants : un Edit nommé Epseudo pour saisir le pseudonyme, et une ListBox nommée LBClientsConnectes qui contiendra la liste des clients connectés.

Image non disponible
Exemple d'interface pour le client


Nous modifions aussi notre code pour y inclure les variables nécessaires. Nous déclarons un entier nommé MonID destiné à contenir l'ID du client, et un tableau de chaine de caractères nommé Users qui contiendra le pseudonyme de tous les utilisateurs. L'identifiant ID jouera le rôle d'indice pour le tableau, c'est-à-dire que le client dont l'identifiant sera 003 sera stocké dans la case 3 du tableau Users.

Nous modifions le code de connexion :

Procédure de demande de connexion
Sélectionnez
procedure TFClient.BConnectClick(Sender: TObject);
begin
  if EPseudo.Text='' then
    ShowMessage('Vous devez saisir un pseudo.')
  else
  begin
    //Nous vérifions si l'adresse IP saisie est valide, dans le cas contraire, nous en informons l'utilisateur
    if not GStack.IsIP(EHost.Text) then
      ShowMessage('L''adresse IP saisie est invalide.')
    else
    begin
      try
        //Nous nous connectons. Si cette ligne déclenche une exception, la suite ne sera pas exécutée.
        //Le port saisi ici doit être le même que celui du serveur.
        IdTCPClient1.Connect(EHost.Text,3333);
 
        //Envoi du message de demande de connexion. À ce stade, nous ne sommes pas encore en capacité de
        //communiquer avec les autres clients. Le serveur doit d'abord vérifier quelques paramètres, et
        //nous renvoyer une réponse
        IdTCPClient1.IOHandler.WriteLn('C'+EPseudo.Text);
 
        //Nous créons notre thread destiné à gérer la réception des messages.
        FReceptionThread:=TReceptionthread.Create(false,IdTCPClient1);
        // Nous lui affectons aussi son évènement OnReception.
        FReceptionThread.OnReception:=ReceptionMessage;
      except
        ShowMessage('Connexion avec le serveur impossible.');
      end;
    end;
  end;
end;


Nous devons aussi modifier le code permettant d'envoyer le texte (bouton BSend) pour prendre en compte le protocole que nous avons établi, c'est-à-dire que les messages texte doivent commencer par * suivi de l'ID de l'émetteur :

Procédure d'envoi d'un message Texte
Sélectionnez
procedure TFClient.BSendClick(Sender: TObject);
begin
  //Envoi du texte de l'edit
  IdTCPClient1.IOHandler.WriteLn('*'+Format('%3i',[MonID])+EMessage.Text);
  EMessage.Clear;
end;


Et nous modifions enfin notre procédure de réception des messages :

Procédure ReceptionMessage
Sélectionnez
procedure TFClient.ReceptionMessage (Sender : TObject; Recept : string);
  //Évènement OnReception de notre objet TReceptionThread
 
 
  function ExtractIDUser (Mess : string) : integer;
  //fonction qui extrait les trois premiers caractères du message et retourne l'ID
  begin
    if Length(Mess)>=2 then
      Result:=StrToIntDef(Mess[1]+Mess[2]+Mess[3],-1)
    else
      Result:=-1;
  end;
 
 
  procedure ConnectionSucceed (R : string);
  //R contient un message de connexion réussie de la forme << ID >> qui signifie que nous sommes connectés avec l'identifiant ID
  begin
    //MonID est une variable globale contenant notre identifiant
    MonID:=ExtractIDUser(R);
 
    //Stockage de notre pseudo dans le tableau d'utilisateurs
    Users[MonID]:=EPseudo.Text;
 
    //Nous annonçons notre connexion et nous activons les composants nécessaires
    MChat.Lines.Add(' --- Connexion établie --- ');
    BDisconnect.Enabled:=true;
    BConnect.Enabled:=false;
    BSend.Enabled:=true;
    EMessage.Enabled:=true;
    MChat.Enabled:=true;
    EPseudo.Enabled:=false;
    LBClientsConnectes.Items.Add('- Liste des clients connectés -');
 
    //Nous ajoutons notre pseudo dans la ListBox
    LBClientsConnectes.Items.Add(EPseudo.Text);
  end;
 
  procedure ConnectionFailed (R : string);
  //R contient un message de connexion échouée de la forme << Raison >> qui signifie que nous ne sommes pas connectés car : Raison
  begin
    //Nous affichons les informations nécessaires
    MChat.Lines.Add(' --- Connexion échouée --- ');
    MChat.Lines.Add('Raison : '+R);
 
    //Nous nous déconnectons du serveur
    IdTCPClient1.Disconnect;
  end;
 
  procedure UserConnection(R : string);
  //R contient un message de connexion de la forme << ID + Pseudo >> qui signifie que Pseudo vient de se connecter avec l'identifiant ID
  var ID : integer;
      Pse : string;
  begin
    //ID contient l'identifiant du message
    ID:=ExtractIDUser(R);
    //Pse contient le Pseudo
    Pse:=Copy(R,4,MaxInt);
    //Nous enregistrons l'utilisateur dans le tableau
    Users[ID]:=Pse;
 
    //Nous annonçons la connexion du client
    MChat.Lines.Add(Pse+' vient de se connecter.');
 
    //Nous ajoutons son nom dans la ListBox
    LBClientsConnectes.Items.Add(Pse);
  end;
 
  procedure UserDeconnection(R : string);
  //R contient un message de déconnexion de la forme << ID >> qui signifie que la personne avec l'identifiant ID vient de se déconnecter
  var ID : integer;
      Pse : string;
  begin
    //ID contient l'identifiant du message
    ID:=ExtractIDUser(R);
    //Pse contient le Pseudo sauvegardé lors de la connexion
    Pse:=Users[ID];
 
    //Nous annonçons la déconnexion du client
    MChat.Lines.Add(Pse+' vient de se déconnecter.');
 
    //Nous effaçons du tableau le client déconnecté
    Users[ID]:='';
 
    //Nous cherchons et supprimons son nom dans la ListBox
    I:=LBClientsConnectes.Items.IndexOf(Pse);
    if I<>-1 then LBClientsConnectes.Items.Delete(I);
  end;
 
  procedure ReceptMessageTexte (R : string);
  //R contient un message texte de la forme << ID + Texte >> qui signifie que la personne avec l'identifiant ID dit "Texte"
  var ID : integer;
      Pse : string;
      Texte : string;
  begin
    //ID contient l'identifiant du message
    ID:=ExtractIDUser(R);
    //Pse contient le Pseudo stocké lors de la connexion
    Pse:=Users[ID];
    //Texte contient le texte envoyé par l'utilisateur
    Texte:=Copy(R,4,MaxInt);
 
    //Nous affichons le texte précédé du pseudo de l'émetteur
    MChat.Lines.Add(Pse+' dit :');
    MChat.Lines.Add(' > '+Texte);
  end;
 
 
var Repere : char;
begin
  //le premier caractère du message permet de différencier la marche à suivre
  Repere:=Recept[1];
  //Nous l'effaçons de la chaine de caractères
  Delete(Recept,1,1);
 
  //Suivant les cas, on exécute une des procédures ci-dessous
  case Repere of
    'O' : ConnectionSucceed(Recept);
    'N' : ConnectionFailed(Recept);
    'C' : UserConnection(Recept);
    'D' : UserDeconnection(Recept);
    '*' : ReceptMessageTexte(Recept);
  end;
end;

5-C-2. Côté serveur

Du côté du serveur, nous avons plusieurs éléments à ajouter. En effet, le serveur doit stocker la liste de tous les utilisateurs et leurs caractéristiques.

Premièrement, nous allons créer un type TUser, qui contiendra les caractéristiques d'un utilisateur (Pseudonyme, Identifiant, Statut de la connexion). Voici sa définition :

Définition des types TUser et TConnexionState
Sélectionnez
type
  TConnexionState = (csOk, csDisconnecting);
 
  TUser = class
  private
    FPseudo : string;
    FID : integer;
    FState : TConnexionState;
  public
    constructor Create (APseudo : string; AID : integer);
 
    property Pseudo : string read FPseudo;
    property ID : integer read FID;
    property State : TConnexionState read FState write FState;
  end;
 
...
 
constructor TUser.Create (APseudo : string; AID : integer);
//Nous créons notre objet TUser en l'initialisant avec les bonnes valeurs
begin
  inherited Create;
 
  FPseudo:=APseudo;
  FID:=AID;
  FState:=usOk;
end;


L'objet TIdContext qui, rappelons-le, correspond à une connexion avec un client, possède une propriété Data, de type Pointer, qui permet de pointer sur tout type d'information. Nous allons nous servir de cette propriété pour pointer sur un objet de type TUser. Ainsi, en possédant uniquement l'IdContext, nous pourrons retrouver les caractéristiques de l'utilisateur associé. Nous allons aussi stocker ces objets TUser dans un tableau Users déclaré comme suit :

 
Sélectionnez
Users : array [1..MAXUSERS] of TUser;

Nous définissons maintenant une procédure SendAll, qui aura pour but d'envoyer un message à tous les clients connectés.

Procédure SendAll
Sélectionnez
procedure TFServeur.SendAll (Mess : string);
var ListeContext : TList;
    i : integer;
    AUser : TUser;
begin
  //Nous récupérons la liste des IdContext qui correspond à la liste des clients
  ListeContext:=IdTCPServer1.Contexts.LockList;
 
  //Pour chaque client prêt (State = usOK), on envoie le message Mess
  for i:=0 to ListeContext.Count-1 do
  begin
    AUser:=TUser(TIdContext(ListeContext.Items[i]).Data);
 
    if Assigned(AUser) and (AUser.State=usOk) then TIdContext(ListeContext.Items[i]).Connection.IOHandler.WriteLn(Mess);
  end;
 
  //Comme nous avons appelé LockList, nous devons débloquer la liste.
  IdTCPServer1.Contexts.UnlockList;
end;


Nous codons l'évènement OnDisconnect :

Événement OnDisconnect du IdTCPServer1
Sélectionnez
procedure TFServeur.IdTCPServer1Disconnect(AContext: TIdContext);
var AUser : TUser;
begin
  //On stocke dans une variable temporaire les caractéristiques de l'utilisateur qui se déconnecte
  AUser:=TUser(AContext.Data);
 
  if Assigned(AUser) then
  begin
    //On met son statut en Disconnecting pour éviter qu'il ne reçoive le message lors du SendAll suivant
    AUser.State:=csDisconnecting;
 
    //On envoie à tous les clients l'annonce de la déconnexion
    SendAll('D'+Format('%.3d',[AUser.ID])+AUser.Pseudo);
 
    //On efface le pointeur Data et on libère le client du tableau Users
    AContext.Data:=nil;
    Users[AUser.ID].Free;
  end;
end;


Et enfin, l'évènement OnExecute :

Évènement OnExecute du IdTCPServer1
Sélectionnez
procedure TFServeur.IdTCPServer1Execute(AContext: TIdContext);
var Recept : string;
begin
  //Lecture du message
  Recept:=AContext.Connection.IOHandler.ReadLn;
 
  //Analyse du message
  GestionMessage(AContext,Recept);
end;


La procédure GestionMessage permet d'analyser chaque message. Lors d'une demande de connexion, le serveur vérifie la place disponible, et si le pseudonyme est déjà utilisé. Lorsqu'il s'agit d'un message texte, le serveur se contente de le renvoyer à tous sans y effectuer de modifications.

Procédure de Gestion des messages
Sélectionnez
procedure TFServeur.GestionMessage (AContext : TIdContext; Recept : string);
 
  procedure EnvoiListeUsers;
  var i : integer;
  begin
    //On parcourt le tableau Users et on envoie la liste des personnes connectées à notre client
    for i:=1 to MAXUSERS do
      if Assigned(Users[i]) and (Users[i].State=csOk) then
        AContext.Connection.IOHandler.WriteLn('C'+Format('%.3d',[i])+Users[i].Pseudo);
  end;
 
  procedure DemandeConnection(R : string);
  //R contient un message de demande de connexion de la forme << Pseudo >> qui signifie que Pseudo veut se connecter
  var Raison : string;
      IsPossibleConnection : boolean;
      ID : integer;
  begin
    IsPossibleConnection:=true;
 
    //Nous testons si une personne ne possède pas le même pseudo
    ID:=0;
    repeat
      inc(ID);
    until (ID>MAXUSERS) or (Assigned(Users[ID]) and (Users[ID].Pseudo=R));
 
    if ID<=MAXUSERS then
    begin
      IsPossibleConnection:=false;
      Raison:='Pseudo déjà utilisé';
    end;
 
    if IsPossibleConnection then
    begin
      //Nous vérifions s'il reste de la place dans le serveur
      ID:=1;
      while (ID<=MAXUSERS) and Assigned(Users[ID]) do
        inc(ID);
 
      if ID>MAXUSERS then
      begin
        IsPossibleConnection:=false;
        Raison:='Serveur plein'
      end;
      //ici, la variable ID contient l'indice de la première place libre dans le tableau Users
    end;
 
    //Maintenant que nos tests sont finis, nous répondons à l'utilisateur
    if not IsPossibleConnection then
    begin
      //Connexion impossible
      AContext.Connection.IOHandler.WriteLn('N'+Raison);
    end
    else
    begin
      //Connexion possible avec l'identifiant ID
      AContext.Connection.IOHandler.WriteLn('O'+Format('%.3d',[ID]));
 
      //Envoi de la liste des personnes déjà connectées
      EnvoiListeUsers;
 
      //Annonce de la connexion de l'utilisateur à tous les autres utilisateurs
      SendAll('C'+Format('%.3d',[ID])+R);
 
      //Stockage dans le tableau Users
      Users[ID]:=TUser.Create(R,ID);
 
      //On fait pointer la propriété Data du IdContext vers notre nouveau TUser
      AContext.Data:=Users[ID];
    end;
 
  end;
 
  procedure ReceptMessageTexte (R : string);
  //R contient un message texte de la forme << ID + Texte >> qui signifie que la personne avec l'identifiant ID dit "Texte"
  begin
    SendAll('*'+R);
  end;
 
 
var Repere : char;
begin
  //Le premier caractère du message permet de différencier la marche à suivre
  Repere:=Recept[1];
  //Nous l'effaçons de la chaine de caractères
  Delete(Recept,1,1);
 
  //Suivant les cas, on exécute une des procédures ci-dessous
  case Repere of
    'C' : DemandeConnection(Recept);
    '*' : ReceptMessageTexte(Recept);
  end;
end;

5-C-3. Codes sources

Conclusion

Nous possédons maintenant un système de chat fonctionnel, permettant de voir les utilisateurs connectés grâce à l'utilisation de pseudonymes. Ce système de chat peut servir de base pour prendre en charge plus de fonctionnalités, comme l'ajout de statuts, la personnalisation du texte, etc. Ma participation au Défi Pascal 2010 est basée sur ce système.

Je tiens à remercier l'équipe Pascal, et tout particulièrement Alcatîz, Krachik et Darrylsite pour l'aide apportée à la rédaction et à la correction de l'article, ainsi que Claude Leloup pour la relecture orthographique.

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

  

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2011 Mick605. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.