JacobHavkrog
JacobHavkrog

Reputation: 61

Dead TCP connections

I use Indy10 and Delphi XE in a Windows service application with a TIdCmdTCPServer serving clients using a TIdTCPClient.

With this function

function ConnectionCount: Integer;
begin
  if IdCmdTCPServer.Contexts = nil then
    Exit(0);
  with IdCmdTCPServer.Contexts do
  try
    Result := LockList.Count
  finally
    UnlockList
  end;
end;

I get a count of connections from clients to the server. Sometimes this count doesn't go down to zero even when all clients are closed. I guess those connections are dead somehow. On a site with 5 clients I've seen a connection count of 35 after some weeks of operation.

Is there a way to detect that a connection is dead and release it?

I suspect a connection can become dead if the client freezes or gets killed, but I don't know if that's the case here.

EDIT: here is the code from one of my TIdCmdTCPServer handlers:

procedure TDSA_Service.ModeHandler(ASender: TIdCommand);
var
  R: TToolReply;
begin
  FBMutex.Enter;
  try
    try
      R := SystemServer.Mode(GetIntParam(0, 'SystemNo', ASender));
      try
        Reply(ASender, R)
      finally
        R.Free
      end
    except
      HandleException;
      Abort
    end
  finally
    FBMutex.Leave
  end
end;

HandleException is madExcept sending a bug report. Am I right that some indy exceptions shouldn't be caught here? Which exceptions?

Upvotes: 0

Views: 341

Answers (1)

Freddie Bell
Freddie Bell

Reputation: 2287

You can't really determine if a connection is "dead". What I do is decide if the connection has been around for longer than say, 24 hours, and delete it if it has. I haven't cared about the issue that Remy raised "why connections hang around". My users also don't mind if they come in the next morning only to find that their client is no longer responsive. I hope this code answers more questions than it raises.

type
  TClientData = class(TObject)
    ClientName: string;
    ClientHost: string;
    ID: TDateTime;
  end;

type
  TDateTimeO = Class(TObject)
public
  Value: TDateTime;
  constructor Create(NewValue: TDateTime);
end;

const
  MAXCONNECT = (24 * 60 * 60);

procedure TfrmMain.IdCmdTCPServer1Connect(AContext: TIdContext);
var
  myClient: TClientData;
begin
  AContext.Connection.IOHandler.DefStringEncoding := IdGlobal.IndyTextEncoding_8Bit;
  myClient := TClientData(AContext.Data);
  if myClient = nil then
  begin
     myClient := TClientData.Create;
     AContext.Data := myClient;
     myClient.ClientName := '<Unknown>';
     myClient.ClientHost := AContext.Binding.PeerIP;
     myClient.ID := Now;
  end;
  // this actually happens elsewhere in my server code, but I've pasted
  // it here for convenience
  ListBox1.Items.AddObject(
    myClient.ClientName + '=' +
    FormatDateTime('yyyy-mm-dd hh:nn:ss', myClient.ID),
    TDateTimeO.Create(myClient.ID));

end;

procedure TfrmMain.Timer1Timer(Sender: TObject);
var
  ThisMoment: TDateTime;
  i, j: Integer;
  List: TList;
  Name: String;
  TimeStamp: TDateTime;
  MyClient: TClientData;
begin

  ThisMoment := Now;
  List := IdCmdTCPServer1.Contexts.LockList;
  try
    for i := ListBox1.Count - 1 downto 0 do
    begin
      // now terminate the actual connection (if it can be found)
      Name := Trim(TokenStr('=', ListBox1.Items[i], 1));
      TimeStamp := TDateTimeO(ListBox1.Items.Objects[i]).Value;
      if (SecondsBetween(TimeStamp, ThisMoment) > MAXCONNECT)
      then
      begin
        for j := List.Count - 1 downto 0 do
        begin
          if Assigned(List[j]) then
          begin
            // is this the corresponding connection?
            myClient := TClientData(TIdContext(List[j]).Data);
            if (Name = myClient.ClientName) and
              (TDateTimeO(ListBox1.Items.Objects[i]).Value = myClient.ID) then
            begin
              TIdContext(List[j]).Connection.Disconnect;
              BREAK;
            end;
          end;
        end; // next j
        // sometimes the connection isn't in the locklist for some reason
        // delete the listbox entry anyway
        TDateTimeO(ListBox1.Items.Objects[i]).Free;
        ListBox1.Items.Delete(i); // and delete the list entry
      end;
    end; // next i
  finally
    IdCmdTCPServer1.Contexts.UnLockList;
  end;

end;

Upvotes: 2

Related Questions