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.
Related
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.
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.
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.
I am testing IndyFTP to upload a file to a server. The file is uploaded but has 0 bytes because there is a EIdAccessTimeout exception - 'Accept timed out". How can I prevent the exception? Is my code incorrect? The code is shown below:
procedure TForm1.FTPUpload1Click(Sender: TObject);
{ Indy FTP Upload. }
var
iHost: string;
iUsername: string;
iPassword: string;
iFolder: string;
iSourceFile: string;
iDestFile: string;
iAppend: boolean;
iStartPos: Int64;
begin
iHost := FTPHost1.Text;
iUsername := Username1.Text;
iPassword := Password1.Text;
iFolder := ServerFolder1.Text;
if FileExists(SourceFile1.Text) then
iSourceFile := SourceFile1.Text
else
Exit;
if FileExists(SourceFile1.Text) then
iDestFile := ExtractFileName(SourceFile1.Text)
else
Exit;
iAppend := False;
iStartPos := -1;
IdFTP1.Host := iHost;
IdFTP1.Username := iUsername;
IdFTP1.Password := iPassword;
IdFTP1.Connect;
IdFTP1.TransferType := ftBinary;
IdFTP1.Put(iSourceFile);
IdFTP1.Disconnect;
end;
There are some unused vars listed because I am just learning and have not used some of the parameters yet.
Most likely, your FTP client is set to ACTIVE mode, so this error means that after a successful login to the FTP server, the "reverse" connection couldn't be established (the file transfer).
In active mode FTP the client connects from a random unprivileged port
(N > 1023) to the FTP server's command port, port 21. Then, the client
starts listening to port N+1 and sends the FTP command PORT N+1 to the
FTP server. The server will then connect back to the client's
specified data port from its local data port, which is port 20.
Active FTP vs. Passive FTP, a Definitive Explanation
You can set to passive mode this way:
IdFTP1.Passive := True;
EDIT
In addition, use try-except-finally blocks, so you can do some error handling. Something like:
try
IdFTP1.Connect;
try
IdFTP1.Put(...);
finally
IdFTP1.Disconnect;
end;
except
// couldn't connect
end;
When i send a message from TCPClient to a TCPServer it will be handled using OnExecute event in the server . Now i want to handle the received messages in the Client but TCPClient doesn't have any event for this. So i have to make a thread to handle them manually. how can i do it ?
As others said in response to your question, TCP is not a message oriented protocol, but a stream one. I'll show you how to write and read to a very simple echo server (this is a slightly modified version of a server I did this week to answer other question):
The 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 server starts with a welcome message, then just reads the connection byte per byte. The server replies the same byte, until the received byte is 65 (the disconnect command) 65 = 0x41 or $41. The server then end with a good 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 next byte procedure can be anything you want to provide a byte. For example, to get input from the user, you can turn the KeyPreview of your form to true and write a OnKeyPress event handler and the NextByte 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 in the form will be sent to the server and then read from there and added to memo1. If the input focus is already in Memo1 you'll see each character twice, one from the keyboard and the other form the server.
So, in order to write a simple client that gets info from a server, you have to know what to expect from the server. Is it a string? multiple strings? Integer? array? a binary file? encoded file? Is there a mark for the end of the connection? This things are usually defined at the protocol or by you, if you're creating a custom server/client pair.
To write a generic TCP without prior known of what to get from the server is possible, but complex due to the fact that there's no generic message abstraction at this level in the protocol.
Don't get confused by the fact there's transport messages, but a single server response can be split into several transport messages, and then re-assembled client side, your application don't control this. From an application point of view, the socket is a flow (stream) of incoming bytes. The way you interpret this as a message, a command or any kind of response from the server is up to you. The same is applicable server side... for example the onExecute event is a white sheet where you don't have a message abstraction too.
Maybe you're mixing the messages abstraction with the command abstraction... on a command based protocol the client sends strings containing commands and the server replies with strings containing responses (then probably more data). Take a look at the TIdCmdTCPServer/Client components.
EDIT
In comments OP states s/he wants to make this work on a thread, I'm not sure about what's the problem s/he is having with this, but I'm adding a thread example. The server is the same as shown before, just the client part for this simple server:
First, the thread 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 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 is stream-based interface. Consequently don't expect that you will get a "message" on the receiver. Instead you read incoming data stream from the socket and parse it according to your high-level protocol.
Here is my code to Read / Write with Delphi 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 "messages" (definition of "message" depends on the protocol used), I recommend to take a look at the implementation of TIdTelnet in the protocols\IdTelnet unit.
This component uses a receiving thread, based on a TIdThread, which asynchronously receives messages from the Telnet server, and passes them to a message handler routine. If you have a similar protocol, this could be a good starting point.
Update: to be more specific, the procedure TIdTelnetReadThread.Run; in IdTelnet.pas is where the asynchronous client 'magic' happens, as you can see it uses Synchronize to run the data processing in the main thread - but of course your app could also do the data handling in the receiving thread, or pass it to a worker thread to keep the main thread untouched. The procedure does not use a loop, because looping / pausing / restarting 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 set Timer.Interval something else 1.
Because, the received data deletes after some milliseconds.