Tcpserver senza evento OnExecute
Domanda
Voglio fare un tcpserver e invio / ricezione messaggi ai clienti se necessario , non evento OnExecute del tcpserver.
Invia / Ricevi messaggio non è un problema; Mi piace che:
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;
Nota 1:. Se io non uso OnExecute, il rilancio del programma un'eccezione quando un client si connette
Nota 2: Se io uso OnExecute senza fare nulla, l'utilizzo della CPU va al% 100 |
Nota 3:. Non ho la possibilità di cambiare i client TCP
Che cosa devo fare?
Soluzione
Usa OnExecute e se non hai niente da fare, Sleep () per un periodo di tempo, diciamo 10 millisecondi. Ogni connessione ha il proprio gestore OnExecute quindi questo effetto solo ogni singola connessione.
Altri suggerimenti
TIdTCPServer
richiede un gestore di eventi OnExecute
assegnato per impostazione predefinita. Per ovviare a questo, si dovrà derivare una nuova classe da TIdTCPServer
e ignorare il suo metodo CheckOkToBeActive()
virtuale, e dovrebbe anche ignorare l'DoExecute()
virtuale chiamata Sleep()
. In caso contrario, basta assegnare un gestore di eventi e lo hanno chiamata Sleep()
.
Non si tratta di un uso efficace delle TIdTCPServer
, però. Un design migliore è quello di non scrivere i dati in uscita verso i clienti all'interno del vostro metodo di SendMessage()
direttamente. Non solo è che soggetto a errori (che non stanno recuperando eccezioni WriteBuffer()
) e blocca SendMessage()
durante la scrittura, ma anche serializza le tue comunicazione (client 2 non possono ricevere dati fino a quando il client 1 fa prima). Un design molto più efficace è quello di dare ad ogni cliente la propria coda in uscita thread-safe, e quindi avere SendMessage()
inserire i dati nella coda di ogni cliente, se necessario. È quindi possibile utilizzare l'evento OnExecute
per controllare la coda di ogni cliente e fare la scrittura vera e propria. In questo modo, SendMessage()
non viene bloccato più, è meno soggetto a errori, e clienti può essere scritto in parallelo (come dovrebbero essere).
provare qualcosa di simile:
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;
Nel gestore OnExecute, è possibile utilizzare i metodi di comunicazione filo come tID e TMonitor aspettare fino a quando non ci sono dati per il cliente.
TMonitor è disponibile dal Delphi 2009 e fornisce i metodi (Wait, Pulse e PulseAll) per inviare / ricevere le notifiche con l'utilizzo della CPU mininmal.
Il set componente Indy
è progettato per emulare blocco operazione su una connessione di rete. Si suppone di incapsulare tutto il codice nel gestore di eventi OnExecute
. Che dovrebbe essere più facile , perché la maggior parte dei protocolli stanno bloccando qualsiasi modo (comando di invio, di attesa per la risposta, ecc).
a quanto pare non mi piace di modalità di funzionamento, vuoi qualcosa che funziona senza bloccare. Si dovrebbe considerare l'utilizzo di una suite di componenti che è progettato per il modo in cui si intende utilizzarlo: dare la ICS suite di una prova! ICS non fa uso di fili, tutto il lavoro è fatto in gestori di eventi.
Ho avuto simile situazione prendendo il 100% della CPU ed è risolto con l'aggiunta di IdThreadComponent e:
void __fastcall TForm3::IdThreadComponent1Run(TIdThreadComponent *Sender)
{
Sleep(10);
}
E 'giusto? Non sono sicuro.