2011-04-03 93 views
13

Cuando envío un mensaje desde TCPClient a un TCPServer se manejará usando el evento OnExecute en el servidor. Ahora quiero manejar los mensajes recibidos en el Cliente pero TCPClient no tiene ningún evento para esto. Entonces tengo que hacer un hilo para manejarlos manualmente. Cómo puedo hacerlo ?¿Cómo manejar los datos recibidos en TCPClient? (Delphi - Indy)

+3

¿Has echado un vistazo a los ejemplos de Indy? – BugFinder

Respuesta

18

Como dijeron otros en respuesta a su pregunta, TCP no es un protocolo orientado a mensajes, sino uno de transmisión. Te voy a mostrar cómo escribir y leer a un servidor de eco muy simple (esta es una versión ligeramente modificada de un servidor que hice esta semana para responder a otra pregunta):

El método OnExecute servidor tiene el siguiente aspecto:

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; 

Este servidor comienza con un mensaje de bienvenida, luego solo lee el byte de conexión por byte. El servidor responde el mismo byte, hasta que el byte recibido sea 65 (el comando de desconexión) 65 = 0x41 o $ 41. El servidor termina con un mensaje de adiós.

Usted puede hacer esto en un cliente:

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; 

El procedimiento siguiente byte puede ser cualquier cosa que desee proporcionar un byte. Por ejemplo, para obtener la entrada del usuario, se puede girar el TeclaDePresentaciónPreliminar de su formulario a la verdadera y escribir un controlador de eventos OnKeyPress y la función NextByte la siguiente manera: Se enviará

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; 

Todo lo que el usuario escribe en la forma al servidor y luego leer desde allí y agregar a memo1. Si el foco de entrada ya está en Memo1, verá cada personaje dos veces, uno del teclado y otro del servidor.

Por lo tanto, para escribir un cliente simple que obtiene información de un servidor, debe saber qué esperar del servidor. ¿Es una cadena? múltiples cadenas? ¿Entero? ¿formación? un archivo binario? archivo codificado? ¿Hay una marca para el final de la conexión? Estas cosas generalmente se definen en el protocolo o por usted, si está creando un par personalizado de servidor/cliente.

Escribir un TCP genérico sin saber previamente qué es lo que se obtiene del servidor es posible, pero complejo debido al hecho de que no existe una abstracción de mensaje genérico en este nivel en el protocolo.

No se deje confundir por el hecho de que hay mensajes de transporte, sino una sola respuesta del servidor puede dividirse en varios mensajes de transporte , y el lado del cliente y luego volver a montar, su aplicación no controlan esto. Desde el punto de vista de una aplicación, el socket es un flujo (flujo) de bytes entrantes. La forma de interpretar esto como un mensaje, un comando o cualquier tipo de respuesta del servidor depende de usted. Lo mismo es aplicable al lado del servidor ... por ejemplo, el evento onExecute es una hoja en blanco donde tampoco tiene una abstracción de mensaje.

Quizás esté mezclando la abstracción de mensajes con la abstracción del comando ... en un protocolo basado en comandos, el cliente envía cadenas que contienen comandos y el servidor responde con cadenas que contienen respuestas (entonces probablemente más datos). Eche un vistazo a los componentes TIdCmdTCPServer/Client.

EDITAR

En los comentarios de los Estados OP s/él quiere hacer este trabajo en un hilo, no estoy seguro de lo que es el problema s/él está teniendo con esto, pero estoy añadiendo una ejemplo de hilo El servidor es el mismo que se muestra antes, sólo la parte del cliente de esta simple servidor:

En primer lugar, la clase de hilo que estoy usando:

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; 

Entonces, estoy añadiendo estos dos métodos a la formulario:

//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; 
+0

Gracias por la respuesta completa. pero quería hacer esto usando Thread. – Kermia

+0

@Kermia, ¿en qué parte de tu pregunta afirmas esto? ¿Qué te hace pensar que no puedes hacer esto en un hilo? – jachguate

+0

Como dije: 'Tengo que hacer un hilo para manejarlos manualmente. ¿Cómo puedo hacerlo? '. porque Indy está trabajando con modo de bloqueo y sin ningún hilo adicional, mi aplicación se congelará. – Kermia

4

TCP no funciona con mensajes. Esa es una interfaz basada en flujo. En consecuencia, no espere que reciba un "mensaje" en el receptor. En su lugar, lee el flujo de datos entrantes del socket y lo analiza de acuerdo con su protocolo de alto nivel.

+0

Ok, ¿Cómo manejar flujos recibidos en el TCPClient usando un hilo externo? – Kermia

+0

El título está editado – Kermia

+0

@Kermia si cambia radicalmente la pregunta, le sugiero que publique una pregunta por separado. –

1

Si necesita que el cliente Indy maneje los "mensajes" entrantes (la definición de "mensaje" depende del protocolo utilizado), le recomiendo echar un vistazo a la implementación de TIdTelnet en la unidad protocols \ IdTelnet.

Este componente utiliza un hilo de recepción, basado en un TIdThread, que asíncronamente recibe mensajes del servidor Telnet, y los pasa a una rutina de manejo de mensajes. Si tienes un protocolo similar, este podría ser un buen punto de partida.

Actualización: para ser más específico, el procedure TIdTelnetReadThread.Run; en IdTelnet.pas es donde ocurre la 'magia' del cliente asíncrono, como se puede ver, utiliza Sincronizar para ejecutar el procesamiento de datos en el hilo principal, pero por supuesto su aplicación podría también haga la gestión de datos en el hilo de recepción, o páselo a un hilo de trabajo para mantener intacto el hilo principal. El procedimiento no utiliza un bucle, porque el bucle/pausa/reinicio se implementa en IdThread.

+0

El título está editado. – Kermia

2

Aquí está mi código para leer/escribir con Delphi 7. Uso del puerto TCP evento Read.

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. 
0

Agregue TTimer. Establezca su Interval en 1. Escribir en OnTimer Evento:

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; 

No establezca otra cosa Timer.Interval1. Porque, los datos recibidos se eliminan después de unos milisegundos.

Cuestiones relacionadas