Why does my Delphi Indy idHTTpServer stop responding when CLOSE_WAIT? - delphi

The Environment
I've created a web server in Delphi using Indy component TidHTTPServer. I'm using Delphi XE2 which came with Indy version 10.5.8. The
server is running as a desktop app with a form that displays a log of the connections and their requests. It is running on Windows 7
Professional. Requests are for SQL data from a Firebird database. The response is JSON. All traffic is HTTP.
The Challenge
When I was testing it with a small number of users everything worked great. Now that I have rolled it out to about 400 users there are
communication problems. The server stops responding to requests and the only way I can get it to respond again is to reboot the machine it is running on and then restart it. The need to reboot occurs more frequently during
high volume times.
The Symptoms
Using Windows netstat I have noticed that whenever a TCP connection of type CLOSE_WAIT occurs the server stops responding to requests and I have to reboot again
The Test Procedure
I have been able to simulate this hanging even when there is no traffic on the server. I created a web page that sends multiple requests with
a delay between each request.
The web page let's me specify the number of requests to make, how long to wait between each request, and how long to wait before timing out. Even at one millisecond between requests the server seems to respond without issue.
The Test Results
If I set the time out period of each request to a very small number, like 1 msec, I can make my Delphi HTTP Server hang. At a 1 msec timeout requests to my server fail every time, as I would expect. The time out is so short my server can't possibly respond quickly enough.
What I don't understand is that after I force this timeout at the client side, even a relatively small number of requests (fewer than 50), my Delphi web server no longer responds to any requests. When I run netstat on the server machine there are a number of CLOSE_WAIT socket connections. Even after an hour and after closing my server the CLOSE_WAIT socket connections persist.
The Questions
What is going on? Why does my Delphi Indy idHTTPServer stop responding when there are (even just one) CLOSE_WAIT socket connection? The CLOSE_WAITs don't go away and the server does not start responding again. I have to reboot.
What am I not doing?
Here is the results of netstat command showing CLOSE_WAITs:
C:\Windows\system32>netstat -abn | findstr 62000
TCP 0.0.0.0:62000 0.0.0.0:0 LISTENING
TCP 10.1.1.13:62000 9.49.1.3:57036 TIME_WAIT
TCP 10.1.1.13:62000 9.49.1.3:57162 CLOSE_WAIT
TCP 10.1.1.13:62000 9.49.1.3:57215 CLOSE_WAIT
TCP 10.1.1.13:62000 9.49.1.3:57244 CLOSE_WAIT
TCP 10.1.1.13:62000 9.49.1.3:57263 CLOSE_WAIT
TCP 10.1.1.13:62000 9.49.1.3:57279 ESTABLISHED
TCP 10.1.1.13:62000 104.236.216.73:59051 ESTABLISHED
Here is the essence of my web server:
unit MyWebServer;
interface
Uses
...
Type
TfrmWebServer = class(TForm)
...
IdHTTPServer: TIdHTTPServer;
...
procedure IdHTTPServerCommandGet(AContext: TIdContext; ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
procedure IdHTTPServerDisconnect(AContext: TIdContext);
procedure btnStartClick(Sender: TObject);
...
dbFirebird : TIBDatabase;
txFireird : TIBTransaction;
...
private
function CreateSomeResponseStringData: string;
end;
implementation
procedure TfrmWebServer.btnStartClick(Sender: TObject);
begin
{set the IP's and proit to listen on}
IdHTTPServer.Bindings.Clear;
IdHTTPServer.Bindings.Add.IP := GetSetting(OPTION_TCPIP_ADDRESS);
IdHTTPServer.Bindings.Add.Port := Str2Int(GetSetting(OPTION_TCPIP_PORT));
{start the web server}
IdHTTPServer.Active := TRUE;
...
dbFirebird.Transactrion := txFirebird;
...
end;
procedure TfrmWebServer.IdHTTPServerCommandGet(AContext: TIdContext; ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
var
qryFirebird : TIBSql;
function CreateSomeResponseStringData: string;
begin
qryFirebird := NIL;
qryFirebird := TIBSql.Create(IdHTTPServer);
qryFirebird.Database := dbFirebird;
dbFirebird.Connected := FALSE;
dbFirebird.Connected := TRUE;
qryFirebird.Active := TRUE;
Result := {...whatever string will be returned}
end;
function CreateAnErrorResponse: string;
begin
Result := {...whatever string will be returned}
end;
begin
try
AResponseInfo.ContentText := CreateSomeResponseStringData;
{Clean up: What do I do here to make sure that the connection that was served is:
- properly closed so that I don't run out of resourses?
- anything that needs to be cleaned up is freed so no memory leaks
- TIME_WAIT, CLOSE_WAIT, any other kind of _WAITs are not accumulating?}
except;
AResponseInfo.ContentText := CreateAnErrorResponse;
end;
qryFirebird.Free;
end;
procedure TfrmWebServer.IdHTTPServerDisconnect(AContext: TIdContext);
begin
{Maybe I do the "Clean Up" here? I tried Disconnect as shown but still lots of
TIME_WAIT tcp/ip connections accumulate. even after the app is closed}
AContext.Connection.Disconnect;
end;
end.

There are at least two major issues with this code that could cause the crashing:
The database and transaction objects are global to all threads created by IdHTTPServer. When you disconnect the database it would disconnect for all threads.
If there is a run time error assigning content text this line AResponseInfo.ContentText := CreateAnErrorResponse; is not in an exception block.
Here is how I would fix this:
...
procedure TfrmWebServer.btnStartClick(Sender: TObject);
begin
{set the IP's and port to listen on}
IdHTTPServer.Bindings.Clear;
IdHTTPServer.Default.Port := Str2Int(GetSetting(OPTION_TCPIP_PORT));
IdHTTPServer.Bindings.Add.IP := GetSetting(OPTION_TCPIP_ADDRESS);
{start the web server}
IdHTTPServer.Active := TRUE;
...
end;
procedure TfrmWebServer.IdHTTPServerCommandGet(AContext: TIdContext; ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
var
{make these local to each thread}
qryFirebird : TIBSql;
dbFirebird : TIBDatabase;
txFirebird : TIBTransaction;
function CreateSomeResponseStringData: string;
begin
dbFirebird := TIBDatbase.Create(IdHTTPServer);
txFirebird := TIBTransaction.Create(IdHTTPServer);
qryFirebird := TIBSql.Create(IdHTTPServer);
dbFirebird.Transaction := txFirebird;
qryFirebird.Database := dbFirebird;
...Add params that do the log in to database
dbFirebird.Connected := TRUE;
qryFirebird.Active := TRUE;
Result := {...whatever string will be returned}
end;
function CreateAnErrorResponse: string;
begin
Result := {...whatever string will be returned}
end;
begin
try
try
...
AResponseInfo.ContentText := CreateSomeResponseStringData;
...
except;
try
AResponseInfo.ContentText := CreateAnErrorResponse;
except
{give up}
end;
end;
finaly
qryFirebird.Free;
dbFirebird.Free;
txFirebird.Free;
end;
end;
end.

Related

TIdHTTPServer Contexts Count keeps increasing

I have created a TIdHTTPServer based web server that runs on Windows. Indy version is very recent (Jan 2015). I have MaxConnections set to 1000, and have developed a mechanism to restart the web server when DoMaxConnections is called.
Once an hour I log the connection count:
TIdThreadSafeList(IdHTTPServer1.Contexts).count;
For the most part the connection count is always increasing. Once in a while I will see it decrease (say from 525 to 520). But the overall trend is increasing. After 5 days or so it gets to 1000 and the server resets (I understand 1000 is relatively small, but don't think it is germane to the subject).
Even on weekends, when traffic is lower, it still increases. I would have thought it would decrease at times of lower traffic. Why are some of these connections not closing?
I do netstat -an and see a lot of TIME_WAIT status.
Other relevant source code:
ServerIOHandler := TIdServerIOHandlerSSLOpenSSL.Create(self);
ServerIOHandler.SSLOptions.CertFile := 'C:\xxxxxx.crt';
ServerIOHandler.SSLOptions.KeyFile := 'C:\xxxxxx.key';
ServerIOHandler.SSLOptions.RootCertFile := 'C:=xxxxxx.crt';
ServerIOHandler.SSLOptions.Method := sslvSSLv23;
ServerIOHandler.SSLOptions.Mode := sslmServer;
IdHTTPServer1 := TIdHTTPServer.Create;
IdHTTPServer1.MaxConnections := 1000;
IdHTTPServer1.AutoStartSession := True;
IdHTTPServer1.KeepAlive := True;
IdHTTPServer1.SessionState := True;
IdHTTPServer1.OnCommandGet := MainGet;
idHttpServer1.ParseParams := True;
idHttpServer1.IOHandler := ServerIOHandler;
idHttpServer1.Bindings.Add.Port := 80;
idHttpServer1.Bindings.Add.Port := 443;
IdHTTPServer1.Active := True;
Update I thought it would be useful to add some netstat statistics.
With the server running for 24+ Hours:
Contexts Count: 587
netstat ESTABLISHED from external to port 80:580
netstat TIME_WAIT from external to port 80:819
I then restarted my Indy Service (did not reboot windows server however):
Contexts Count: 60
netstat ESTABLISHED from external to port 80:49
netstat TIME_WAIT from external to port 80:797
Update 2 - Request was made to show the MainGet procedure. There is too much code to feasibly do this. But what I have done is to provide snippets of code that could shed light on problem. Below is a list of some of the calls I am making (under different circumstances to improve performance).
AResponseInfo.ContentText := filetostring('c:\.....');
AResponseInfo.ResponseNo
Aresponseinfo.RawHeaders.Add('Connection:close');
Aresponseinfo.CustomHeaders.Add('Keep-Alive: timeout=30');
Aresponseinfo.Connection:='keep-alive';
AResponseInfo.ContentStream := MemoryStream;
AResponseInfo.Redirect('xxxxx');
AResponseInfo.ServeFile(AContext,mFile);
AResponseinfo.CacheControl:='max-age=1209600';
Update 3 - provide additional information of my the MainGet function. I am keeping track of the number of times the function is called and the number of times that it exits. The value of iGlobalGetStart and iGlobalGetFinish are always very close (certainly way less than number of contexts that are active).
procedure TMyWebUnit.MainGet(AContext: TIdContext;
ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
try
InterlockedIncrement(iGlobalGetStart);
.... all of my logic
finally
InterlockedIncrement(iGlobalGetFinish);
end;
Update 4 - I have implemented additional tracking code. I have created my own class descended from TIdServerContext. In it I set the date/time it is created, and also have a date/time member called DatetimeLastUsed which will get updated in oncommandget. Here is the class:
type
TEAIdServerContext = class(TIdServerContext)
DateTimelastUsed,DateTimeCreated: TDateTime;
constructor Create(AConnection: TIdTCPConnection; AYarn: TIdYarn; AList: TIdContextThreadList = nil); override;
destructor Destroy; override;
end;
I set the IdHTTPServer1.ContextClass to this new class:
IdHTTPServer1.ContextClass := TEAIdServerContext;
In my OnCommandGet, the first thing I do is to set the date/time of DateTimeLastUsed to the current time:
TEAIDServerContext(AContext).DateTimeLastUsed := Now;
I also have a function which will list all contexts:
procedure SayContexts();
var
i: Integer;
mstring: String;
mList: TList;
begin
mList := IdHTTPServer1.Contexts.LockList;
mstring := 'Contexts:'+inttostr(mlist.Count);
with mList do try
for i := 0 to count -1 do begin
mString := mString +
datetimetostr(TEAIDServerContext(mList[i]).datetimecreated)+'; last used:'+datetimetostr(TEAIDServerContext(mList[i]).datetimeLastUsed)
end;
end;
IdHTTPServer1.Contexts.UnLockList;
....
end;
The connections that are remaining open are never getting to the oncommandget code because their datetimelastused field is null. Is this normal?

How to continuously send messages with TIdTCPServer?

I need to create a delphi application where when it's started the server is started as well and starts sending messages immediately, but I haven't found an example or tutorial and the nearly 5000 page Indy manual doesn't make it clear to me how I can do this...
This example uses a Delphi 2009 VCL application with a main form, which contains only one visual component, a TMemo named “MemoLog”.
Client and server are both started in the FormCreate event. Note that the client code does not handle connection loss, but this can be implemented with a separate re-connect loop within the thread.
procedure TServerPushExampleForm.FormCreate(Sender: TObject);
begin
ExampleServer := TMyPushServer.Create;
ExampleServer.DefaultPort := 8088;
ExampleServer.Active := True;
ExampleClient := TMyPushClientThread.Create('localhost', 8088,
MemoLog.Lines);
end;
Server
The server code uses a TIdTCPCustomServer subclass which waits for a random time and then sends a string to the client.
function TMyPushServer.DoExecute(AContext: TIdContext): Boolean;
begin
Result := inherited;
// simulate hard work
Sleep(Random(3000));
AContext.Connection.IOHandler.WriteLn(
'Completed at ' + TimeToStr(Now), IndyTextEncoding_UTF8);
end;
Client
The client code uses a TThread subclass to run asynchronously without blocking the main VCL thread. It contains a private TIdTCPClient instance, and periodically tries to receive a string from the connection.
...
S := TCPClient.IOHandler.ReadLn(IndyTextEncoding_UTF8);
...
Full Delphi Form Code
Below is the full code for the example main form.
unit Unit1;
interface
uses
IdCustomTCPServer, IdTCPClient, IdContext,
SysUtils, Classes, Forms, StdCtrls, Controls;
type
TMyPushClientThread = class(TThread)
private
TCPClient: TIdTCPClient;
FLog: TStrings;
public
constructor Create(AHost: string; APort: Word; ALog: TStrings);
destructor Destroy; override;
procedure Execute; override;
end;
TMyPushServer = class (TIdCustomTCPServer)
protected
function DoExecute(AContext: TIdContext): Boolean; override;
end;
TServerPushExampleForm = class(TForm)
MemoLog: TMemo;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
ExampleClient: TMyPushClientThread;
ExampleServer: TMyPushServer;
end;
var
ServerPushExampleForm: TServerPushExampleForm;
implementation
uses
IdGlobal;
{$R *.dfm}
procedure TServerPushExampleForm.FormCreate(Sender: TObject);
begin
ExampleServer := TMyPushServer.Create;
ExampleServer.DefaultPort := 8088;
ExampleServer.Active := True;
ExampleClient := TMyPushClientThread.Create('localhost', 8088, MemoLog.Lines);
end;
procedure TServerPushExampleForm.FormDestroy(Sender: TObject);
begin
ExampleServer.Free;
ExampleClient.Terminate;
ExampleClient.WaitFor;
ExampleClient.Free;
end;
{ TMyPushServer }
function TMyPushServer.DoExecute(AContext: TIdContext): Boolean;
begin
Result := inherited;
// simulate hard work
Sleep(Random(3000));
AContext.Connection.IOHandler.WriteLn(
'Completed at ' + TimeToStr(Now), IndyTextEncoding_UTF8);
end;
{ TMyPushClientThread }
constructor TMyPushClientThread.Create(AHost: string; APort: Word; ALog: TStrings);
begin
inherited Create(False);
FLog := ALog;
TCPClient := TIdTCPClient.Create;
TCPClient.Host := AHost;
TCPClient.Port := APort;
TCPClient.ReadTimeout := 500;
end;
destructor TMyPushClientThread.Destroy;
begin
TCPClient.Free;
inherited;
end;
procedure TMyPushClientThread.Execute;
var
S: string;
begin
TCPClient.Connect;
while not Terminated do
begin
S := TCPClient.IOHandler.ReadLn(IndyTextEncoding_UTF8);
if not TCPClient.IOHandler.ReadLnTimedout then
begin
TThread.Queue(nil,
procedure
begin
FLog.Append(S);
end);
end;
end;
TCPClient.Disconnect;
end;
end.
(From https://mikejustin.wordpress.com/2014/04/19/indy-10-tidtcpserver-server-side-message-push-example/)
The way that Indy works is to have the client (TidTCPClient) connect to the server (TidTCPServer) and then exchange data between them, back-and-forth until the connection is terminated either willfully or by premature disconnect.
I am only referring to the actual Indy TCP components here and not to the way you see your applications.
At the application level you might consider an application the server app and another the client app but both can/may contain both TidTCPClient and TidTCPServer components with which they communicate with other apps. This means that the server app can initiate a connection to a client app via the server app's TidTCPClient component and the client app will receive the connection via its TidTCPServer component. This would be a possible solution but keep in mind that generally clients are dynamic and ever changing while servers are usually static and as such it will be a mission to keep track of where clients are. Too many headaches and too much work as well.
So I think it is better to have clients keep track of their rarely changing servers and as such it is better to have a TidTCPServer component for the server app and have it wait for client connections before it starts to send messages.
So to implement; your clients would have to constantly try to connect to the server at regular intervals until it finds the server. The server can then send as many messages as it wants until asked to stop or until premature disconnect in which case the cycle will be restarted. There are ways in Indy to keep track of client connections and you can keep an internal list of the clients through those means. This makes more sense. It is the way that most client-server apps work. Just think of Skype and any Web Server. The clients contacts the server and receives data if needs be.
At the server side:
Create the TidTCPServer object.
Setup the TidTCPServer to listen on one or more of its local IP
Addresses and choose an IP port for them.
Assign code to the TidTCPServer which it will run as soon as a client
connects to it via the OnExecute of the TidTCPServer. In this code you will send the messages to the connected client.
Activate the TidTCPServer so that it is in Listening mode.
At the client side:
Create a TidTCPClient object.
Setup the TidTCPClient to use a specific host and port (The IP
Address/Host Name of the server and the port you chose)
In a repeating loop with intervals try to connect to the server.
As soon as the connection is established the client may send the
server something or immediatelly try to read from the connection
which is what it will receive if the server sends something
There are many examples for this type of operation. You must try first and if you struggle you can always ask questions specific to the problem you are having.

Unable to connect IdPop3 to IdPop3Server via SSL

I have a TIdPop3Server in one application that has a IdServerIOHandlerSSLOpenSSL1 attached to it and retrieves emails and sends them to a TIdPop3 client in another application (having TIdSSLIOHandlerSocketOpenSSL attached to it). Everything's fine when the connections are made insecure using port 110. But when I try to use SSL connection through port 995 I get error Connection Closed Gracefully after connect attemp from the client fails. This is my Pop3SeverOnConnect event :
procedure TMainForm.Pop3ServerConnect(AContext: TIdContext);
begin
if (AContext.Connection.IOHandler is TIdSSLIOHandlerSocketBase) then
TIdSSLIOHandlerSocketBase(AContext.Connection.IOHandler).PassThrough :=
(AContext.Binding.Port <> 995);
showmessage('SSL connection made!');
end;
And this is the client-side :
procedure TMainForm.btnCheckMailBoxClick(Sender: TObject);
begin
IdSSLIOHandlerSocketOpenSSL1.PassThrough := False;
POP3Client.IOHandler := IdSSLIOHandlerSocketOpenSSL1;
with POP3Client do begin
AuthType := patUserPass;
Host := myHost;
UserName := myUserName;
Password := myPass;
Port := myPort;
end;
try
POP3Client.Connect;
Except on e : Exception do
showmessage('error=' + e.Message);
end;
// code for retrieving message data
end;
And I always get an exception from Pop3Client.Connect like I've already mentioned above (The message SSL connection made! in the server application never shows up). If I use however another mail client like for example Mozilla Thunderbird I achieve a successful SSL connection for port 995. So the problem should be somewhere in the client's procedure but who knows - that's why I'm asking you guys for help.
In your client code, you need to set the TIdPOP3.UseTLS property instead of the TIdSSLIOHandlerSocketOpenSSL.PassThrough property directly, eg:
procedure TMainForm.btnCheckMailBoxClick(Sender: TObject);
begin
with POP3Client do
begin
IOHandler := IdSSLIOHandlerSocketOpenSSL1;
AuthType := patUserPass;
UseTLS := utUseImplicitTLS; // <-- here
Host := myHost;
UserName := myUserName;
Password := myPass;
Port := myPort;
end;
try
POP3Client.Connect;
try
// code for retrieving message data
finally
POP3Client.Disconnect;
end;
except
on e : Exception do
ShowMessage('error=' + e.Message);
end;
end;
In your server code, you need to get rid of the ShowMessage(). TIdPOP3Server is multi-threaded, the OnConnect event is fired in the context of a worker thread, and ShowMessage() is not thread-safe. If you must display a popup message, use Windows.MessageBox() instead.

Indy10 TCP and asynchronous data exchange

Good morning to all.I am building a Delphi TCP server/client application using Indy 10.0.52,with TIdTCPClient and TIdTCPServer. I have problem with receiving asynchronous responses from server. Here's my part of sample code:
Client:
procedure TfrmMain.ClientSend();
begin
tcpClient.IOHandler.WriteLn('100|HelloServer');
if tcpClient.IOHandler.ReadLn() = '1100' then //Here I get 1100
begin
tcpClient.IOHandler.WriteLn('200|Are_you_ready_for_data?');
if tcpClient.IOHandler.ReadLn() = '1200' then
begin
end;
end;
end;
Server:
procedure TfrmMain.tcpServerExecute(AContext: TIdContext);
var command:Integer;
var received,value:String;
begin
received := AContext.Connection.IOHandler.ReadLn;
command := StrToInt(SomeSplitFunction(received,'first_part')); //Here I get 100
value := SomeSplitFunction(received,'second_part'); //Here I get 'HelloServer'
case command of
100:begin
//Do something with value...
AContext.Connection.IOHandler.WriteLn('1100');
end;
200:begin
//Do something with value...
AContext.Connection.IOHandler.WriteLn('1200');
end;
end;
end;
The problem is that the case 200 on tcpServerExecute is never executed, therefore the second ReadLn on client site is never read.Is multiple asynchronous data sending in single procedure supported?I have came across several examples with simple Indy TCP Server/Client applications, but I'm little stuck here.Just to mention that connection is working and I connect to server without problems.

Not able to read messages while making tcp ip connection in delphi

I have on exe which is I run on my local machine (127.0.0.1). This exe writes at port 1234 and reads at 5678. exe writes after every 50 seconds an integer value say 1212, 4545. 6767 etc. I want to read that integer value and display. So I am using Indy Client to serve the purpose. I have developed following code snippet for that.
IdTCPClient1.Port := 1234; //Set port to connect to
IdTCPClient1.Host := '127.0.0.1'; //Set host to connect to
IdTCPClient1.Connect; //Make connection
sMsg := IdTCPClient1.Socket.ReadLn; //Read the response from the server
ShowMessage(sMsg);
But its not reading. While debugging it gets stuck at line (sMsg := IdTCPClient1.Socket.ReadLn;)
When I try to do this by using telnet command like this
telnet 127.0.0.1 1234
some miscellaneous or special characters are displayed after regular intervals not the integer values which server sends.
Please suggest any solution for this.
I'm not sure about where resides your problem, so, I'm posting a full example to create a basic Indy Server and Client applications.
First, I have a server application with a IdTCPServer component and a button. Relevant properties are:
object Button1: TButton
Text = 'Listen'
OnClick = Button1Click
end
object IdTCPServer1: TIdTCPServer
DefaultPort = 1234
OnExecute = IdTCPServer1Execute
end
and the IdTCPServer.OnExecute and Button.OnClick method on the server looks like this:
procedure TServerForm.Button1Click(Sender: TObject);
begin
IdTCPServer1.Active := not IdTCPServer1.Active;
if IdTCPServer1.Active then
Button1.Text := 'Close'
else
Button1.Text := 'Listen';
end;
procedure TServerForm.IdTCPServer1Execute(AContext: TIdContext);
var
Num: Integer;
begin
while (IdTCPServer1.Active) and (AContext.Connection.Connected) do
begin
Num := Random(MaxInt);
AContext.Connection.IOHandler.WriteLn(IntToStr(Num));
Sleep(1000);
end;
end;
As you can see, for each connected client, we will enter a loop where each second will be written a random number (as string) to the socket.
I execute the server and press the button to start listening, accept the Firewall warning to allow the port open and then I can successfully connect and get information from this server via telnet:
Now, I created the client application.
A Button, Memo and IdTCPClient on the form, relevant properties are:
object Button1: TButton
Text = 'Connect'
OnClick = Button1Click
end
object Memo1: TMemo
end
object IdTCPClient1: TIdTCPClient
Host = 'localhost'
Port = 1234
end
and the code looks like this:
procedure TClientForm.ReadResults;
var
S: string;
begin
while IdTCPClient1.Connected do
begin
S := IdTCPClient1.IOHandler.ReadLn;
Memo1.Lines.Add(S);
//don't repeat this approach in production code, it's just a test here
Application.ProcessMessages;
end;
end;
procedure TClientForm.Button1Click(Sender: TObject);
begin
if IdTCPClient1.Connected then
begin
IdTCPClient1.Disconnect;
Button1.Text := 'Connect';
end
else
begin
IdTCPClient1.Connect;
Button1.Text := 'Disconnect';
Button1.Repaint;
ReadResults;
end;
end;
At runtime it looks like this:
The project is made in FireMonkey with Delphi XE3, but it should work also with VCL with any Delphi version that supports Indy 10.

Resources