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 ?
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;
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.
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.
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.