Question

Je veux faire un tcpserver et envoyer / recevoir des messages à des clients au besoin , non événement OnExecute du tcpserver.

Envoyer / recevoir un message n'est pas un problème; Je fais comme ça:

procedure TFormMain.SendMessage(IP, Msg: string);
var
  I: Integer;
begin
  with TCPServer.Contexts.LockList do
  try
    for I := 0 to Count-1 do
      if TIdContext(Items[I]).Connection.Socket.Binding.PeerIP = IP then
      begin
        TIdContext(Items[I]).Connection.IOHandler.WriteBuffer(Msg[1], Length(Msg));
        //  and/or Read 
        Break;
      end;
  finally
    TCPServer.Contexts.UnlockList;
  end;
end;

Note 1:. Si je ne me OnExecute, la relance du programme une exception d'lorsqu'un client se connecte
Note 2: Si je OnExecute sans faire quoi que ce soit, l'utilisation du processeur passe à 100%
Note 3:. Je n'ai pas une chance de changer les clients TCP

Alors, que dois-je faire?

Était-ce utile?

La solution

Utilisez OnExecute et si vous avez rien à faire, Sleep () pendant une période de temps, disons 10 millisecondes. Chaque connexion a son propre gestionnaire de OnExecute donc cela n'affectera chaque connexion.

Autres conseils

TIdTCPServer nécessite un gestionnaire d'événements OnExecute attribué par défaut. Pour contourner cette difficulté, vous devez obtenir une nouvelle classe de TIdTCPServer et remplacer la méthode de CheckOkToBeActive() virtuelle, et devrait également remplacer la DoExecute() virtuelle à l'appel Sleep(). Sinon, attribuer un gestionnaire d'événements et l'ont appelé Sleep().

Ce n'est pas une utilisation efficace de TIdTCPServer, cependant. Une meilleure conception est de ne pas écrire vos données sortantes à des clients à l'intérieur de votre méthode SendMessage() directement. Non seulement est que sujette à l'erreur (vous n'êtes pas Attraper les exceptions de WriteBuffer()) et des blocs SendMessage() lors de l'écriture, mais aussi sérialise vos communications (client 2 ne peuvent pas recevoir des données jusqu'à ce que le client 1 ne premier). Une conception beaucoup plus efficace est de donner à chaque client sa propre file d'attente sortante thread-safe, et ensuite SendMessage() mettre les données dans la file d'attente de chaque client en fonction des besoins. Vous pouvez ensuite utiliser l'événement OnExecute pour vérifier la file d'attente de chaque client et faire l'écriture proprement dite. De cette façon, SendMessage() ne soit pas bloqué plus, moins, et les clients sujette aux erreurs peuvent être écrites en parallèle (comme ils devraient l'être).

Essayez quelque chose comme ceci:

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; 

Dans le gestionnaire de OnExecute, vous pouvez utiliser des méthodes de communication de fil comme TEvent et TMonitor attendre jusqu'à ce qu'il y a des données pour le client.

TMonitor est disponible depuis Delphi 2009 et fournit des méthodes (Wait, Pulse et PulseAll) pour envoyer / recevoir des notifications avec l'utilisation du processeur mininmal.

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.

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.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top