-1

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
fangrenxing
  • 45
  • 11

1 Answers1

2

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;
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • Hi, Mr Remy. The treatment is almost good. But when I make client's network disable, then client program continues to occure exception "connection reset by peer". How should I do? In case of I stop server program, then there isn't any problem..... – fangrenxing Dec 03 '15 at 07:29
  • 1
    Then do are doing something wrong in your code. You are continuing to use the old connection, which is gone. You have to close the client and reconnect. If you are still having trouble doing that, please edit your question to show your actual code. – Remy Lebeau Dec 03 '15 at 18:33
  • Mr Remy. I posted my code on here. please check it and notify my mistakes – fangrenxing Dec 04 '15 at 05:41
  • 1
    Oh, my god. I tried by your code. But "connection reset by peer" alert message is occuring continuosly....isn't not hired. It's not catched by `try..except` block – fangrenxing Dec 04 '15 at 10:48
  • The ONLY way you could be seeing a popup message is if either 1) you are running the code inside the debugger and seeing the *debugger* catch the exception before the *application* does, or 2) the exception is being raised in code that is not inside a `try/except` block. Please run your code inside the debugger (if you are not already) and let it show you exactly where the exception is actually occurring. – Remy Lebeau Dec 04 '15 at 16:44
  • It's raised in the first line "`if not IdTCPClient_StrSend.Connected then`" of `Timer_strTimer event`. So I applied `try/except` block include this line..But every time the error message isn't catched and escape `try/except` block. Really I want to know is just the appliying position of try/except block on my code for catching "connection reset by peer"... – fangrenxing Dec 04 '15 at 18:07
  • I guarantee you that a "connection reset by peer" exception from Indy *CAN BE* and *IS* caught by a `try/except` block. It is just a regular Indy `EIdSocketError` exception, which is derived from Delphi's base `Exception` class. `Connected()` is not supposed to raise any exception, though. Which makes me think maybe you are using an old buggy version of Indy (you did not say which versions of Delphi and Indy you are actually using). That error was addressed several years ago. If it is still happening in the latest version, I will have to look into it again. – Remy Lebeau Dec 04 '15 at 21:10
  • Oh, I have came back to delphi 2010 vs Indy 10.55 from Delphi xe8 vs indy 10.6.x, because of some reasons... I wanna use delphi 2010 rather than delphi xe8 version. But will try to upgrade Indy version to latest as you said. Then how could I upgrade indy easily? By my searching result, It's difficult to perform....Oh, OK, want not disturb you for such version up problem. will try it by myself, and then try your code again.. thanks good teacher. – fangrenxing Dec 05 '15 at 00:23
  • By the way, from which version, such problem resolved??? And when I visited Indy site, there may be not latest indy version support. – fangrenxing Dec 05 '15 at 00:31
  • [Indy 10 Install Instructions](http://www.indyproject.org/Sockets/Docs/Indy10Installation.aspx) and [download links to latest SVN version](http://www.indyproject.org/Sockets/Download/DevSnapshot.aspx). If you run into any problems, please post a new question, or ask on [Indy's forum server](http://forums2.atozed.com). – Remy Lebeau Dec 05 '15 at 01:20
  • I have upgrade my indy version from 10.5.5 to 10.6.2 on DELPHI 2010. The package file I used for upgrading is "**https://indy.fulgan.com/ZIP/Indy10_5317.zip**". The svn repository files can't be installed on my delphi 2010 environment. So I selected Zip format. After upgrading it, there are also trobles... 1st, unidac controls are disappered from component pallete and can't be reinstalled. And finally the "Connection reset by peer"message still continues occuring.... In other words there is nothing any changes after upgrading of Indy.... Hoooooooooooooooooooow should I do in this situation? – fangrenxing Dec 05 '15 at 06:22
  • The UniDAC issue is mentioned in Indy's Install Instructions: "*In D/CB/RAD 2009-XE, Embarcadero's DataSnap framework is compiled against the Indy 10 packages that ship with the IDE. **Installing a new version of Indy will render DataSnap unusable**, as it will not be able to load the Indy packages anymore, and DataSnap cannot be recompiled by end users. If you need to use DataSnap, then you will need to maintain the original Indy 10 packages for use in DataSnap projects. You can use a separate installation of Indy 10 for non-DataSnap projects.*" – Remy Lebeau Dec 05 '15 at 08:02
  • In any case, I cannot reproduce your issue with the code you have provided. When the connection is lost, `Connected()` inside of `Timer_StrTimer` raises a "connection reset" exception, and a `try/except` block is able to catch that just fine. Calling `Disconnect()` inside that `try/except` then allows `Timer_StrAlive` to reconnect as expected. – Remy Lebeau Dec 05 '15 at 08:14