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)
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 :
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 :
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 :
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 :
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.
FReceptionThread : TReceptionThread;
procedure
ReceptionMessage (Sender : TObject; Recept : string
);
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.
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.
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.
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 :
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 :
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 :
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 :
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 :
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.
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 :
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 :
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.
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.