Everyone. I am developing Server/Clients program on based Indy TCP controls. Now, I faced some uncertain problems.. It's just about the connection broken abnormally... Let us suppose network status is broken unexpectedly, or Server application is terminated abnormally, so client can't communicate with server anymore... then clients will occure exceptions like as "connection reset by peer" or "connection refused..." In these cases, how to treate these exceptions smartly ? I want that client will connect again automatically and communicate normally after recovering of server status... If you have a good idea, please share it.....
Below is my code. I have used two timer controls. One is to send alive and confirm networks status(5000ms). If network status is ok, then this timer is dead, and another timer is enable. Second timer is to send info to server(1000ms)
If in second timer, exception occures then it's disabled, and the 1st timer is enabled again.
when "connection refused" is occured, then try except
block can catch it.
But if "Connection reset by peer" is occured, then try except
block can't catch it.
{sendbuffer funtion}
function SendBuffer(AClient: TIdTCPClient; ABuffer: TBytes): Boolean; overload;
begin
try
Result := True;
try
AClient.IOHandler.Write(LongInt(Length(ABuffer)));
AClient.IOHandler.WriteBufferOpen;
AClient.IOHandler.Write(ABuffer, Length(ABuffer));
AClient.IOHandler.WriteBufferFlush;
finally
AClient.IOHandler.WriteBufferClose;
end;
except
Result := False;
end;
end;
{alive timer}
procedure TClientForm.Timer_StrAliveTimer(Sender: TObject);
var
infoStr : string;
begin
if not IdTCPClient_StrSend.Connected then
begin
try
if IdTCPClient_StrSend.IOHandler <> nil then
begin
IdTCPClient_StrSend.IOHandler.InputBuffer.Clear;
IdTCPClient_StrSend.IOHandler.WriteBufferClear;
end;
IdTCPClient_StrSend.Connect;
except on E: Exception do
begin
SAOutMsg := 'connect fail : ' + E.ToString ;
Exit;
end;
end;
SAOutMsg := 'connect success : ';
if IdTCPClient_StrSend.Connected then
begin
IdTCPClient_StrSend.IOHandler.CheckForDisconnect(True, True);
IdTCPClient_StrSend.IOHandler.CheckForDataOnSource(100);
infoStr := MY_MAC_ADDRESS+'|'+MY_COMPUTER_NAME;
try
IdTCPClient_StrSend.IOHandler.WriteLn(infoStr, nil);
except on E: Exception do
begin
SAOutMsg := 'login info send fail : ';
Exit;
end;
end;
SAOutMsg := 'login info send success : ';
try
if IdTCPClient_StrSend.IOHandler.ReadLn() = 'OK' then
begin
Timer_StrAlive.Enabled := False;
Timer_Str.Enabled := True;
end;
except on E: Exception do
begin
SAOutMsg := 'login fail : ' + E.ToString ;
Exit;
end;
end;
SAOutMsg := 'login ok : ' ;
end;
end;
end;
{send part}
procedure TClientForm.Timer_StrTimer(Sender: TObject);
var
LBuffer: TBytes;
LClientRecord: TClientRecord;
begin
// IdTCPClient_StrSend.CheckForGracefulDisconnect(False);
if not IdTCPClient_StrSend.Connected then
begin
Timer_Str.Enabled := False;
Timer_StrAlive.Enabled := True;
Exit;
end;
if IdTCPClient_StrSend.Connected then
begin
LClientRecord.data1 := str1;
LClientRecord.data2:= Trim(str2);
LClientRecord.data3 := Trim(str3);
LBuffer := MyRecordToByteArray(LClientRecord);
IdTCPClient_StrSend.IOHandler.CheckForDisconnect(True, True);
IdTCPClient_StrSend.IOHandler.CheckForDataOnSource(100);
if (SendBuffer(IdTCPClient_StrSend, LBuffer) = False) then
begin
SOutMsg := 'info send fail' ;
IdTCPClient_StrSend.Disconnect(False);
if IdTCPClient_StrSend.IOHandler <> nil then
IdTCPClient_StrSend.IOHandler.InputBuffer.Clear;
Timer_Str.Enabled := False;
Timer_StrAlive.Enabled := True;
Exit;
end
Exceptions related to lost connections, like "connection reset by peer", do not occur until you perform a socket read/write operation after the OS has detected the lost connection (usually after an internal timeout period has elapsed) and invalidated the socket connection. Most Indy client components do not perform such operations automatically, you have to tell them to do so (TIdTelnet
and TIdCmdTCPClient
being notable exceptions to that rule, as they run internal reading threads). So simply wrap your socket operations in a try/except
block, and if you catch an Indy socket exception (EIdSocketError
or descendant, for instance) then you can call Disconnect()
and Connect()
to re-connect.
"connection refused" can only occur when calling Connect()
. It usually means the server was reached but could not accept the connection at that time, either because there is no listening socket on the requested IP/port, or there are too many pending connections in the listening socket's backlog (it could also mean a firewall blocked the connection). Again, simply wrap Connect()
in a try/except
to handle the error so you can call Connect()
again. You should wait a small timeout period before doing so, to allow the server some time to possibly clear up whatever condition made it refuse the connection in the first place (assuming a firewall is not the issue).
Indy relies heavily on exceptions for error reporting, and to a lesser degree for status reporting. So you usually need to make use of try/except
handlers when using Indy.
Update: I see a few problems in your code. SendBuffer()
is not implementing writing buffering correctly. And most of the calls to Connected()
, and all of the calls to CheckForDisconnect()
and CheckForDataOnSource()
, are overkill and should be removed completely. The only calls that make sense to keep are the first call to Connected()
in each timer.
Try something more like this:
{sendbuffer function}
function SendBuffer(AClient: TIdTCPClient; const ABuffer: TBytes): Boolean; overload;
begin
Result := False;
try
AClient.IOHandler.WriteBufferOpen;
try
AClient.IOHandler.Write(LongInt(Length(ABuffer)));
AClient.IOHandler.Write(ABuffer);
AClient.IOHandler.WriteBufferClose;
except
AClient.IOHandler.WriteBufferCancel;
raise;
end;
Result := True;
except
end;
end;
{alive timer}
procedure TClientForm.Timer_StrAliveTimer(Sender: TObject);
var
infoStr : string;
begin
if IdTCPClient_StrSend.Connected then Exit;
try
IdTCPClient_StrSend.Connect;
except
on E: Exception do
begin
SAOutMsg := 'connect fail : ' + E.ToString;
Exit;
end;
end;
try
SAOutMsg := 'connect success : ';
infoStr := MY_MAC_ADDRESS+'|'+MY_COMPUTER_NAME;
try
IdTCPClient_StrSend.IOHandler.WriteLn(infoStr);
except
on E: Exception do
begin
E.Message := 'login info send fail : ' + E.Message;
raise;
end;
end;
SAOutMsg := 'login info send success : ';
try
if IdTCPClient_StrSend.IOHandler.ReadLn() <> 'OK' then
raise Exception.Create('not OK');
except
on E: Exception do
begin
E.Message := 'login fail : ' + E.Message;
raise;
end;
end;
SAOutMsg := 'login ok : ' ;
except
on E: Exception do
begin
SAOutMsg := E.ToString;
IdTCPClient_StrSend.Disconnect(False);
if IdTCPClient_StrSend.IOHandler <> nil then
IdTCPClient_StrSend.IOHandler.InputBuffer.Clear;
Exit;
end;
end;
Timer_StrAlive.Enabled := False;
Timer_Str.Enabled := True;
end;
{send part}
procedure TClientForm.Timer_StrTimer(Sender: TObject);
var
LBuffer: TBytes;
LClientRecord: TClientRecord;
begin
if not IdTCPClient_StrSend.Connected then
begin
Timer_Str.Enabled := False;
Timer_StrAlive.Enabled := True;
Exit;
end;
LClientRecord.data1 := str1;
LClientRecord.data2:= Trim(str2);
LClientRecord.data3 := Trim(str3);
LBuffer := MyRecordToByteArray(LClientRecord);
if not SendBuffer(IdTCPClient_StrSend, LBuffer) then
begin
SOutMsg := 'info send fail' ;
IdTCPClient_StrSend.Disconnect(False);
if IdTCPClient_StrSend.IOHandler <> nil then
IdTCPClient_StrSend.IOHandler.InputBuffer.Clear;
Timer_Str.Enabled := False;
Timer_StrAlive.Enabled := True;
Exit;
end
...
end;
Now, with that said, using Indy inside of timers in the main UI thread is not the best, or even the safest, way to use Indy. This kind of logic would work much better in a worker thread instead, eg:
type
TStrSendThread = class(TThread)
private
FClient: TIdTCPClient;
...
protected
procedure Execute; override;
procedure DoTerminate; override;
public
constructor Create(AClient: TIdTCPClient); reintroduce;
end;
constructor TStrSendThread.Create(AClient: TIdTCPClient);
begin
inherited Create(False);
FClient := AClient;
end;
procedure TStrSendThread.Execute;
var
LBuffer: TIdBytes;
...
begin
while not Terminated do
begin
Sleep(ConnectInterval);
if Terminated then Exit;
try
FClient.Connect;
try
// report status to main thread as needed...
FClient.IOHandler.WriteLn(MY_MAC_ADDRESS+'|'+MY_COMPUTER_NAME);
if FClient.IOHandler.ReadLn() <> 'OK' then
raise Exception.Create('error message');
// report status to main thread as needed...
while not Terminated do
begin
Sleep(SendInterval);
if Terminated then Exit;
...
if not SendBuffer(FClient, LBuffer) then
raise Exception.Create('error message');
end;
finally
FClient.FDisconnect(False);
if FClient.IOHandler <> nil then
FClient.IOHandler.InputBuffer.Clear;
end;
except
on E: Exception do
begin
// report error to main thread as needed...
end;
end;
end;
end;
procedure TStrSendThread.DoTerminate;
begin
// report status to main thread as needed...
inherited;
end;
private
Thread: TStrSendThread;
...
// Timer_StrAliveTimer.Active := True;
if Thread = nil then
Thread := TStrSendThread.Create(IdTCPClient_StrSend);
...
// Timer_StrAliveTimer.Active := False;
if Thread <> nil then
begin
Thread.Terminate;
Thread.WaitFor;
FreeAndNil(Thread);
end;