Клиент-серверное приложение Indy 10 + Delphi пожирает весь процессор
Я написал небольшое клиент-серверное приложение, которое запускается на двух или более разных машинах для целей перезагрузки / выключения. Поскольку я относительно новичок в клиент-серверных приложениях, я выбрал подход About Delphi здесь. Короче говоря, мое серверное приложение ожидает подключения через порт 7676, добавляет клиента в список клиентов, а затем ничего не делает (позже будут выполнены процедуры выключения и перезапуска). Однако, даже если он пассивен, он потребляет до 90% ресурсов ЦП только с двумя подключенными клиентами. Вот код клиента, состоящий из TidTCPServer и TidAntiFreeze:
type
PClient = ^TClient;
TClient = record
PeerIP : string[15]; { Client IP address }
HostName : String[40]; { Hostname }
Connected, { Time of connect }
LastAction : TDateTime; { Time of last transaction }
AContext : Pointer; { Pointer to thread }
end;
[...]
procedure TForm1.StartServerExecute(Sender: TObject);
var
Bindings: TIdSocketHandles;
begin
//setup and start TCPServer
Bindings := TIdSocketHandles.Create(TCPServer);
try
with Bindings.Add do
begin
IP := DefaultServerIP;
Port := DefaultServerPort;
end;
try
TCPServer.Bindings:=Bindings;
TCPServer.Active:=True;
except on E:Exception do
ShowMessage(E.Message);
end;
finally
Bindings.Free;
end;
//setup TCPServer
//other startup settings
Clients := TThreadList.Create;
Clients.Duplicates := dupAccept;
RefreshListDisplay;
if TCPServer.Active then
begin
Protocol.Items.Add(TimeToStr(Time)+' Shutdown server running on ' + TCPServer.Bindings[0].IP + ':' + IntToStr(TCPServer.Bindings[0].Port));
end;
end;
procedure TForm1.TCPServerConnect(AContext: TIdContext);
var
NewClient: PClient;
begin
GetMem(NewClient, SizeOf(TClient));
NewClient.PeerIP := AContext.Connection.Socket.Binding.PeerIP;
NewClient.HostName := GStack.HostByAddress(NewClient.PeerIP);
NewClient.Connected := Now;
NewClient.LastAction := NewClient.Connected;
NewClient.AContext := AContext;
AContext.Data := TObject(NewClient);
try
Clients.LockList.Add(NewClient);
finally
Clients.UnlockList;
end;
Protocol.Items.Add(TimeToStr(Time)+' Connection from "' + NewClient.HostName + '" from ' + NewClient.PeerIP);
RefreshListDisplay;
end;
procedure TForm1.TCPServerDisconnect(AContext: TIdContext);
var
Client: PClient;
begin
Client := PClient(AContext.Data);
Protocol.Items.Add (TimeToStr(Time)+' Client "' + Client.HostName+'"' + ' disconnected.');
try
Clients.LockList.Remove(Client);
finally
Clients.UnlockList;
end;
FreeMem(Client);
AContext.Data := nil;
RefreshListDisplay;
end;
procedure TForm1.TCPServerExecute(AContext: TIdContext);
var
Client : PClient;
Command : string;
//PicturePathName : string;
ftmpStream : TFileStream;
begin
if not AContext.Connection.Connected then
begin
Client := PClient(AContext.Data);
Client.LastAction := Now;
//Command := AContext.Connection.ReadLn;
if Command = 'CheckMe' then
begin
{do whatever necessary in here}
end;
end;
end;
Компонент idTCPServer устанавливается следующим образом: ListenQueue:= 15, MaxConnections:= 0, TerminateWaitTime: 5000.
Я что-то здесь не так делаю? Должен ли я использовать другой подход для поддержки одновременно 30 - 40 клиентов?
Спасибо, Боб.
2 ответа
Причина, по которой ваш процессор uage привязан, состоит в том, что ваш OnExecute
Обработчик событий на самом деле ничего не делает, поэтому каждый поток соединения эффективно выполняет узкий цикл, который не дает временных интервалов ЦП другим потокам, ожидающим времени ЦП. В этом обработчике события должна быть операция уступки. Как только вы реализуете свои действительные команды, эта отдача будет обрабатываться ReadLn()
для вас, но пока вы не реализуете это, вы можете использовать вызов IndySleep()
вместо этого, например:
procedure TForm1.TCPServerExecute(AContext: TIdContext);
var
Client : PClient;
Command : string;
//PicturePathName : string;
ftmpStream : TFileStream;
begin
Client := PClient(AContext.Data);
Client.LastAction := Now;
//Command := AContext.Connection.ReadLn;
IndySleep(10);
//...
end;
Теперь, с учетом сказанного, есть некоторые другие проблемы в вашем коде, такие как неправильное использование TIdSocketHandles
проблемы безопасности потоков и т. д. Попробуйте вместо этого:
uses
..., IdContext, IdSync;
//...
type
PClient = ^TClient;
TClient = record
PeerIP : String; { Client IP address }
HostName : String; { Hostname }
Connected : TDateTime; { Time of connect }
LastAction : TDateTime; { Time of last transaction }
AContext : TIdContext; { Pointer to thread }
end;
//...
procedure TForm1.StartServerExecute(Sender: TObject);
begin
//setup and start TCPServer
TCPServer.Bindings.Clear;
with TCPServer.Bindings.Add do
begin
IP := DefaultServerIP;
Port := DefaultServerPort;
end;
TCPServer.Active := True;
//setup TCPServer
//other startup settings
Protocol.Items.Add(TimeToStr(Time) + ' Shutdown server running on ' + TCPServer.Bindings[0].IP + ':' + IntToStr(TCPServer.Bindings[0].Port));
RefreshListDisplay;
end;
procedue TForm1.RefreshListDisplay;
var
List: TList;
I: Integer;
Client: PClient;
begin
// clear display list as needed...
List := TCPServer.Contexts.LockList;
try
for I := 0 to List.Count-1 do
begin
Client := PClient(TIdContext(List[I]).Data);
if Client <> nil then
begin
// add Client to display list as needed..
end;
end;
finally
TCPServer.Contexts.UnlockList;
end;
end;
type
TProtocolNotify = class(TIdNotify)
protected
FStr: String;
procedure DoNotify; override;
public
class procedure Add(const AStr: String);
end;
procedure TProtocolNotify.DoNotify;
begin
Form1.Protocol.Items.Add(FStr);
end;
class procedure TProtocolNotify.Add(const AStr: String);
begin
with Create do
begin
FStr := AStr;
Notify;
end;
end;
type
TRefreshListNotify = class(TIdNotify)
protected
procedure DoNotify; override;
public
class procedure Refresh;
end;
procedure TRefreshListNotify.DoNotify;
begin
Form1.RefreshListDisplay;
end;
class procedure TRefreshListNotify.Refresh;
begin
Create.Notify;
end;
procedure TForm1.TCPServerConnect(AContext: TIdContext);
var
NewClient: PClient;
begin
GetMem(NewClient, SizeOf(TClient));
try
NewClient.PeerIP := AContext.Connection.Socket.Binding.PeerIP;
NewClient.HostName := GStack.HostByAddress(NewClient.PeerIP);
NewClient.Connected := Now;
NewClient.LastAction := NewClient.Connected;
NewClient.AContext := AContext;
AContext.Data := TObject(NewClient);
except
FreeMem(NewClient);
raise;
end;
TProtocolNotify.Add(TimeToStr(Time) + ' Connection from "' + NewClient.HostName + '" from ' + NewClient.PeerIP);
TRefreshListNotify.Refresh;
end;
procedure TForm1.TCPServerDisconnect(AContext: TIdContext);
var
Client: PClient;
begin
Client := PClient(AContext.Data);
TProtocolNotify.Add(TimeToStr(Time) + ' Client "' + Client.HostName+'"' + ' disconnected.');
FreeMem(Client);
AContext.Data := nil;
TRefreshListNotify.Refresh;
end;
procedure TForm1.TCPServerExecute(AContext: TIdContext);
var
Client : PClient;
Command : string;
//PicturePathName : string;
ftmpStream : TFileStream;
begin
Client := PClient(AContext.Data);
Client.LastAction := Now;
//Command := AContext.Connection.ReadLn;
IndySleep(10);
if Command = 'CheckMe' then
begin
{do whatever necessary in here}
end;
end;
В TCPServerExecute()
не инициализируешь Command
,
Вы не должны освобождать Bindings
в StartServerExecute()
, Вместо этого попробуйте что-то вроде этого:
var
sh: TidSocketHandle;
begin
sh := TCPServer.Bindings.Add;
sh.IP := DefaultServerIP;
sh.Port := DefaultServerPort;
Что такое StartServerExecute()
?
К сожалению, слишком много проблем с кодом, и слишком много кода не хватает, чтобы угадать, что происходит.