TCPserver without OnExecute event

后端 未结 5 1023
情歌与酒
情歌与酒 2021-01-06 04:20

I want to make a TCPserver and send/receive message to clients as needed, not OnExecute event of the TCPserver.

Send/receive message is not a probl

相关标签:
5条回答
  • 2021-01-06 04:28

    In the OnExecute handler, you can use thread communication methods like TEvent and TMonitor to wait until there is data for the client.

    TMonitor is available since Delphi 2009 and provides methods (Wait, Pulse and PulseAll) to send / receive notifications with mininmal CPU usage.

    0 讨论(0)
  • 2021-01-06 04:35

    The Indy component set is designed to emulate blocking operation on a network connection. You're supposed to encapsulate all your code in the OnExecute event handler. That's supposed to be easier, because most protocols are blocking any way (send command, wait for response, etc).

    You apparently don't like it's mode of operation, you'd like something that works without blocking. You should consider using a component suite that's designed for the way you intend to use it: give the ICS suite a try! ICS doesn't use threads, all the work is done in event handlers.

    0 讨论(0)
  • 2021-01-06 04:40

    TIdTCPServer requires an OnExecute event handler assigned by default. To get around that, you would have to derive a new class from TIdTCPServer and override its virtual CheckOkToBeActive() method, and should also override the virtual DoExecute() to call Sleep(). Otherwise, just assign an event handler and have it call Sleep().

    This is not an effective use of TIdTCPServer, though. A better design is to not write your outbound data to clients from inside of your SendMessage() method directly. Not only is that error-prone (you are not catching exceptions from WriteBuffer()) and blocks SendMessage() during writing, but it also serializes your communications (client 2 cannot receive data until client 1 does first). A much more effective design is to give each client its own thread-safe outbound queue, and then have SendMessage() put the data into each client's queue as needed. You can then use the OnExecute event to check each client's queue and do the actual writing. This way, SendMessage() does not get blocked anymore, is less error-prone, and clients can be written to in parallel (like they should be).

    Try something like this:

    uses
      ..., IdThreadSafe;
    
    type
      TMyContext = class(TIdServerContext)
      private
        FQueue: TIdThreadSafeStringList;
        FEvent: TEvent;
      public
        constructor Create(AConnection: TIdTCPConnection; AYarn: TIdYarn; AList: TThreadList = nil); override;
        destructor Destroy; override;
        procedure AddMsgToQueue(const Msg: String);
        function GetQueuedMsgs: TStrings;
      end;
    
    constructor TMyContext.Create(AConnection: TIdTCPConnection; AYarn: TIdYarn; AList: TThreadList = nil);
    begin
      inherited;
      FQueue := TIdThreadSafeStringList.Create;
      FEvent := TEvent.Create(nil, True, False, '');
    end;
    
    destructor TMyContext.Destroy;
    begin
      FQueue.Free;
      FEvent.Free;
      inherited;
    end;
    
    procedure TMyContext.AddMsgToQueue(const Msg: String);
    begin
      with FQueue.Lock do
      try
        Add(Msg);
        FEvent.SetEvent;
      finally
        FQueue.Unlock;
      end;
    end;
    
    function TMyContext.GetQueuedMsgs: TStrings;
    var
      List: TStringList;
    begin
      Result := nil;
      if FEvent.WaitFor(1000) <> wrSignaled then Exit;
      List := FQueue.Lock;
      try
        if List.Count > 0 then
        begin
          Result := TStringList.Create;
          try
            Result.Assign(List);
            List.Clear;
          except
            Result.Free;
            raise;
          end;
        end;
        FEvent.ResetEvent;
      finally
        FQueue.Unlock;
      end;
    end;
    
    procedure TFormMain.FormCreate(Sender: TObject);
    begin
      TCPServer.ContextClass := TMyContext;
    end; 
    
    procedure TFormMain.TCPServerExecute(AContext: TIdContext);
    var
      List: TStrings;
      I: Integer;
    begin
      List := TMyContext(AContext).GetQueuedMsgs;
      if List = nil then Exit;
      try
        for I := 0 to List.Count-1 do
          AContext.Connection.IOHandler.Write(List[I]);
      finally
        List.Free;
      end;
    end;
    
    procedure TFormMain.SendMessage(const IP, Msg: string); 
    var 
      I: Integer; 
    begin 
      with TCPServer.Contexts.LockList do 
      try 
        for I := 0 to Count-1 do 
        begin
          with TMyContext(Items[I]) do
          begin
            if Binding.PeerIP = IP then 
            begin 
              AddMsgToQueue(Msg); 
              Break; 
            end;
          end; 
        end;
      finally 
        TCPServer.Contexts.UnlockList; 
      end; 
    end; 
    
    0 讨论(0)
  • 2021-01-06 04:47

    Use OnExecute and if you have nothing to do, Sleep() for a period of time, say 10 milliseconds. Each connection has its own OnExecute handler so this will only affect each individual connection.

    0 讨论(0)
  • 2021-01-06 04:47

    I had similar situation taking 100% CPU and it solved by adding IdThreadComponent and:

    void __fastcall TForm3::IdThreadComponent1Run(TIdThreadComponent *Sender)
    {
        Sleep(10);
    }
    

    Is it right? I am not sure.

    0 讨论(0)
提交回复
热议问题