web-dev-qa-db-fra.com

Comment gérer les données reçues dans le TCPClient? (Delphi - Indy)

Lorsque j'envoie un message de TCPClient à une TCPServer, il sera traité à l'aide de l'événement OnExecute sur le serveur. Maintenant, je veux gérer les messages reçus dans le client, mais TCPClient n'a aucun événement pour cela. Donc, je dois faire un fil pour les manipuler manuellement. Comment puis-je le faire ?

14
Kermia

Comme d'autres l'ont dit en réponse à votre question, TCP n'est pas un protocole orienté message, mais un flux. Je vais vous montrer comment écrire et lire sur un serveur d'écho très simple (il s'agit d'une version légèrement modifiée d'un serveur que j'ai créé cette semaine pour répondre à une autre question):

La méthode du serveur OnExecute ressemble à ceci:

procedure TForm2.IdTCPServer1Execute(AContext: TIdContext);
var
  aByte: Byte;
begin
  AContext.Connection.IOHandler.Writeln('Write anything, but A to exit');
  repeat
    aByte := AContext.Connection.IOHandler.ReadByte;
    AContext.Connection.IOHandler.Write(aByte);
  until aByte = 65;
  AContext.Connection.IOHandler.Writeln('Good Bye');
  AContext.Connection.Disconnect;
end;

Ce serveur commence par un message de bienvenue, puis lit simplement l'octet de connexion par octet. Le serveur réponses le même octet, jusqu’à ce que l’octet reçu atteigne 65 (la commande de déconnexion) 65 = 0x41 ou $ 41. Le serveur se termine alors par un message d'adieu.

Vous pouvez le faire dans un client:

procedure TForm3.Button1Click(Sender: TObject);
var
  AByte: Byte;
begin
  IdTCPClient1.Connect;
  Memo1.Lines.Add(IdTCPClient1.IOHandler.ReadLn);  //we know there must be a welcome message!
  Memo1.Lines.Add('');// a new line to write in!
  AByte := 0;
  while (IdTCPClient1.Connected) and (AByte <> 65) do
  begin
    AByte := NextByte;
    IdTCPClient1.IOHandler.Write(AByte);
    AByte := IdTCPClient1.IOHandler.ReadByte;
    Memo1.Lines[Memo1.Lines.Count - 1] :=  Memo1.Lines[Memo1.Lines.Count - 1] + Chr(AByte);
  end;
  Memo1.Lines.Add(IdTCPClient1.IOHandler.ReadLn);  //we know there must be a goodbye message!
  IdTCPClient1.Disconnect;
end;

La procédure d'octet suivant peut être tout ce que vous voulez fournir un octet. Par exemple, pour obtenir les entrées de l'utilisateur, vous pouvez définir la KeyPreview de votre formulaire sur true et écrire un gestionnaire d'événements OnKeyPress et la fonction NextByte comme ceci:

procedure TForm3.FormKeyPress(Sender: TObject; var Key: Char);
begin
  FCharBuffer := FCharBuffer + Key;
end;

function TForm3.NextByte: Byte;
begin
  Application.ProcessMessages;
  while FCharBuffer = '' do  //if there is no input pending, just waint until the user adds input
  begin
    Sleep(10);
    //this will allow the user to write the next char and the application to notice that
    Application.ProcessMessages;
  end;  
  Result := Byte(AnsiString(FCharBuffer[1])[1]);  //just a byte, no UnicodeChars support
  Delete(FCharBuffer, 1, 1);
end;

Tout ce que l'utilisateur écrit dans le formulaire sera envoyé au serveur, puis lu à partir de là et ajouté à memo1. Si le focus de saisie est déjà dans Memo1, chaque caractère apparaît deux fois, l'un du clavier et l'autre du serveur.

Ainsi, pour écrire un client simple qui obtient les informations d’un serveur, vous devez savoir à quoi vous attendre du serveur. Est-ce une ficelle? plusieurs chaînes? Entier? tableau? un fichier binaire? fichier encodé? Y a-t-il une marque pour la fin de la connexion? Ces éléments sont généralement définis par le protocole ou par vous-même si vous créez une paire serveur/client personnalisée.

Écrire un TCP générique sans connaître au préalable ce qu'il faut obtenir du serveur est possible, mais complexe en raison du fait qu'il n'y a pas d'abstraction de message générique à ce niveau dans le protocole. 

Ne soyez pas déconcerté par le fait qu'il y a messages de transport , mais une réponse de serveur unique peut être divisée en plusieurs messages de transport , puis ré-assemblé côté client, votre application ne le contrôle pas. Du point de vue de l'application, le socket est un flux d'octets entrants. Vous interprétez cela comme un message, une commande ou tout type de réponse du serveur. La même chose s’applique côté serveur ... Par exemple, l’événement onExecute est une feuille blanche sur laquelle vous n’avez pas non plus d’abstraction de message.

Peut-être que vous mélangez l'abstraction des messages avec l'abstraction de la commande ... sur un protocole basé sur une commande, le client envoie des chaînes contenant des commandes et le serveur répond avec des chaînes contenant des réponses (probablement plus de données). Examinez les composants TIdCmdTCPServer/Client.

MODIFIER

Dans les commentaires, OP indique qu'il/elle souhaite que cela fonctionne sur un fil, je ne sais pas quel est le problème qu'il/elle rencontre, mais j'ajoute un exemple de fil. Le serveur est le même que celui indiqué ci-dessus, juste la partie client de ce serveur simple:

Tout d'abord, la classe de thread que j'utilise:

type
  TCommThread = class(TThread)
  private
    FText: string;
  protected
    procedure Execute; override;
    //this will hold the result of the communication
    property Text: string read FText;
  end;

procedure TCommThread.Execute;
const
  //this is the message to be sent. I removed the A because the server will close 
  //the connection on the first A sent.  I'm adding a final A to close the channel.
  Str: AnsiString = 'HELLO, THIS IS _ THRE_DED CLIENT!A';
var
  AByte: Byte;
  I: Integer;
  Client: TIdTCPClient;
  Txt: TStringList;
begin
  try
    Client := TIdTCPClient.Create(nil);
    try
      Client.Host := 'localhost';
      Client.Port := 1025;
      Client.Connect;
      Txt := TStringList.Create;
      try
        Txt.Add(Client.IOHandler.ReadLn);  //we know there must be a welcome message!
        Txt.Add('');// a new line to write in!
        AByte := 0;
        I := 0;
        while (Client.Connected) and (AByte <> 65) do
        begin
          Inc(I);
          AByte := Ord(Str[I]);
          Client.IOHandler.Write(AByte);
          AByte := Client.IOHandler.ReadByte;
          Txt[Txt.Count - 1] :=  Txt[Txt.Count - 1] + Chr(AByte);
        end;
        Txt.Add(Client.IOHandler.ReadLn);  //we know there must be a goodbye message!
        FText := Txt.Text;
      finally
        Txt.Free;
      end;
      Client.Disconnect;
    finally
      Client.Free;
    end;
  except
    on E:Exception do
      FText := 'Error! ' + E.ClassName + '||' + E.Message;
  end;
end;

Ensuite, j'ajoute ces deux méthodes à la fiche:

//this will collect the result of the thread execution on the Memo1 component.
procedure TForm3.AThreadTerminate(Sender: TObject);
begin
  Memo1.Lines.Text := (Sender as TCommThread).Text;
end;

//this will spawn a new thread on a Create and forget basis. 
//The OnTerminate event will fire the result collect.
procedure TForm3.Button2Click(Sender: TObject);
var
  AThread: TCommThread;
begin
  AThread := TCommThread.Create(True);
  AThread.FreeOnTerminate := True;
  AThread.OnTerminate := AThreadTerminate;
  AThread.Start;
end;
21
jachguate

TCP ne fonctionne pas avec les messages. C'est une interface basée sur le flux. Par conséquent, ne vous attendez pas à recevoir un "message" sur le récepteur. Au lieu de cela, vous lisez le flux de données entrant à partir du socket et vous l'analysez conformément à votre protocole de haut niveau.

Voici mon code pour lire/écrire avec Delphi 7. Utilisation de la lecture d'événement Tcp.

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, ScktComp;

type
  TForm1 = class(TForm)
    ClientSocket1: TClientSocket;
    Button1: TButton;
    ListBox1: TListBox;
    Edit1: TEdit;
    Edit2: TEdit;
    procedure Button1Click(Sender: TObject);
    procedure ClientSocket1Read(Sender: TObject; Socket: TCustomWinSocket);
    procedure ClientSocket1Error(Sender: TObject; Socket: TCustomWinSocket;
      ErrorEvent: TErrorEvent; var ErrorCode: Integer);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
var
 UsePort: Integer;
 UseHost: String;

begin
UseHost := Edit1.Text;
UsePort := STRTOINT(Edit2.Text);
ClientSocket1.Port :=  UsePort;
ClientSocket1.Host :=  UseHost;
ClientSocket1.Active :=  true;
end;

procedure TForm1.ClientSocket1Read(Sender: TObject;
  Socket: TCustomWinSocket);
begin
ListBox1.Items.Add(ClientSocket1.Socket.ReceiveText);

end;

procedure TForm1.ClientSocket1Error(Sender: TObject;
  Socket: TCustomWinSocket; ErrorEvent: TErrorEvent;
  var ErrorCode: Integer);
begin
  ErrorCode:=0;
  ClientSocket1.Active := False;
end;

procedure TForm1.BitBtn1Click(Sender: TObject);
begin
  ClientSocket1.Socket.SendText(Edit1.Text);
end;

end.
2
Tprice88

Si vous avez besoin du client Indy pour gérer les "messages" entrants (la définition de "message" dépend du protocole utilisé), il est conseillé de jeter un coup d'œil à l'implémentation de TIdTelnet dans l'unité protocoles\IdTelnet.

Ce composant utilise un thread de réception, basé sur un TIdThread, qui reçoit de manière asynchrone des messages du serveur Telnet et les transmet à une routine de gestionnaire de messages. Si vous avez un protocole similaire, cela pourrait être un bon point de départ.

Mise à jour: pour être plus précis, le procedure TIdTelnetReadThread.Run; dans IdTelnet.pas est l'endroit où le client asynchrone 'magic' se produit, comme vous pouvez le voir, il utilise Synchronize pour exécuter le traitement des données dans le thread principal - mais bien sûr, votre application peut aussi faire les données. gérer dans le thread de réception, ou le transmettre à un thread de travail pour garder le thread principal intact. La procédure n'utilise pas de boucle car la mise en boucle/pause/redémarrage est implémentée dans IdThread.

1
mjn

Ajouter une TTimer. Définissez sa Interval sur 1. Écrivez dans OnTimer événement: 

procedure TForm1.Timer1Timer(Sender: TObject);
var
s: string;
begin
if not IdTCPClient1.Connected then Exit;
if IdTCPClient1.IOHandler.InputBufferIsEmpty then Exit;
s := IdTCPClient1.IOHandler.InputBufferAsString;
Memo1.Lines.Add('Received: ' + s);
end;

Ne définissez pas Timer.Interval autre chose 1. Car les données reçues sont supprimées après quelques millisecondes.

0
H. Farid