(Using Delphi 2009) I'm trying to write a simple network connection monitor using Indy TidIcmpClient. The idea is to ping an address, then inside an attached TidIcmpClient.OnReply handler test how much data was returned. If the reply contains > 0 bytes then I know the connection succeeded but if either the TidIcmpClient timed out or the reply contains 0 bytes I will assume the link is down
I'm having difficulty understanding the logic of TidIcmpClient as there is no 'OnTimeout' event.
Two sub questions...
Does TidIcmpClient.OnReply get called anyway, either (a) when data is received OR (b) when the timeout is reached, whichever comes first?
How can I distinguish a zero byte reply because of a timeout from a real reply within the timeout period that happens to contain zero bytes (or can't this happen)?
In other words is this sort of code OK or do I need to do something else to tell if it timed out or not
procedure TForm1.IdIcmpClient1Reply(ASender: TComponent; const AReplyStatus: TReplyStatus);
begin
if IdIcmpClient1.ReplyStatus.BytesReceived = 0 then
//we must have timed out, link is down
else
//got some data, connection is up
end;
procedure DoPing;
begin
IdIcmpClient1.ReceiveTimeout := 200;
IdIcmpClient1.Host := '8.8.8.8';
IdIcmpClient1.Ping;
end;
When Ping() exits, the ReplyStatus property contains the same information that is passed to the AReplyStatus parameter of the OnReply event (you are ignoring that parameter). Ping() simply calls the OnReply handler right before exiting, passing it the ReplyStatus property, so you don't actually need to use the OnReply event in your example. All that is doing is breaking up your code unnecessarily.
procedure DoPing;
begin
IdIcmpClient1.ReceiveTimeout := 200;
IdIcmpClient1.Host := '8.8.8.8';
IdIcmpClient1.Ping;
// process IdIcmpClient1.ReplyStatus here as needed...
end;
That being said, you are not processing the ReplyStatus data correctly. The BytesReceived field can be greater than 0 even if the ping fails. As its name implies, it simply reports how many bytes were actually received for the ICMP response. ICMP defines many different kinds of responses. The ReplyStatusType field will be set to the type of response actually received. There are 20 values defined:
type
TReplyStatusTypes = (rsEcho,
rsError, rsTimeOut, rsErrorUnreachable,
rsErrorTTLExceeded,rsErrorPacketTooBig,
rsErrorParameter,
rsErrorDatagramConversion,
rsErrorSecurityFailure,
rsSourceQuench,
rsRedirect,
rsTimeStamp,
rsInfoRequest,
rsAddressMaskRequest,
rsTraceRoute,
rsMobileHostReg,
rsMobileHostRedir,
rsIPv6WhereAreYou,
rsIPv6IAmHere,
rsSKIP);
If the ping is successful, the ReplyStatusType will be rsEcho, and the ReplyData field will contain the (optional) data that was passed to the ABuffer parameter of Ping(). You might also want to pay attention to the FromIpAddress and ToIpAddress fields as well, to make sure the response is actually coming from the expected target machine.
If a timeout occurs, the ReplyStatusType will be rsTimeOut instead.
Try this:
procedure DoPing;
begin
IdIcmpClient1.ReceiveTimeout := 200;
IdIcmpClient1.Host := '8.8.8.8';
IdIcmpClient1.Ping;
if IdIcmpClient1.ReplyStatus.ReplyStatusType = rsEcho then
begin
// got some data, connection is up
end
else if IdIcmpClient1.ReplyStatus.ReplyStatusType = rsTimeout then
begin
// have a timeout, link is down
end
else
begin
// some other response, do something else...
end;
end;
IdIcmpClient companent has OnReply event. This event has AReplyStatus param as type TReplyStatus. TReplyStatus has ReplyStatusType property. This property's type is TReplyStatusTypes. TReplyStatusTypes has rsTimeOut value. So add code to OnReply event and check timeout or other error.
procedure TForm1.IdIcmpClient1Reply(ASender: TComponent;
const AReplyStatus: TReplyStatus);
begin
if AReplyStatus.ReplyStatusType = rsTimeOut then
begin
//do someting on timeout.
end;
end;
Related
I have three questions:
is it possible to destroy IdTCPServer by to many connection?
I tried to test my application and when I have several connections - it works very good (even several days) but when sometimes number of connection increases application gives acess violation. I wrote application similates 50 clients sending data constantly (with only sleep(200)). And in this situation IdTCPServer gives exceptions?
My application reseives information from clients by onExecute event and modyfies databases table using
TidNotify and TIdSync classes. I believe it protects crosses connections threads?
Sending information to clients is doing by TTimer (it is only now, I'll change it to other thread).
Have I use in this situation special protection or something like that is enough:
type
PClient = ^TClient;
TClient = record
Activity_time:TDateTime;
AContext: TIdContext;
end;
...
list := server.Contexts.LockList;
try
for i := 0 to list.Count - 1 do
with TIdContext(list[i]) do
begin
if SecondsBetween(now(), PClient(data)^.activity_time) > 6 then
begin
Connection.IOHandler.Close;
Continue;
end;
try
Connection.IOHandler.writeln('E:');
Except
Connection.IOHandler.Close;
end;
end;
finally
server.Contexts.UnlockList;
end;
2.Is a simple way to refuse connection when server is to busy (I think my database isn't complicated (100 rows, only one row is modyfied by one connection) but maybe here is a way to keep stability of server?
3.I know that this question was repeating many times but I didn't find satisfying answer: how to protect application to avoid message exception: "Connection closed gracefully" and "Connection reset by peer"?
Thank You for all advices
is it possible to destroy IdTCPServer by to many connection?
You are asking the wrong question, because you are not actually destroying TIdTCPServer itself, you are simply closing idle connections from an outside thread. That kind of logic can be (and should be) handled inside of the OnExecute event instead, where it is safest to access the connection, eg:
type
PClient = ^TClient;
TClient = record
Activity_time: TDateTime;
Heartbeat_time: TDateTime;
AContext: TIdContext;
end;
procedure TForm1.serverConnect(AContext: TIdContext);
var
Client: PClient;
begin
New(Client);
Client^.Activity_time := Now();
Client^.Heartbeat_time := Client^.Activity_time;
AContext.Data := TObject(Client);
end;
procedure TForm1.serverDisconnect(AContext: TIdContext);
var
Client: PClient;
begin
Client := PClient(AContext.Data);
AContext.Data := nil;
if Client <> nil then Dispose(Client);
end;
procedure TForm1.serverExecute(AContext: TIdContext);
var
Client: PClient;
dtNow: TDateTime;
begin
Client := PClient(AContext.Data);
dtNow := Now();
if SecondsBetween(dtNow, Client^.Activity_time) > 6 then
begin
AContext.Connection.Disconnect;
Exit;
end;
if SecondsBetween(dtNow, Client^.Heartbeat_time) > 2 then
begin
AContext.Connection.IOHandler.WriteLn('E:');
Client^.Heartbeat_time := dtNow;
end;
if AContext.Connection.IOHandler.InputBufferIsEmpty then
begin
if not AContext.Connection.IOHandler.CheckForDataOnSource(100) then
Exit;
end;
// process incoming data as needed ...
Client^.Activity_time := Now();
end;
Is a simple way to refuse connection when server is to busy (I think my database isn't complicated (100 rows, only one row is modyfied by one connection) but maybe here is a way to keep stability of server?
The current architecture does not allow for refusing connections from being accepted. You can let the server accept connections normally and then close accepted connections when needed. You can do that in the OnConnect event, or you can set the server's MaxConnection property to a low non-zero number to allow the server to auto-disconnect new connections for you without wasting resources creating new TIdContext objects and threads for them.
Another option is to call the server's StopListening() method when the server is busy, so that new connections cannot reach the server anymore, and then call the server's StartListening() method when you are ready to accept new clients again. Existing clients who are already connected should not be affected, though I have not actually tried that myself yet.
I know that this question was repeating many times but I didn't find satisfying answer: how to protect application to avoid message exception: "Connection closed gracefully" and "Connection reset by peer"?
You should not avoid them. Let them happen, they are normal errors. If they are happening inside of the server's events, just let the server handle them normally for you. That is how TIdTCServer is designed to be used. If they are happening outside of the server's events, such as in your timer, then just wrap the socket operation(s) in a try/except block and move on.
I am still having issues with the TComPort component but this time is not the component itself is the logic behind it. I have a device witch sends some ascii strings via serial port i need to prase those strings the problem is the computer reacts very fast so in the event char it captures only a part of the string the rest of the string comes back later... so parsing it when it is recived makes it impossible.
I was thinking in writing a timer witch verify if there was no serial activity 10 secons or more and then prase the string that i am saving into a buffer. But this method is unprofessional isn't there a idle event witch i can listen...Waiting for the best solution for my problem. Thanks.
After using a number of serial-port-components, I've got the best results until now, by using CreateFile('\\?\COM1',GENERIC_READ or GENERIC_WRITE,0,nil,OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL,0), passing that handle to a THandleStream instance, and starting a dedicated thread to read from it. I know threads take a little more work than writing an event handler, but it still is the best way to handle any synchronization issues that arise from using serial ports.
Typical handler for OnRXChar event:
procedure XXX.RXChar(Sender: TObject; Count: Integer);
begin
ComPort.ReadStr(s, Count);
Accumulator := Accumulator + s;
if not AccumContainsPacketStart then
Accumulator := ''
else if AccumContainsPacketEndAfterStart then begin
ExtractFullStringFromAccum;
ParseIt;
end;
end;
Note.
Most com-port components do not have a clue when to report back to the owner. Normally the thread that is responsible to gather the bytes from the port is informed by the OS that one or more bytes are ready to be processed. This information is then simply popped up to your level. So when you expect the message to be transferred, you get what the OS is giving you.
You have to buffer all incoming characters in a global buffer. When you get the final character in your message string, handle the message.
Here is an example where the message start is identified with a special character and the end of the message is identified with another character.
If your message is constructed in another way, I'm sure you can figure out how to adapt the code.
var
finalBuf: AnsiString;
{- Checking message }
Function ParseAndCheckMessage(const parseS: AnsiString) : Integer;
begin
Result := 0; // Assume ok
{- Make tests to confirm a valid message }
...
end;
procedure TMainForm.ComPortRxChar(Sender: TObject; Count: Integer);
var
i,err: Integer;
strBuf: AnsiString;
begin
ComPort.ReadStr(strBuf, Count);
for i := 1 to Length(strBuf) do
case strBuf[i] of
'$' :
finalBuf := '$'; // Start of package
#10 :
begin
if (finalBuf <> '') and (finalBuf[1] = '$') then // Simple validate check
begin
SetLength( finalBuf, Length(finalBuf) - 1); // Strips CR
err := ParseAndCheckMessage(finalBuf);
if (err = 0) then
{- Handle validated string }
else
{- Handle error }
end;
finalBuf := '';
end;
else
finalBuf := finalBuf + strBuf[i];
end;
end;
If your protocol has begin/end markers, you can use TComDataPacket to provide you full packets, when they are available.
For certain amount of character we can use delay some miliseconds before ReadStr to make sure the data is completely sent. Example for 4 amount of character:
procedure TForm1.ComPort1RxChar(Sender: TObject; Count: Integer);
var
Str: String;
tegangan : real;
begin
sleep(100); //delay for 100ms
ComPort1.ReadStr(Str, 4);
...
I've been trying to set the length of the amount of characters you recover from the ReceiveText TClientSocket function and nothing seems to be working. E.g., Receiving the first leftmost character(s) from the recovered data or otherwise data stream. Is there a way to accomplish this in Delphi using this specific object?
Help would be much appreciated. Thanks in advance.
ReceiveText doesn't have any means to control the maximum length of the received text.
The easiest way in ClientType := ctBlocking mode is to use a TWinSocketStream as the documentation states:
http://docwiki.embarcadero.com/VCL/XE2/en/ScktComp.TClientSocket.ClientType
When ClientType is ctBlocking, use a TWinSocketStream object for reading and writing. TWinSocketStream prevents the application from hanging indefinitely if a problem occurs while reading or writing. It also can wait for the socket connection to indicate its readiness for reading.
Example code:
var
Stream : TWinSocketStream;
Buffer : TBytes;
S : string;
begin
SetLength(Buffer, 100); // 100 bytes buffer size
Stream := TWinSocketStream.Create(Socket, 5000); // 5 seconds or 5000 milliseconds
try
Stream.ReadBuffer(Buffer[0], Length(Buffer)); // raises an Exception if it couldn't read the number of bytes requested
S := TEncoding.Default.GetString(Buffer); // Works in Delphi 2009+
finally
Stream.Free;
end;
end;
here is a little tipp for sending and receiving text
first you must send the length of yout text too
Socket.SendText(IntToStr(Length(text)) + seperator + text);
then you can check at your server socket on receiving data streams, if your incoming text is complete
procedure TMyServer.OnClientRead(Sender: TObject; Socket: TCustomWinSocket);
begin
if (xRecLength = 0) then begin
if Length(Socket.ReceiveText) <= 0 then EXIT;
xRecLength:= StrToIntDef(GetFirstFromSplitted(Socket.ReceiveText, seperator), -1);
if xRecLength = -1 then EXIT;
end;
xActLength:= xActLength + Length(Socket.ReceiveText);
xRecPuffer:= xRecPuffer + Socket.ReceiveText;
isComplete:= xActLength = xRecLength;
if isComplete then begin
// complete text received
end;
end;
hope that helps you...
I'm not at home with Delphi, but a quick Google search turned up this page that indicates that ReceiveText does not accept any parameters, but instead returns a string of as much as it can read.
What you might need is might be ReceiveBuf instead.
I use Indy for TCP communication (D2009, Indy 10).
After evaluating a client request, I want to send the answer to the client. I therefore store the TIdContext, like this (pseudocode)
procedure ConnectionManager.OnIncomingRequest (Context : TIdContext);
begin
Task := TTask.Create;
Task.Context := Context;
ThreadPool.AddTask (Task);
end;
procedure ThreadPool.Execute (Task : TTask);
begin
// Perform some computation
Context.Connection.IOHandler.Write ('Response');
end;
But what if the client terminates the connection somewhere between the request and the answer being ready for sending? How can I check if the context is still valid? I tried
if Assigned (Context) and Assigned (Context.Connection) and Context.Connection.Connected then
Context.Connection.IOHandler.Write ('Response');
but it does not help. In some cases the program just hangs and if I pause execution I can see that the current line is the one with the if conditions.
What happens here? How can I avoid trying to send using dead connections?
Okay, I found a solution. Instead of storing the TIdContext I use the context list provided by TIdTcpServer:
procedure ThreadPool.Execute (Task : TTask);
var
ContextList : TList;
Context : TIdContext;
FoundContext : Boolean;
begin
// Perform some computation
FoundContext := False;
ContextList := FIdTCPServer.Contexts.LockList;
try
for I := 0 to ContextList.Count-1 do
begin
Context := TObject (ContextList [I]) as TIdContext;
if (Context.Connection.Socket.Binding.PeerIP = Task.ClientInfo.IP) and
(Context.Connection.Socket.Binding.PeerPort = Task.ClientInfo.Port) then
begin
FoundContext := True;
Break;
end;
end;
finally
FIdTCPServer.Contexts.UnlockList;
end;
if not FoundContext then
Exit;
// Context is a valid connection, send the answer
end;
That works for me.
If the client closes the connection, the client machine/network card dies or you have some other network problem between you and the client, you might not know about it until the next time you try to write to the connection.
You could use a heartbeat. Occasionally send a message to the client with a short timeout to see if the connection is still valid. This way, you'll know sooner if there has been an unexpected disconnect. You could wrap it in a "CheckConnection" function and call it before sending your response.
First Question:
Is the following routine a correct implementation of an Indy 9 IdTcpServer.OnExecute routine?
procedure TMyConnServer.ServerExecute(AContext: TIdPeerThread);
var
buffSize: integer;
str: string;
begin
AContext.Connection.ReadFromStack(True, 600, False);
buffSize := AContext.Connection.InputBuffer.Size;
if (buffSize > 0) then
{ Extract input buffer as string }
str := AContext.Connection.ReadString(buffSize);
{ Notify connection object of received data }
if (AContext.Data <> nil) then
begin
TConnectionHandler(AContext.Data).Read(str);
end;
end;
end;
Second (actually more important) Question:
Now there is occasionally an access violation (read from address 000000). Obviously in the line:
AContext.Connection.ReadFromStack(True, 600, False);
but checking if AContext / Connection / InputBuffer / IOHandler = nil BEFORE is false.
AFTER the call (and after the exception was raised) the IOHandler is nil.
We are using RAD Studio / Delphi 2007.
The only way the IOHandler can become nil like you describe is if another thread in your app called Disconnect() on the connection while your worker thread was still running.
Well, the simplest onExecute handler I have is like this. (excuse C++ instead of Delphi, but you'll get the idea.
void __fastcall MyPanel::DoTCPExecute(TIdPeerThread * AThread)
{
AnsiString text =AThread->Connection->ReadLn();
// now do something with text
}
The only obvious issue I can see is that you're trying to use the "timing" of data to determine when you have a complete string. This is a real no-no with TCP. You might just have the first byte of a string, or you might have several strings all sent at once. With TCP there's no guarantee that each "send" ends up as single "receive" at the other end.
You need to "delimit" your string in some other way. Readln uses the newline character as a terminator - another approach is to prefix each chunk of data with a length field. You read the length, then read the remaining data.
The code works like that, but I don't think it's a clean option:
if (AContext.Connection.Connected) then
begin
try
AContext.Connection.ReadFromStack(false, 1, false);
except on E: EAccessViolation do
// ignore
end;
end;
buffSize := AContext.Connection.InputBuffer.Size;