Tcpserver sans événement OnExecute
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?
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.