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 : 934, dernière mise à jour : 23 octobre 2024  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 le Client/Serveur (7)
précédent sommaire suivant
 

Si vous utilisez un serveur basé sur un protocole pris en charge par Indy (HTTP, FTP, SMTP, etc.), celui-ci fournit des événements dans lesquels vous avez ou vous pouvez, directement ou indirectement, communiquer avec le client. La plupart du temps vous n'avez pas besoin d'envoyer directement du texte ou des commandes "brutes", il s'agit de remplir des propriétés ou des variables que Indy utilisera pour générer une réponse correctement formatée, en fonction du protocole utilisé.

Vous pouvez également vous servir des "Command Handlers". Ces propriétés serveur permettent de réagir à des commandes et d'exécuter des fonctions à leur réception.

Enfin, la dernière solution est d'utiliser l'événement OnExecute. Celui-ci est déclenché juste après la connexion du client et est appelé en boucle tant que celui-ci n'est pas déconnecté. Vous pouvez dans cet événement écrire directement sur la connexion et échanger des données avec le client.

Il est important dans cette procédure de bien gérer les exceptions, car en cas d'erreur non gérée, l'exécution de la procédure sera interrompue, et la connexion avec le client laissée en suspens tant que la pile TCP/IP ne ferme pas la connexion pour cause de "time out". Cela pouvant prendre une ou deux minutes, on utilisera toujours un bloc try...finally...end.

Code Delphi : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
procedure TForm1.IdTCPServer1Execute(AThread: TIdPeerThread); 
begin 
  with AThread.Connection do 
  try 
    try 
//Travail à effectuer 
    except 
//Notification d'une erreur au client 
on E : Exception do 
  WriteLn(Format('ERROR : %s',[e.Message])); 
    end; 
  finally 
    Disconnect; //Fermeture de la connexion 
  end; 
end;


Mis à jour le 5 janvier 2014 Reisubar

Si vous souhaitez échanger facilement des lignes de texte entre client et serveur, utilisez les fonctions WriteLn() et ReadLn() du TIdTCPClient et de l'objet Connexiondu TIdPeerThread du serveur.

Voici un exemple simple : un serveur qui exécute la ligne de commande qui lui est envoyée. Dans l'extrait de code ci-dessous, le client fait exécuter au serveur la commande "shutdown", et donc commande à distance son extinction.

Code du client

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
procedure ShutdownServer(const AHost : string; const APort : integer); 
begin 
  with IdTCPClient do 
  begin 
    Port := APort; 
    Host := AHost; 
    try 
      Connect; //connecte 
      Try 
  IdTCPClient.WriteLn('shutdown -h -t 00'); //envoie la ligne de commande 
  if IdTCPClient.ReadLn()<>'OK' then //attend la réponse. Si <> de OK, erreur : 
    MessageDlg('Erreur : l''exécution a été refusée par le serveur.', 
  mtInformation, [mbOK], 0); 
      finally 
  Disconnect; //dans tous les cas, se déconnecter 
      end; 
    except 
      MessageDlg('Une erreur est survenue durant l''envoi de commandes', mtError, [mbOK], 0); 
    end; 
  end; 
end;
Code du serveur

Le code se place dans l'événement OnExecute du serveur :
Code Delphi : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
procedure TForm1.IdTCPServerExecute(AThread: TIdPeerThread); 
var 
  Line, Command, Args : String; 
begin 
  Line := AThread.Connection.ReadLn(); //Lire une ligne 
  AThread.Connection.WriteLn('OK'); //Signifie que la demande a été correctement reçue. 
  //On pourrait renvoyer autre chose pour notifier un refus d'exécution. 
  if Pos(' ',Line)>0 then 
  begin //Il y a un/des paramètre(s), il faut extraire Commande et Arguments 
    Command := Copy(Line,1,Pred(Pos(' ',Line))); 
    Args := Copy(Line,Succ(Pos(' ',Line)),Length(Line)) 
  end else 
  begin //Aucun paramètre. Commande = toute la ligne reçue 
    Command := Line; 
    Args := ''; 
  end; 
  //Exécuter la ligne de commande 
  ShellExecute(Handle,nil,PChar(Command),PChar(Args),nil,SW_SHOWNORMAL); 
end;

Mis à jour le 21 janvier 2014 Reisubar

Il est très facile de compresser les flux échangés entre un client et un serveur descendant respectivement de TIdTCPClient et TIdTCPServer.

Côté client, il faut associer un TIdCompressionIntercept à la propriété Intercept du client. Vous devez entrer un nombre entre 1 et 9 dans la propriété CompressionLevel pour indiquer le niveau de compression à utiliser (0 correspond à une compression nulle).
Côté serveur, vous devez créer dynamiquement pour chaque thread client un CompressionIntercept afin de permettre la décompression du flux de connexion (et le libérer la connexion terminée).

Ceci peut s'implémenter ainsi :

Dans l'évènement OnConnect :

Code Delphi : Sélectionner tout
1
2
3
4
5
6
7
8
procedure TForm1.IdTCPServerConnect(AThread: TIdPeerThread); 
begin 
with (AThread.Connection) do 
begin 
    Intercept := TIdCompressionIntercept.Create(nil); // Créer le "compresseur" 
    TIdCompressionIntercept(Intercept).CompressionLevel := 9; // Niveau de compression 
end; 
end;
Dans l'évènement OnDisconnect :
Code Delphi : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
procedure TForm1.IdTCPServerDisconnect(AThread: TIdPeerThread); 
begin 
  with (AThread.Connection) do  
  begin 
    if Assigned(Intercept) then // Assigné ? 
    begin 
      Intercept.Free; // On libère... 
      Intercept := nil;  
    end; 
  end; 
end;

Mis à jour le 21 janvier 2014 Reisubar

Tout d'abord, il faut définir un record :

  • dont les membres sont de longueur statique, pour que la taille globale du record soit prévisible du côté client comme du côté serveur. Par conséquent, vous ne pouvez pas utiliser des types de longueur variable automatiquement gérés par Delphi. Le type brut string est à proscrire. Indiquez toujours une longueur maximum (par exemple string [15], pour créer une chaîne de 15 caractères maximum), voire passez vos chaînes par des array of char.
  • exempts de tout pointeurs. Les pointeurs désignent un espace mémoire propre à un processus. Le serveur et le client sont généralement sur des machines différentes et ne partagent pas le même processus mémoire : l'adresse transmise ne correspondrait donc à rien.

Exemple d'enregistrement valide :
Code Delphi : Sélectionner tout
1
2
3
4
5
  TClient  = record 
      Nom : string[50]; 
      Adresse : string[200]; 
      CodeVille : integer; 
  end;
Cet enregistrement doit être le même côté serveur et côté client. L'idéal est que les deux entités partagent la même unité de définition du record.

Code côté client :

On utilisera WriteBuffer pour écrire dans le tampon de connexion. Le dernier paramètre, mis à true, permet de le vider instantanément dans le flot pour qu'il soit directement transmis au serveur :
Code Delphi : Sélectionner tout
IdTCPClient.WriteBuffer(ARecord,sizeof(ARecord),true);
Code côté serveur :

Dans l'événement OnExecute, on utilisera ReadBuffer pour lire dans le tampon :
Code Delphi : Sélectionner tout
AThread.Connection.ReadBuffer(Recv,Sizeof(Recv));

Mis à jour le 21 janvier 2014 Reisubar

On peut facilement envoyer un fichier entre un client et un serveur TCP. Pour cela, on utilise chez le client la méthode WriteStream. Sur le serveur, on se contente de lire le flux et de le placer dans un TFileStream.

Il est nécessaire de mettre au point un minuscule protocole pour que les entités puissent communiquer aisément. Voilà celui que j'ai choisi :

  • Le client se connecte au serveur, et lui envoie la commande "TRANS" suivie du nom de fichier qu'il envoie.
  • Le serveur reçoit la commande TRANS, et comprend qu'on lui envoie un fichier. Il extrait alors le nom du fichier et crée un TFileStream sur un document du même nom dans son répertoire.
  • Le client envoie ensuite la taille du fichier. Cette taille sera nécessaire lorsqu'il faudra lire un flux sur la connexion (spécifier la taille du flux est obligatoire, ou alors le flux est lu jusqu'à la déconnexion, ce qui n'est pas ce que nous souhaitons faire ici).
  • Le serveur reçoit la taille du fichier. Il attend donc un flux et le stocke dans le TFileStream jusqu'à que le nombre d'octets reçus atteigne la valeur lue précédemment.
  • Pendant ce temps, le client ouvre son tampon d'écriture, place le contenu du fichier à transférer en utilisant WriteStream puis ferme ce flux.
  • Une fois la réception terminée, le serveur confirme la bonne réception en écrivant sur la connexion "OK". Notez que toute exception éventellement levée côté serveur fait écrire "ERR" au lieu de "OK".
  • Le client attend une réponse du serveur. Si celle-ci est égale à "OK", on en déduit que tout s'est bien passé, et on fait renvoyer true à la fonction, false sinon.
  • L'échange terminé, client et serveur se déconnectent mutuellement.

Code de la fonction d'envoi :
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
23
24
25
function TForm1.SendFile(const AFileName: string; 
  ATcpClient: TIdTCPClient) : Boolean; 
  var 
    Fs : TFileStream; 
begin 
  Result := False; 
  ATcpClient.Connect(); //Connecte. Les propriétés Host et Port doivent être remplies. 
  try 
    Fs := TFileStream.Create(AFileName,fmOpenRead,fmShareDenyWrite); //Créer le flux 
    try 
      ATcpClient.WriteLn(Format('TRANS %s',[ExtractFileName(AFileName)])); //demander transfert 
      try 
        ATcpClient.WriteInteger(Fs.Size); //Ecrire la taille 
        ATcpClient.WriteStream(Fs); //Ecrit le flux 
      except 
        MessageDlg('Erreur pendant l''envoi du fichier.', mtError, [mbOK], 0); 
      end; 
    finally 
      FreeAndNil(Fs); //Libérer le flux 
      Result := ATcpClient.ReadLn()='OK'; //OK uniquement si le serveur a renvoyé "OK" 
    end; 
  finally 
    ATcpClient.Disconnect; //Déconnecter à la fin. 
  end; 
end;
Code du serveur :
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
23
24
25
26
27
28
29
30
31
32
33
procedure TForm1.IdTCPServerExecute(AThread: TIdPeerThread); 
var 
  Line, FileName : String; 
  i, FileSize : integer; 
  Fs : TFileStream; 
begin 
  with AThread.Connection do 
  try 
    Line := ReadLn(); //Attends une commande de la forme TRANS suivi du nom de fichier 
    i := Pos(' ',Line); 
    if (i>0) and (LowerCase(Copy(Line,1,Pred(i)))='trans') then 
    begin 
      FileName := IncludeTrailingPathDelimiter(ExtractFilePath(Application.ExeName)) 
      + Copy(Line,Succ(i),Length(Line)); //Copier nom de fichier 
      Fs := TFileStream.Create(FileName,fmCreate); //Créer le flux 
      try 
        try 
          FileSize := ReadInteger(); //Lire la taille 
          ReadStream(Fs,FileSize,False); //Lire le flux 
          WriteLn('OK'); //Signaler succès 
        except 
          WriteLn('ERR'); //Signaler une erreur 
        end; 
      finally 
        FreeAndNil(Fs); //Libérer le flux dans tous les cas 
      end; 
    end 
    else 
      WriteLn('ERR'); //Commande incomprise 
  finally 
    Disconnect; //A la fin, on déconnecte 
  end; 
end;

Exemple d'exploitation :

Code Delphi : Sélectionner tout
1
2
3
4
5
6
7
8
9
procedure TForm1.Button1Click(Sender: TObject); 
begin 
  IdTCPClient.Host := 'localhost'; //Hôte 
  IdTCPClient.Port := 1985; //Le serveur doit aussi écouter sur le port 1985 
  if SendFile('c:\setup.exe',IdTCPClient) then 
    MessageDlg('Ok !', mtInformation, [mbOK], 0) 
  else 
    MessageDlg('Erreur...', mtInformation, [mbOK], 0) 
end;

Mis à jour le 21 janvier 2014 Reisubar

Dans les protocoles à connexions "persistantes" (la connexion est maintenue durant toute la session), on peut utiliser dans le thread associé au client la propriété Data de type TObject pour stocker un ensemble d'informations qui lui sont propres.

Par exemple, dans un serveur FTP, pour stocker le répertoire racine du client pendant la connexion, on peut procéder ainsi :

  • Au moment de la connexion, on crée un objet qu'on remplit avec les propriétés adéquates. On l'associe ensuite au thread client.
  • Dans tout le reste du programme on pourra relire cette propriété dans les événements serveurs (puisque tous les événements ont accès au thread qui leur sont liés). On prendra garde à bien transtyper cette propriété dans le type choisi au départ pour l'objet.
  • A la déconnexion, on libèrera l'objet.

On définit un objet à associer à chaque thread :
Code Delphi : Sélectionner tout
1
2
3
4
  TFtpClient = class(TObject) 
    RootPath : String; // L'information à mémoriser 
    Thread : Pointer; // Pointe vers le thread client 
  end;
Au moment de la connexion, on l'associe au thread client :
Code Delphi : Sélectionner tout
1
2
3
4
5
6
7
8
9
procedure TMainForm.IdFTPServerAfterUserLogin(ASender: TIdFTPServerThread); 
var 
  AClient : TFtpClient; 
begin 
  AClient := TFtpClient.Create; // Création 
  AClient.Thread := ASender; // Association Objet->Thread attaché 
  AClient.RootPath := 'Donnée à mémoriser';  
  ASender.Data := AClient; // Associer l'objet au thread 
end;
Dans n'importe quel événement, on pourra accéder à cette propriété en n'oubliant pas de la transtyper (ici, on appelle la fonction StrGetRealPathName avec la valeur "RootPath" associée précédemment à l'objet) :
Code Delphi : Sélectionner tout
1
2
3
4
5
procedure TMainForm.IdFTPServerMakeDirectory(ASender: TIdFTPServerThread; 
  var APath : string; 
begin 
  APath := StrGetRealPathName(TFTPClient(ASender.Data).RootPath,ASender.CurrentDir,VDirectory); 
  ...
À la déconnexion, on libèrera l'objet :
Code Delphi : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
procedure TMainForm.IdFTPServerDisconnect(AThread: TIdPeerThread); 
var 
  AClient : TFtpClient; 
begin 
  AClient := TFtpClient(AThread.Data); // On récupère l'objet 
  if AClient <> nil then // Si le client a une structure associée... 
  begin 
    FreeAndNil(AClient); // on la détruit 
    AThread.Data := nil;  
  end; 
end;

Mis à jour le 21 janvier 2014 Reisubar

Il existe deux modes de communication : le mode connecté, le plus courant, correspondant au protocole TCP et le mode déconnecté correspondant au protocole UDP. Vous êtes invités à lire de la documentation appropriée pour plus de détails sur ces deux protocoles.

Pour utiliser le protocole UDP à partir de Delphi, nous pouvons utiliser les composants de la suite Indy, en particulier les composants IdUDPClient et IdUDPServer.
Le client envoie des données au serveur par l'intermédiaire de la méthode Send du composant IdUDPClient, comme nous sommes en mode déconnecté, nous devons préciser le destinataire du message ainsi que le port à chaque message :

Code Delphi : Sélectionner tout
IdUDPClt.Send('Host', 'Port', 'Le message à envoyer');
Côté serveur, nous devons écrire un gestionnaire pour l'événement UDPSrvUDPRead pour spécifier les opérations à exécuter à la réception d'un message. L'exemple qui suit affiche le message reçu ainsi que le port et le nom de l'expéditeur :
Code Delphi : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
procedure TForm1.UDPSrvUDPRead(Sender: TObject; AData: TStream; 
  ABinding: TIdSocketHandle); 
var 
  DataStringStream: TStringStream; 
begin 
  DataStringStream := TStringStream.Create(''); 
  try 
    DataStringStream.CopyFrom(AData, AData.Size); 
    ShowMessage('Reçu : "' + DataStringStream.DataString + '" De : ' + ABinding.PeerIP + ' sur le port : ' + IntToStr(ABinding.PeerPort)); 
  finally 
    DataStringStream.Free; 
  end; 
end;

Mis à jour le 21 janvier 2014 SubZero2

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.