Indy:服务器和客户端之间的连接异常中断时的处理

时间:2015-12-02 08:25:26

标签: delphi indy10

的所有人。 我正在开发基于Indy TCP控件的服务器/客户端程序。 现在,我遇到了一些不确定的问题.. 这只是关于异常破坏的连接...... 让我们假设网络状态意外中断,或者服务器应用程序异常终止,因此客户端无法再与服务器通信... 然后客户端将出现像“通过对等方重置连接”或“连接被拒绝...”之类的异常 在这些情况下,如何巧妙地处理这些异常? 我希望客户端在恢复服务器状态后自动再次连接并正常通信... 如果你有个好主意,请分享一下......

以下是我的代码。我使用了两个计时器控件。一种是发送活动并确认网络状态(5000ms)。如果网络状态正常,则此计时器已停止,另一个计时器已启用。第二个计时器是向服务器发送信息(1000毫秒)

如果在第二个计时器中,则发生异常,然后它被禁用,并且第一个计时器再次启用。

当发生“连接被拒绝”时,try except块可以捕获它。 但是如果发生“通过对等方重置连接”,则try except阻止无法捕获它。

{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

1 个答案:

答案 0 :(得分:2)

在操作系统检测到丢失的连接(通常在内部超时时间已过去)之后执行套接字读/写操作之前,不会发生与连接丢失相关的异常,例如“通过对等方重置连接”,并且无效套接字连接。大多数Indy客户端组件不会自动执行此类操作,您必须告诉他们这样做(TIdTelnetTIdCmdTCPClient是该规则的明显例外,因为它们运行内部读取线程)。因此,只需将套接字操作包装在try/except块中,如果捕获Indy套接字异常(例如EIdSocketError或后代),则可以调用Disconnect()Connect()重新连接。

“拒绝连接”只能在调用Connect()时发生。它通常意味着服务器已到达但当时无法接受连接,因为请求的IP /端口上没有侦听套接字,或者侦听套接字的待办事项中有太多未决连接(它也可能意味着防火墙)阻止连接)。再次,只需将Connect()包裹在try/except中即可处理错误,以便再次调用Connect()。在这样做之前,您应该等待一个小的超时时间,以便让服务器有一段时间可能清除任何条件,使其首先拒绝连接(假设防火墙不是问题)。

Indy在很大程度上依赖于错误报告的异常,并且在较小程度上依赖于状态报告。因此,在使用Indy时,您通常需要使用try/except处理程序。

更新:我发现代码中存在一些问题。 SendBuffer()没有正确实现写缓冲。大多数Connected()的来电以及CheckForDisconnect()CheckForDataOnSource()的所有来电都是过度的,应该完全删除。唯一有意义的通话是每个计时器中第一次调用Connected()

尝试更像这样的事情:

{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;

现在,据说,在主UI线程中使用Indy定时器并不是使用Indy的最佳,甚至是最安全的方式。这种逻辑在工作线程中可以更好地工作,例如:

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;