IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
logo

FAQ DelphiConsultez toutes les FAQ

Nombre d'auteurs : 124, nombre de questions : 929, dernière mise à jour : 31 décembre 2023  Ajouter une question

 

Cette FAQ a été réalisée à partir des questions fréquemment posées sur les forums Delphi et Delphi et bases de données de www.developpez.com et de l'expérience personnelle des auteurs.

Nous tenons à souligner que cette FAQ ne garantit en aucun cas que les informations qu'elle propose soient correctes. Les auteurs font le maximum, mais l'erreur est humaine. Cette FAQ ne prétend pas non plus être complète. Si vous souhaitez y apporter des corrections ou la compléter, contactez un responsable (lien au bas de cette page).

Nous espérons que cette FAQ saura répondre à vos attentes. Nous vous en souhaitons une bonne lecture.

L'équipe Delphi de Developpez.com.

SommaireComposants IndyIndy et les threads (4)
précédent sommaire suivant
 

Les composants Indy sont des sockets bloquants, c'est à dire qu'une fonction nécessitant l'envoi ou la réception de données ne rendra pas la main tant que tout le volume d'informations n'aura pas été reçu ou envoyé. Pendant ce temps, les messages Windows ne sont plus traités, ce qui se caractérise essentiellement par l'aspect "gelé" de l'application.

Pour y remédier, il y a plusieurs solutions :

  • Travailler avec des applications console qui ne demandent pas d'interaction avec l'utilisateur,
  • Poser un TIdAntiFreeze sur la fiche. Ce composant appelle automatiquement "ProcessMessages()" pendant les "timeout" des sockets ce qui permet de traiter les messages et de ne pas figer l'application. Au prix d'un léger (mais imperceptible) ralentissement, vous avez tout les avantages des sockets bloquants sans leurs inconvénients.
  • Travailler avec des threads. Indy est conçu pour être threadé : créer un nouveau thread comportant le socket et son code empêchera tout blocage (du moins, le thread principal du programme ne sera pas affecté lorsque le (ou les) thread(s) secondaires seront en phase bloquante).

Notez que seul les clients doivent se préoccuper de problèmes de blocage. Les serveurs eux sont automatiquement threadés.

Mis à jour le 21 janvier 2014 Reisubar

Il y a plusieurs méthodes de synchronisation qui peuvent (qui doivent) être exécutées dans le serveur, étant donné que chaque requête client s'exécute dans un thread particulier.

Objets et variables privées au thread : aucune méthode de synchronisation ni précautions particulières ne doivent être prises, car ces variables ne sont partagées que par l'instance du thread en elle-même.

Objets publics, fichiers, éléments non graphiques : Si l'accès à ces objets se fait en lecture seule il n'y a pas besoin de protection. Si par exemple un fichier INI doit être lu pour extraire une valeur dans une fonction, cette fonction peut être appelée sans protection particulière depuis le thread serveur. Dès que ces éléments doivent être modifiés, on doit utiliser des Sections Critiques (l'objet TCriticalSection). Cela assure qu'un seul thread à la fois exécute les passages de code accédant à des objets non propres aux threads.

Voilà un exemple ajoutant le nom d'un utilisateur dans une TStringList publique:

1. Déclaration d'une section critique, dans les variables globales de la fiche (on n'oubliera pas d'ajouter SyncObjs dans la clause uses):

Code Delphi : Sélectionner tout
1
2
var 
  GLock : TCriticalSection;
2. On crée et on détruit cette section dans la partie initialization et finalization de l'unité :
Code Delphi : Sélectionner tout
1
2
3
4
initialization 
  GLock := TCriticalSection.Create; 
  finalization 
  GLock.Free;

3. Dans la fonction, on appelle Acquire pour commencer le blocage et Leave pour l'arrêter :
Code Delphi : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
procedure TForm1.AddUser(const AUserName: string); 
begin 
  GLock.Acquire; 
  try 
    //Code ne devant être exécuté que par un thread à la fois 
    if UserList.IndexOf(AUserName)<0 then 
      UserList.Add(AUserName); 
  finally 
    GLock.Leave; 
  end; 
end;
Éléments graphiques de la VCL : la plupart les objets VCL supposent qu'ils s'exécutent depuis le thread principal de l'application. Utiliser les sections critiques résout le problème de la concurrence (seulement un thread peut mettre à jour l'interface graphique de l'application) mais pas de l'unicité des threads puisque c'est toujours le thread du serveur qui utilise les fonctions de mise à jour graphiques. On utilise donc Synchronize(), qui interrompt l'exécution du thread qui l'appelle et fait exécuter la méthode passée en paramètre par le thread principal de l'application.

Il y a plusieurs manières de procéder avec Synchronize. La première méthode est de créer des procédures dans la fenêtre principale qui mettent à jour l'interface et de les appeler via la méthode Synchronize() du TIdPeerThread :
Code Delphi : Sélectionner tout
1
2
3
4
procedure TForm1.ClientConnected; 
begin 
  ListBox1.ItemIndex := ListBox1.Items.Add('Un client s''est connecté !') 
end;
Dans un événement serveur :
Code Delphi : Sélectionner tout
1
2
3
4
procedure TForm1.IdTCPServer1Connect(AThread: TIdPeerThread); 
begin 
  AThread.Synchronize(Form1.ClientConnected); //MAJ GUI 
end;
Le principal problème est que la fonction ne doit pas accepter de paramètres pour être synchronisée.

La seconde méthode est de créer une classe de synchronisation. Il faudra ensuite créer une instance de cette classe, assigner une valeur à sa variable privée Data , puis appeler 'DoSynchronize' avec un thread Indy et la méthode qui doit être invoquée par Synchronize(), et enfin libérer l'objet.
Code Delphi : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
  
TSyncClass = class 
  protected 
    FData : string; 
  public 
    procedure UpdateVCL; //Procédure sans paramètre qui pourra être synchronisée 
    procedure DoSynchronize(AThread : TIdThread; AMethod : TThreadMethod); 
end; 
  
{ TSyncClass } 
  
procedure TSyncClass.DoSynchronize(AThread: TIdThread; 
  AMethod: TThreadMethod); 
begin 
  AThread.Synchronize(AMethod); //synchro. 
end; 
  
procedure TSyncClass.UpdateVCL; 
begin 
  //Procédure manipulant les éléments graphiques de la VCL 
  Form1.ListBox1.ItemIndex := Form1.ListBox1.Items.Add(FData) 
end;
On pourra ainsi appeler notre classe dans un événement serveur :
Code Delphi : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
  
procedure TForm1.IdTCPServer1Connect(AThread: TIdPeerThread); 
begin 
  with TSyncClass.Create do 
  try 
    FData := Format('Le client %s se connecte...',[AThread.Connection.Socket.Binding.PeerIP]); 
    DoSynchronize(AThread,UpdateVCL); 
  finally 
    Free; 
  end; 
end;

Mis à jour le 21 janvier 2014 Reisubar

Les serveurs Indy basés sur TCP ont à peu près tous le même fonctionnement. Par défaut, un thread d'écoute (Listener Thread) est créé et attend les connexions clientes. Dès qu'un client est accepté, le thread d'écoute crée un nouveau thread qui accueillera la connexion cliente. C'est dans le contexte de ce thread que sont déclenchés tous les événements du serveur. Les événements serveur s'exécutent donc dans des threads différents du thread principal. Une fois le client déconnecté, le thread client est supprimé par le thread d'écoute.

Voici la signature classique d'une fonction membre d'un serveur :

Code Delphi : Sélectionner tout
1
2
3
4
procedure TForm1.IdPOP3Server1Execute(AThread: TIdPeerThread); 
begin 
  // Code 
end;
Remarquez la présence de AThread: TIdPeerThread. Cette variable désigne le thread dans lequel est appelée la fonction. Encore une fois, comme un thread est créé pour chaque client, plusieurs "exemplaires" de la fonction ci-dessus peuvent s'exécuter en même temps.
Cela implique que tout accès à des éléments non-membres du thread devra être fait à l'aide de méthodes de synchronisation. De même, l'accès aux éléments graphiques de la VCL devra se faire absolument en utilisant Synchronize().

Mis à jour le 21 janvier 2014 Reisubar

Pour diffuser un message à tous ses clients, vous pouvez récupérer la liste de tous les threads clients par la propriété Threads du serveur. LockList() est utilisé pour refuser l'accès à la liste pendant qu'on effectue les opérations.

Dans l'exemple suivant, il s'agit d'un simple WriteLn() envoyant le message fourni en paramètre.

Code Delphi : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
procedure TForm1.BroadcastMessage(TheMessage : String); 
var 
  Count: Integer; 
  List : TList; 
begin 
  List := tcpServer.Threads.LockList; //vérouiller la liste 
  try 
    for Count := 0 to Pred(List.Count) do //Pour chaque thread 
    try 
      TIdPeerThread(List.Items[Count]).Connection.WriteLn(TheMessage); //Envoie le message 
    except 
      TIdPeerThread(List.Items[Count]).Stop; 
    end; 
  finally 
    tcpServer.Threads.UnlockList; //dévérouiller la liste 
  end; 
end;

Mis à jour le 21 janvier 2014 Reisubar

Proposer une nouvelle réponse sur la FAQ

Ce n'est pas l'endroit pour poser des questions, allez plutôt sur le forum de la rubrique pour ça


Réponse à la question

Liens sous la question
précédent sommaire suivant
 

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 © 2024 Developpez Developpez LLC. Tous droits réservés Developpez LLC. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez LLC. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.