Read Input from Indy10 - delphi

I am working with Indy10 in Delphi XE4, and am trying to make a simple TCP connection with a TCP server on the network. The server sends a simple Hello World message, and then disconnects, and waits for another connection. I have the following code, to connect, and to try and read the message from the server.
with Client do
begin
Host := '10.10.81.122';
Port := 3490;
Connect;
if IOHandler.Connected then
writeln('Connected!');
if IOHandler.InputBufferIsEmpty then
begin
if IOHandler.CheckForDataOnSource(1000) then
begin
writeln('Buffer size:' + IntToStr(IOHandler.RecvBufferSize));
IOhandler.ReadBytes(buffer, IOHandler.RecvBufferSize);
end
else
writeln('Message not received');
end;
end;
When I run this code, I get a value for the recvBufferSize, but the buffer remains empty and then I get a connection terminated gracefully message.

The RecvBufferSize has nothing to do with how many data is actually available for reading. The RecvBufferSize merely specifies the size that Indy uses when allocating internal buffers for reading raw data from the socket. Whatever raw data is actually read gets placed in the IOHandler.InputBuffer for later use by ReadBytes() and other IOHandler reading methods.
Use this code instead:
with Client do
begin
Host := '10.10.81.122';
Port := 3490;
try
Connect;
WriteLn('Connected!');
if IOHandler.CheckForDataOnSource(1000) then
begin
WriteLn(Bytes available:' + IntToStr(IOHandler.InputBuffer.Size));
IOHandler.ReadBytes(buffer, IOHandler.InputBuffer.Size);
end else
WriteLn('Message not received');
except
on E: Exception do
WriteLn('Error! ' + E.Message);
end;
end;
Alternatively:
with Client do
begin
Host := '10.10.81.122';
Port := 3490;
ReadTimeout := 1000;
try
Connect;
WriteLn('Connected!');
IOHandler.ReadBytes(buffer, -1);
if Length(buffer) > 0 then
WriteLn('Bytes read:' + IntToStr(Length(buffer)))
else
WriteLn('Message not received');
except
on E: Exception do
WriteLn('Error! ' + E.Message);
end;
end;
That being said, that is no guaratee that you will actually receive the full message, since this code is merely reading whatever raw data is available at the moment of reading. Since your server is sending a line break at the end of the data, you can use ReadLn() instead of ReadBytes() and let Indy wait until the line break is actually received:
with Client do
begin
Host := '10.10.81.122';
Port := 3490;
ReadTimeout := 1000;
try
Connect;
WriteLn('Connected!');
Msg := IOHandler.ReadLn;
if not IOHandler.ReadLnTimedOut then
WriteLn('Message read:' + Msg)
else
WriteLn('Message not received');
except
on E: Exception do
WriteLn('Error! ' + E.Message);
end;
end;
Alternatively:
with Client do
begin
Host := '10.10.81.122';
Port := 3490;
try
Connect;
WriteLn('Connected!');
Msg := IOHandler.ReadLn(LF, 1000);
if not IOHandler.ReadLnTimedOut then
WriteLn('Message read:' + Msg)
else
WriteLn('Message not received');
except
on E: Exception do
WriteLn('Error! ' + E.Message);
end;
end;

Related

Trying to block a Indy connection (Incoming)

I intent to block certain clients from connecting to my server. I have the following code segments:
procedure TMain.WebServiceConnect(AContext:TIdContext);
Var
IP:String;
begin
try
IP:=AContext.Binding.PeerIP;
if Allowlist.IndexOf(IP)=-1 then Begin;
if Blocklist.IndexOf(IP)>-1 then Begin;
LogWrite('Blocking IP "'+IP+'"!',Detailed);
LogRequest(IP,'*BLOCKED*');
AContext.Connection.Disconnect;
End;
End;
except
ON E:Exception do
LogWrite('"'+E.Message+'" while connection-check on blocklist',Detailed);
end;
end;
procedure TMain.WebServiceReceive(AContext:TIdContext; ARequestInfo:TIdHTTPRequestInfo; AResponseInfo:TIdHTTPResponseInfo);
begin
try
try
LogWrite('Handling connection from '+ARequestInfo.RemoteIP,Debug);
AResponseInfo.ResponseNo:=401;
ProcessWebRequest(AContext,ARequestInfo,AResponseInfo);
LogWrite('Closing connection to '+ARequestInfo.RemoteIP,Debug);
except
ON E:Exception do
LogWrite('"'+E.Message+'" while handling request!',verbose,FALSE);
End;
finally
End;
End;
But I see both events trigger from blocked IPs! I do see the "blocked" logline, but it seems the AContext.Connection.Disconnect doesnt act as expected.
What is wrong?
I see no possible way that the server's OnCommand... events can be fired for a given client connection if the OnConnect event has closed that connection. The server would fail to read the client's request data to populate the TIdRequestInfo object before firing the OnCommand... events.
So, either you are not actually calling Disconnect() like you are expecting, or you are misdiagnosing the issue.
For instance, in your log messages, try including the AContext.Binding.PeerPort, or even the AContext.Binding.Handle, in addition to the PeerIP to make sure the log messages actually belong to the same client connection. PeerIP alone is not adequate to uniquely identify individual connections.
That being said, in the unlikely situation that Disconnect() is actually not closing the connection, you can force a disconnect by raising an exception, such as via SysUtils.Abort(), so that the server will terminate the calling thread and close the socket.
Also, speaking of exceptions, you should re-raise any exception that you catch with except (or, at least, any Indy exception derived from EIdException), let the server handle it.
Try something more like this:
procedure TMain.WebServiceConnect(AContext:TIdContext);
var
IP: String;
Port: TIdPort;
begin
try
IP := AContext.Binding.PeerIP;
Port := AContext.Binding.PeerPort;
if Allowlist.IndexOf(IP) = -1 then begin
if Blocklist.IndexOf(IP) > -1 then begin
LogWrite('Blocking IP "' + IP + ':' + IntToStr(Port) + '"!', Detailed);
LogRequest(IP, '*BLOCKED*');
AContext.Connection.Disconnect;
SysUtils.Abort;
end;
end;
except
on E: Exception do begin
if not (E is EAbort) then
LogWrite('"' + E.Message + '" while connection-check on blocklist', Detailed);
raise;
end;
end;
end;
procedure TMain.WebServiceReceive(AContext: TIdContext; ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
var
IP: String;
Port: TIdPort;
begin
try
IP := AContext.Binding.PeerIP;
Port := AContext.Binding.PeerPort;
LogWrite('Handling connection from ' + IP + ':' + IntToStr(Port), Debug);
AResponseInfo.ResponseNo := 401;
ProcessWebRequest(AContext, ARequestInfo, AResponseInfo);
LogWrite('Closing connection to ' + IP + ':' + IntToStr(Port), Debug);
except
on E: Exception do begin
LogWrite('"' + E.Message + '" while handling request!', verbose, FALSE);
raise;
end;
end;
end;

Downloading directory from FTP server with Delphi XE

i used this codes, but i am taking' "xxx.xxx" not understood' error message sometimes. And it doesn't download and than i am taking "unable to build data connection: connection time out" message.
My ConnectionTimeOut setting is 240000.
What can i do? Can you help me please? I am using Delphi XE.
Have nice day.
It is best to include your own code to solve your problem.but,To download my file,
I zip the file or folder on the server and then receive the following code in the client:
var
STListZip: TStringList;
SZipDown: String;
fFtp:TIdFTP;
begin
fFtp := TIdFTP.Create(nil);
fFtp.Passive := true;
fFtp.Host := 'myserver.com';
fFtp.Username := 'u1';
fFtp.Password := '1';
fFtp.port:='21';
fFtp.ConnectTimeout := 20000;
fFtp.TransferTimeout := 20000;
try
fFtp.Connect;
fFtp.ChangeDir('');
except
on E: Exception do
begin
ShowMessage('ERROR ftp not connect');
Exit;
end;
end;
if fFtp.Connected then
begin
STListZip := TStringList.Create;
fFtp.List(STListZip, 'abc.zip', false);
if STListZip.Count < 1 then
begin
ShowMessage('ERROR file not exist');
Exit;
end;
STListZip.Sort;
SZipDown := STListZip[STListZip.Count - 1];
try
fftp.BeginWork(wmRead);
fftp.Get(SZipDown, 'd:\', true, fftp.ResumeSupported);
fftp.Disconnect();
fftp.EndWork(wmRead);
except
on E: Exception do
begin
ShowMessage('ERROR File not download');
Exit;
end;
end;
end;
end;
Notice: Instead of abc.zip you can put *.zip to get all the zip file names.

Delphi TCPClient read string from TCPServer

I need to write a simple chat program that will be used by some customers. Basically, there are a lot of clients connected to a server and they chat together. The server works:
Here the code if needed:
//CONNECT TO THE SERVER
procedure TFormServer.ButtonStartClick(Sender: TObject);
begin
if not TCPServer.Active then
begin
try
TCPServer.DefaultPort := 8002;
TCPServer.Bindings[0].IP := LIP.Text;
TCPServer.Bindings[0].Port := StrToInt(LPort.Text);
TCPServer.MaxConnections := 5;
TCPServer.Active := true;
Memo1.Lines.Add(TimeNow + 'Server started.');
except
on E: Exception do
Memo1.Lines.Add(sLineBreak + ' ====== INTERNAL ERROR ====== ' +
sLineBreak + ' > ' + E.Message + sLineBreak);
end;
end;
end;
//DISCONNECT
procedure TFormServer.ButtonStopClick(Sender: TObject);
begin
if TCPServer.Active then
begin
TCPServer.Active := false;
Memo1.Lines.Add(TimeNow + 'Server stopped.');
end;
end;
//IF CLOSE THE APP DONT FORGET TO CLOSE SERVER!!
procedure TFormServer.FormClose(Sender: TObject; var Action: TCloseAction);
begin
ButtonStopClick(Self);
end;
procedure TFormServer.FormCreate(Sender: TObject);
begin
FClients := 0;
end;
//When a client connects I write a log
procedure TFormServer.TCPServerConnect(AContext: TIdContext);
begin
Inc(FClients);
TThread.Synchronize(nil, procedure
begin
LabelCount.Text := 'Connected sockets: ' + FClients.ToString;
Memo1.Lines.Add(TimeNow + ' Client connected # ' + AContext.Binding.IP + ':' + AContext.Binding.Port.ToString);
end);
end;
//Same, when a client disconnects I log it
procedure TFormServer.TCPServerDisconnect(AContext: TIdContext);
begin
Dec(FClients);
TThread.Synchronize(nil, procedure
begin
LabelCount.Text := 'Connected sockets: ' + FClients.ToString;
Memo1.Lines.Add(TimeNow + ' Client disconnected');
end);
end;
//WHAT I DO HERE:
//I receive a message from the client and then I send this message to EVERYONE that is connected here. It is a global chat
procedure TFormServer.TCPServerExecute(AContext: TIdContext);
var
txt: string;
begin
txt := AContext.Connection.IOHandler.ReadLn();
AContext.Connection.IOHandler.WriteLn(txt);
TThread.Synchronize(nil, procedure
begin
Memo1.Lines.Add(TimeNow + txt);
end);
end;
The sever code is very easy and minimal but it does what I need. This is the client instead:
Here there is the code, very simple:
//CONNECT TO THE SERVER
procedure TFormClient.ConnectClick(Sender: TObject);
begin
if Length(Username.Text) < 4 then
begin
Memo1.Lines.Clear;
Memo1.Lines.Add('ERROR: Username must contain at least 4 characters');
Exit;
end;
if not TCPClient.Connected then
begin
try
Username.Enabled := false;
Memo1.Lines.Clear;
TCPClient.Host := '127.0.0.1';
TCPClient.Port := 8002;
TCPClient.ConnectTimeout := 5000;
TCPClient.Connect;
Connect.Text := 'Disconnect';
except
on E: Exception do
Memo1.Lines.Add(' ====== ERROR ======' + sLineBreak +
' > ' + E.Message + sLineBreak);
end;
end
else
begin
TCPClient.Disconnect;
Username.Enabled := true;
Connect.Text := 'Connect';
end;
end;
//IF YOU FORGET TO DISCONNECT WHEN APP IS CLOSED
procedure TFormClient.FormDestroy(Sender: TObject);
begin
if TCPClient.Connected then
TCPClient.Disconnect;
end;
//Here I send a string to the server and it's good
procedure TFormClient.SendClick(Sender: TObject);
begin
if TCPClient.Connected then
begin
TCPClient.IOHandler.WriteLn(Username.Text + ': ' + EditMessage.Text);
EditMessage.Text := '';
end
else
begin
Memo1.Lines.Add('ERROR: You aren''t connected!');
end;
end;
//Problems here
procedure TFormClient.Timer1Timer(Sender: TObject);
begin
Memo1.Lines.Add(TCPClient.IOHandler.ReadLn());
end;
The problems start in the last procedure Timer1Timer. I have found that TCPServer uses a thread and this is why I call Synchronize to update the UI. Instead TCPClient does not use a thread and I manually have to check the server. Please see this code:
procedure TFormServer.TCPServerExecute(AContext: TIdContext);
var
txt: string;
begin
txt := AContext.Connection.IOHandler.ReadLn();
AContext.Connection.IOHandler.WriteLn(txt);
TThread.Synchronize(nil, procedure
begin
Memo1.Lines.Add(TimeNow + txt);
end);
end;
As you can see, when the server receives a string he immediatly sends it back to all the clients. I try to get the string here:
procedure TFormClient.Timer1Timer(Sender: TObject);
begin
Memo1.Lines.Add(TCPClient.IOHandler.ReadLn());
end;
What is wrong? I have seen a similar question here and answers said that I have to use a timer and IOHandler.ReadLn(), which is what I am doing. I think that the problem is here. How to fix?
Also timer has an interval of 200, is it too short?
I have read what Remy Lebeau said in the answer and I've produced this simple code:
procedure TFormClient.Timer1Timer(Sender: TObject);
begin
if not(TCPClient.Connected) then
Exit;
if TCPClient.IOHandler.InputBufferIsEmpty then
Exit;
Memo1.Lines.Add(TCPClient.IOHandler.InputBufferAsString());
end;
There is a Timer1 component in the form. This works as I expect but it still could lock the UI or not?
The server works
Just an FYI, you don't need the FClients variable at all, especially since you are not really accessing it safely. At the very least, use TInterlocked to access it safely. Or switch to TIdThreadSafeInteger. Though really, the only place you use it is in LabelCount, and you can get the current client count from the TIdTCPServers.Contexts property instead.
This is the client instead:
...
The problems start in the last procedure Timer1Timer.
That is because you are using a UI-based TTimer, and (like most things in Indy) the IOHandler.ReadLn() method blocks until completed. You are calling it within the context of the UI thread, so it blocks the UI message loop until a full line has arrived from the socket.
One way to work around blocking the UI is to place an Indy TIdAntiFreeze component onto your Form. Then the UI will remain responsive while ReadLn() blocks. However, this would be kind of dangerous to use with a TTimer, as you would end up with OnTimer reentry issues that could corrupt the IOHandler's data.
Really, the best solution is to simply not call the IOHandler.ReadLn() in the UI thread at all. Call it in a worker thread instead. Start the thread after you successfully Connect() to the server, and terminate the thread when disconnected. Or even move the Connect() itself into the thread. Either way, you can use Indy's TIdThreadComponent, or write your own T(Id)Thread-derived class.
Instead TCPClient does not use a thread and I manually have to check the server.
Correct, but the way you are doing it is wrong.
If you don't want to use a worker thread (which you should), then at least change your OnTimer event handler so it does not block the UI anymore, like this:
Or:
procedure TFormClient.Timer1Timer(Sender: TObject);
begin
if TCPClient.IOHandler.InputBufferIsEmpty then
begin
TCPClient.IOHandler.CheckForDataOnSource(0);
TCPClient.IOHandler.CheckForDisconnect(False);
if TCPClient.IOHandler.InputBufferIsEmpty then Exit;
end;
// may still block if the EOL hasn't been received yet...
Memo1.Lines.Add(TCPClient.IOHandler.ReadLn);
end;
Alternatively:
procedure TFormClient.Timer1Timer(Sender: TObject);
begin
// Connected() performs a read operation and will buffer
// any pending bytes that happen to be received...
if not TCPClient.Connected then Exit;
while TCPClient.IOHandler.InputBuffer.IndexOf(Byte($0A)) <> -1 do
Memo1.Lines.Add(TCPClient.IOHandler.ReadLn());
end;
I have seen a similar question here and answers said that I have to use a timer and IOHandler.ReadLn(), which is what I am doing.
Whoever said that is wrong, or you misunderstood what was required. Using a timer in the UI is a possible solution, if used correctly, but it is not a very good solution.
Create a thread for your tcpclient and sync messages back to the UI.

Send email using indy component delphi xe2 SSL negotiation faild

I tried with Indy component to send email in XE2, and it works fine on my laptop that I compiled my project on.
But if I take my exe project to another PC, it shows an error message
Connection closed gracefully
or, sometimes I get
SSL Negotiation failed
Actually I tried many times to solve my problem, but I can't.
This is my code - where is my mistake? I need a practical solution.
procedure Gmail(username, password, totarget, subject, body :string);
var
DATA : TIdMessage;
SMTP : TIdSMTP;
SSL : TIdSSLIOHandlerSocketOpenSSL;
result:Boolean;
begin
try
SMTP := TIdSMTP.Create(nil);
DATA := TIdMessage.Create(nil);
SSL := TIdSSLIOHandlerSocketOpenSSL.Create(nil);
SSL.Destination := 'smtp.gmail.com:587';
SSL.Host := 'smtp.gmail.com';
// SSL.MaxLineAction.maException;
SSL.Port:= 587;
SSL.SSLOptions.Method := sslvTLSv1;
SSL.SSLOptions.Mode := sslmUnassigned;
SSL.SSLOptions.VerifyMode := [];
SSL.SSLOptions.VerifyDepth := 0;
DATA.From.Address := username;
DATA.Recipients.EMailAddresses := totarget;
DATA.Subject := subject;
DATA.Body.Text := body;
if FileExists('D:\Test1.txt') then
TIdAttachmentFile.Create(DATA.MessageParts, 'D:\Test1.txt');
SMTP.IOHandler := SSL;
SMTP.Host := 'smtp.live.com';
SMTP.Port := 587 ;
SMTP.Username := username;
SMTP.Password := password;
// SMTP.SASLMechanisms;
SMTP.UseTLS := utUseExplicitTLS;
try
try
SMTP.Connect;
SMTP.Send(DATA);
Result := True;
except
on E:Exception do
begin
ShowMessage('Cannot send E-Mail: ' + E.Message);
Result := False;
end;
end;
finally
if SMTP.Connected then SMTP.Disconnect;
end;
except
on E : Exception do
begin
ShowMessage('Error in the function SendEmailDelphi: ' + E.Message);
Result := False;
end;
end;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
Timer1.Enabled:= True;
end;
procedure TForm1.Timer1Timer(Sender: TObject);
begin
mail_username := 'email#gmail.com';
mail_password := 'pass';
mail_to := 'email#gmail.com';
mail_subject := 'Text email from Delphi project';
mail_body := 'this is a test email sent from Delphi project, do not reply';
try
begin
Gmail(mail_username, mail_password, mail_to , mail_subject, mail_body);
end;
finally
end;
end;
DO NOT set the SSL.Destination, SSL.Host, or SSL.Port properties manually! TIdTCPClient.Connect() handles that for you. Besides, don't you think it's odd that you are setting SSL.Destination/Host to smtp.gmail.com but are setting SMTP.Host to smtp.live.com instead? Gmail and Live are not the same service provider.
Also, SSL.SSLOptions.Mode should be set to sslmClient instead of sslmUnassigned. Not too important, TIdSSLIOHandlerSocketOpenSSL will simply flip it when it configures the connection. But you should do it anyway, since you know your code is acting as a client.
And lastly, try setting SMTP.UseTLS before setting SMTP.Port, as setting UseTLS may change the Port, so you want to make sure you are really connecting to the correct port you are expecting.
With that said, the SSL Negotiation failed error means the TLS handshake was started but failed part-way through the negotiation. Try assigning handlers to TIdSSLIOHandlerSocketOpenSSL's OnStatusInfo/Ex events to see how far the handshake is actually getting. And if you are using a relatively modern version of Indy 10, try looking at the raised exception's InnerException property, it might give you a clue as to what went wrong before the EIdTLSClientTLSHandShakeFailed exception was raised afterwards.

Sending mail using Indy and smtp

Im trying to send mail using this code:
With IdMessage1 Do Begin
Recipients.EMailAddresses := 'XXXXX#gmail.com';
From.Address := 'XXXXX#gmail.com';
From.Name := edit_from.Text;
CCList.EMailAddresses := '';
BccList.EMailAddresses := '';
Priority := mpNormal;
Subject := edit_subject.Text;
Body.Add(memo_body.Lines.Text);
End;
With IdSMTP1 Do Begin
Host := 'smtp.gmail.com';
Username := 'XXXXX#gmail.com';
Password := '*****';
IOHandler := TIdSSLIOHandlerSocketOpenSSL.Create(Self);
Port := 465;
UseTLS := utUseImplicitTLS;
Try
Connect;
Except
End;
If Not Connected Then Begin
Showmessage('Error');
Exit;
End;
Try
Send(IdMessage1);
Finally
Disconnect;
End;
End;
It works fine on my computer but when i test it on other machines the 'ERROR' (Error in If block before last Try block) will be raised...
Where is the problem?
This is not the proper way to do error handling with Indy. It should be more like this instead:
With IdSMTP1 Do Begin
IOHandler := TIdSSLIOHandlerSocketOpenSSL.Create(Self);
UseTLS := utUseImplicitTLS;
Host := 'smtp.gmail.com';
Username := 'XXXXX#gmail.com';
Password := '*****';
Port := 465;
Try
Connect;
Try
Send(IdMessage1);
Finally
Disconnect;
End;
Except
Showmessage('Error');
Exit;
End;
End;
Send() and Disconnect() can fail just as easily as Connect() can. If you want Connect() to be in its own try/except block, then at least don't use Connected to validate whether Connect() succeeded:
Try
Connect;
Except
Showmessage('Error connecting');
Exit;
End;
Try
Try
Send(IdMessage1);
Finally
Disconnect;
End;
Except
Showmessage('Error sending');
Exit;
End;
That being said, the exception tells you what actually failed, so do not ignore it. Had you displayed its content, you would have had a better idea of what was failing, eg:
Except
on E: Exception do
Begin
ShowMessage(Format('Error!'#10'[%s] %s', [E.ClassName, e.Message]));
Exit;
End;
End;
The most likely culprit is that you did not deploy the OpenSSL DLLs with your app. You can download them from OpenSSL's website, or from Indy's Fulgan mirror.

Resources