views:

402

answers:

2

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?

+1  A: 

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.

Bruce McGee
The problem is, that accessing the TIdContext and the connection causes the application to halt - a test connection message would make no difference.
Smasher
Does it freeze because it's waiting for a timeout?
Bruce McGee
Indy is blocking so probably that is why the app halts there. The best way to check if the client is still alive is to listen for client "status messages". Client should check in at regular intervals. If client misses to many such checks you can treat it as unavailable. Or you can use nonblocking library (ICS), so your application will not block on TCP connections.And design the application in a way that waiting for a connection does not mean the app is not responding. Use nonblocking sockets or threads.
Runner
I use threads for client communication, but unfortunately the complete application freezes, not only the communication thread.
Smasher
+2  A: 

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.

Smasher