1
votes

I know that what I describe below is not technically multicasting, but I lack a better description of what I am trying to do.

I currently have a TIdTCPServer with an OnExecute that reads the incoming message, generates the response messages into a thread safe queue and then calls a function to step through the queue and send the messages back to the client. This part of the code is working and allows clients to request sets of data.

When a client changes an object on the server, I want all of the clients notified of this change. The code currently adds the notification message to each queue for each connection. My problem is that OnExecute is not looped, so the call to send messages on the queue is not called until the Server receives a message from the Client.

Is there a way to get OnExecute to loop? Or a way to trigger an OnExecute for a connection I know I have queued messages for?

My procedure TWinSocketSession object has a reference to the connection and includes the code for the outgoing message Queue. It also has a procedure called SendMessages that steps through the queue and calls Connection.Write. Below is my OnExecute procedure.

procedure TWinServerSession.IdTCPServer1Execute(AThread: TIdPeerThread);
  Var
    i:           Integer;
    strMessage:  String;
  begin
    //find the Socket Session for connection
    for i := 0 to m_usClientCount do
      begin
        if m_mWinSocketSession[i]<>Nil then
          if ( m_mWinSocketSession[i].Connection = Athread.Connection ) then break;
      end;

    //read the message
    strMessage := m_mWinSocketSession[i].Connection.ReadLn(#0,25,-1);

    //parse the message and populate the Queue with outgoing messages
    m_mWinSocketSession[i].ParseInString(strMessage);

    //send all of the outgoing messages
    m_mWinSocketSession[i].SendMessages;
  end;
1

1 Answers

3
votes

Yes, the TIdTCPServer.OnExecute event IS a looped event. It is NOT triggered when an inbound message arrives. It is triggered in a continuous loop for the lifetime of the connection, regardless of what the connection is actually doing. It is the event handler's resposibility to implement blocking behavior as needed. Your code is calling ReadLn() with a 25ms timeout specified, so it will not block the event handler from exiting in a timely manner so it can re-enter itself immediately. If your event handler is being blocked from exiting in a timely manner, then you have a deadlock issue elsewhere in your code.

I would suggest the following changes, however:

procedure TWinServerSession.IdTCPServer1Connect(AThread: TIdPeerThread); 
var
  i: Integer;
begin 
  for i := 0 to m_usClientCount do  
  begin  
    if (m_mWinSocketSession[i] = nil) then
    begin
      m_mWinSocketSession[i] := TWinSocketSession.Create;
      m_mWinSocketSession[i].Connection := AThread.Connection;

      // for easier access in the other events...
      AThread.Data := m_mWinSocketSession[i];  

      Exit;
    end;
  end;

  // cannot start a new session
  AThread.Connection.Disconnect;
end; 

procedure TWinServerSession.IdTCPServer1Disconnect(AThread: TIdPeerThread); 
var
  session: TWinSocketSession;
  i: Integer;
begin 
  session := TWinSocketSession(AThread.Data);
  AThread.Data := nil;

  if session <> nil then
  begin      
    for i := 0 to m_usClientCount do  
    begin  
      if m_mWinSocketSession[i] = session then
      begin
        m_mWinSocketSession[i] := nil;
        Break;
      end;
    end;
    session.Free;
  end;
end; 

procedure TWinServerSession.IdTCPServer1Execute(AThread: TIdPeerThread); 
var 
  session: TWinSocketSession;
  strMessage:  String; 
begin 
  session := TWinSocketSession(AThread.Data);

  //read a message  
  strMessage := AThread.Connection.ReadLn(#0, 25, -1);  
  if not AThread.Connection.ReadLnTimedOut then
  begin
    //parse the message and populate the Queue with outgoing messages  
    session.ParseInString(strMessage);  
  end;

  //send all of the outgoing messages  
  session.SendMessages;  
end; 

This would work even better if you can eliminate the m_mWinSocketSession list completely and just assign a new queue directly to the Connection in the OnConnect event. The OnDisconnect event can free the queue when the client disconnects.

procedure TWinServerSession.IdTCPServer1Connect(AThread: TIdPeerThread); 
begin
  AThread.Data := TWinSocketSession.Create;
  TWinSocketSession(AThread.Data).Connection := AThread.Connection;
end;

procedure TWinServerSession.IdTCPServer1Disconnect(AThread: TIdPeerThread); 
begin
  session := TWinSocketSession(AThread.Data);
  AThread.Data := nil;
  session.Free;
end;