1

再起動/シャットダウンの目的で、2 つ以上の異なるマシンで実行される小さなクライアント サーバー アプリケーションを作成しました。私はクライアント サーバー アプリに比較的慣れていないので、ここで Delphi についてのアプローチを採用しました。簡単に言うと、サーバー アプリはポート 7676 で接続を待機し、クライアントをクライアント リストに追加してから何もしません (後でシャットダウンと再起動の手順を実装します)。ただし、パッシブであっても、クライアントが 2 つしか接続されていない場合、CPU の最大 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 のクライアントをサポートするには、別のアプローチを取る必要がありますか?

ありがとう、ボブ。

4

2 に答える 2

5

CPU 使用率が固定されている理由は、OnExecuteイベント ハンドラーが実際には何も実行していないためです。そのため、各接続スレッドは、CPU 時間を待機している他のスレッドに CPU タイムスライスを提供しないタイトなループを効果的に実行しています。そのイベント ハンドラーに生成操作が必要です。実際のコマンドを実装すると、その生成は自動的に処理されますが、それを実装するまでは、代わりに次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; 
于 2012-08-28T23:21:09.683 に答える
0

ではTCPServerExecute()、 を初期化していませんCommand

Bindingsで解放するべきではありませんStartServerExecute()。代わりに、次のようにしてみてください。

var
  sh: TidSocketHandle;
begin
  sh := TCPServer.Bindings.Add;
  sh.IP := DefaultServerIP;
  sh.Port := DefaultServerPort;

とはStartServerExecute()?

残念ながら、コードには問題が多すぎて、何が起こっているのかを推測するにはコードが不足しています。

于 2012-08-28T20:33:58.427 に答える