[ACCEPTED]-How to handle received data in the TCPClient ? (Delphi - Indy)-indy
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;
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.
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.
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.
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
We use cookies to improve the performance of the site. By staying on our site, you agree to the terms of use of cookies.