[ACCEPTED]-How to handle received data in the TCPClient ? (Delphi - Indy)-indy

Accepted answer
Score: 22

As others said in response to your question, TCP 61 is not a message oriented protocol, but 60 a stream one. I'll show you how to write 59 and read to a very simple echo server (this 58 is a slightly modified version of a server 57 I did this week to answer other question):

The 56 server OnExecute method looks like this:

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;

This 55 server starts with a welcome message, then 54 just reads the connection byte per byte. The 53 server replies the same byte, until the received 52 byte is 65 (the disconnect command) 65 = 0x41 51 or $41. The server then end with a good 50 bye message.

You can do this in a 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;

The 49 next byte procedure can be anything you 48 want to provide a byte. For example, to 47 get input from the user, you can turn the 46 KeyPreview of your form to true and write 45 a OnKeyPress event handler and the NextByte 44 function like this:

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;

Anything the user writes 43 in the form will be sent to the server and 42 then read from there and added to memo1. If 41 the input focus is already in Memo1 you'll 40 see each character twice, one from the keyboard 39 and the other form the server.

So, in order 38 to write a simple client that gets info 37 from a server, you have to know what to 36 expect from the server. Is it a string? multiple 35 strings? Integer? array? a binary file? encoded 34 file? Is there a mark for the end of the 33 connection? This things are usually defined 32 at the protocol or by you, if you're creating 31 a custom server/client pair.

To write a generic 30 TCP without prior known of what to get from 29 the server is possible, but complex due 28 to the fact that there's no generic message 27 abstraction at this level in the protocol.

Don't 26 get confused by the fact there's transport messages, but a 25 single server response can be split into 24 several transport messages, and then re-assembled client side, your 23 application don't control this. From an 22 application point of view, the socket is 21 a flow (stream) of incoming bytes. The 20 way you interpret this as a message, a command 19 or any kind of response from the server 18 is up to you. The same is applicable server 17 side... for example the onExecute event 16 is a white sheet where you don't have a 15 message abstraction too.

Maybe you're mixing 14 the messages abstraction with the command 13 abstraction... on a command based protocol 12 the client sends strings containing commands 11 and the server replies with strings containing 10 responses (then probably more data). Take 9 a look at the TIdCmdTCPServer/Client components.

EDIT

In 8 comments OP states s/he wants to make this 7 work on a thread, I'm not sure about what's 6 the problem s/he is having with this, but 5 I'm adding a thread example. The server 4 is the same as shown before, just the client 3 part for this simple server:

First, the thread 2 class I'm using:

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;

Then, I'm adding this two 1 methods to the form:

//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;
Score: 4

TCP doesn't operate with messages. That 5 is stream-based interface. Consequently 4 don't expect that you will get a "message" on 3 the receiver. Instead you read incoming 2 data stream from the socket and parse it 1 according to your high-level protocol.

Score: 3

Here is my code to Read / Write with Delphi 1 7. Using the Tcp Event 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.
Score: 1

If you need the Indy client to handle incoming 19 "messages" (definition of "message" depends 18 on the protocol used), I recommend to take 17 a look at the implementation of TIdTelnet 16 in the protocols\IdTelnet unit.

This component 15 uses a receiving thread, based on a TIdThread, which 14 asynchronously receives messages from the 13 Telnet server, and passes them to a message 12 handler routine. If you have a similar protocol, this 11 could be a good starting point.

Update: to 10 be more specific, the procedure TIdTelnetReadThread.Run; in IdTelnet.pas is 9 where the asynchronous client 'magic' happens, as 8 you can see it uses Synchronize to run the 7 data processing in the main thread - but 6 of course your app could also do the data 5 handling in the receiving thread, or pass 4 it to a worker thread to keep the main thread 3 untouched. The procedure does not use a 2 loop, because looping / pausing / restarting 1 is implemented in IdThread.

Score: 1

Add a TTimer. Set its Interval to 1. Write in OnTimer Event:

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;

Don't 2 set Timer.Interval something else 1. Because, the received 1 data deletes after some milliseconds.

More Related questions