11

私は60台のコンピューター/デバイス(40台のコンピューターと20台のオシロスコープがWindows CEベース)を備えた部屋を持っていますが、pingを使用して誰もが生きているか知りたいです。最初に、標準のping(ここでDelphi Indy Ping Error 10040を参照)を作成しました。これは現在は正常に機能していますが、ほとんどのコンピューターがオフラインになると時間がかかります。

だから私がやろうとしているのはマルチスレッドPingを書くことですが、私はそれにかなり苦労しています。私はインターネット上でほんの少しの例しか見たことがなく、誰も私のニーズに合っていなかったので、私はそれを自分で書こうとしています。

XE2とIndy10を使用していますが、フォームはメモとボタンのみで構成されています。

unit Main;

interface

uses
  Winapi.Windows, System.SysUtils, System.Classes, Vcl.Forms,
  IdIcmpClient, IdGlobal, Vcl.StdCtrls, Vcl.Controls;

type
  TMainForm = class(TForm)
    Memo1: TMemo;
    ButtonStartPing: TButton;
    procedure ButtonStartPingClick(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

type
  TMyPingThread = class(TThread)
  private
    fIndex : integer;
    fIdIcmpClient: TIdIcmpClient;
    procedure doOnPingReply;
  protected
    procedure Execute; override;
  public
    constructor Create(index: integer);
  end;

var
  MainForm: TMainForm;
  ThreadCOunt : integer;

implementation

{$R *.dfm}

constructor TMyPingThread.Create(index: integer);
begin
  inherited Create(false);

  fIndex := index;
  fIdIcmpClient := TIdIcmpClient.Create(nil);
  fIdIcmpClient.ReceiveTimeout := 200;
  fIdIcmpClient.PacketSize := 24;
  fIdIcmpClient.Protocol := 1;
  fIdIcmpClient.IPVersion := Id_IPv4;

  //first computer is at adresse 211
  fIdIcmpClient.Host := '128.178.26.'+inttostr(211+index-1);

  self.FreeOnTerminate := true;
end;

procedure TMyPingThread.doOnPingReply;
begin
  MainForm.Memo1.lines.add(inttostr(findex)+' '+fIdIcmpClient.ReplyStatus.Msg);
  dec(ThreadCount);

  if ThreadCount = 0 then
    MainForm.Memo1.lines.add('--- End ---');
end;

procedure TMyPingThread.Execute;
begin
  inherited;

  try
    fIdIcmpClient.Ping('',findex);
  except
  end;

  while not Terminated do
  begin
    if fIdIcmpClient.ReplyStatus.SequenceId = findex then Terminate;
  end;

  Synchronize(doOnPingReply);
  fIdIcmpClient.Free;
end;

procedure TMainForm.ButtonStartPingClick(Sender: TObject);
var
  i: integer;
  myPing : TMyPingThread;
begin
  Memo1.Lines.Clear;

  ThreadCount := 0;
  for i := 1 to 40 do
  begin
    inc(ThreadCount);
    myPing := TMyPingThread.Create(i);
    //sleep(10);
  end;
end;

end.

私の問題は、「sleep(10)」のコメントを外すと機能するように見え、それなしでは機能しないように見えることです。これは確かに、私が書いたスレッドのポイントが欠落していることを意味します。

言い換えると。Sleep(10)がコードに含まれている場合。ボタンをクリックして接続を確認するたびに、結果は正しいものでした。

sleep(10)がないと、ほとんどの場合動作しますが、結果が間違って、オフラインコンピューターではpingエコーが発生し、オンラインコンピューターではpingエコーが発生しないことがあります。これは、ping応答が正しいものに割り当てられていないためです。スレッド。

コメントやヘルプは大歓迎です。

-----編集/重要-----

この質問の一般的なフォローアップとして、@DarianMillerはここ https://code.google.com/p/delphi-stackoverflow/でGoogleCodeプロジェクトを開始しました。これは実用的な基盤です。私は彼の回答を「承認済みの回答」としてマークしますが、将来確実に拡張および更新されるため、ユーザーはこのオープンソースプロジェクト(すべてのクレジットは彼に帰属します)を参照する必要があります。

4

4 に答える 4

11

根本的な問題は、pingがコネクションレス型のトラフィックであるということです。複数のTIdIcmpClientオブジェクトが同時にネットワークにpingを実行している場合、1つのTIdIcmpClientインスタンスは、実際には別のインスタンスに属する応答を受信できTIdIcmpClientます。値をチェックすることにより、スレッドループでそれを説明しようとしていますが、内部で同じチェックをすでに実行しているSequenceIdことを考慮していません。TIdIcmpClient期待する応答を受信するまで、またはReceiveTimeout発生するまで、ネットワーク応答をループで読み取ります。予期しない応答を受信した場合は、その応答を破棄するだけです。したがって、あるインスタンスがTIdIcmpClient別のインスタンスが予期していた応答を破棄した場合TIdIcmpClient、その応答はコードによって処理されず、他のインスタンスは別のインスタンスをTIdIcmpClient受信する可能性がありますTIdIcmpClient代わりにの返信など。を追加することによりSleep()、pingが互いにオーバーラップする可能性を減らします(ただし、なくすことはできません)。

実行しようとしていることについては、TIdIcmpClientそのままでは複数のpingを並行して実行することはできません。申し訳ありません。それは単にそのために設計されていません。返信データを必要な方法で区別する方法はありません。一度に1つのスレッドのみが呼び出すことができるようTIdIcmpClient.Ping()に、スレッドをシリアル化する必要があります。

pingをシリアル化するオプションがない場合は、TIdIcmpClientのソースコードの一部を独自のコードにコピーしてみてください。41個のスレッドを実行します-40個のデバイススレッドと1個の応答スレッド。すべてのスレッドが共有する単一のソケットを作成します。各デバイススレッドに個別のping要求を準備させ、そのソケットを使用してネットワークに送信させます。次に、応答スレッドに同じソケットからの応答を継続的に読み取り、処理のために適切なデバイススレッドにルーティングして戻します。これはもう少し手間がかかりますが、探している複数のpingの並列処理が可能になります。

そのような問題をすべて解決したくない場合は、 FREEPingのように、同時に複数のマシンへのpingをすでにサポートしているサードパーティのアプリを使用することもできます。

于 2012-10-13T00:16:57.903 に答える
5

レミーは問題を説明しました...私はしばらくの間インディでこれをやりたかったので、ここに長いコメントをする代わりに、新しいGoogleCodeプロジェクトにまとめた解決策を投稿しました。これは最初の試みのようなものです。統合する変更がある場合はお知らせください: https ://code.google.com/p/delphi-vault/

このコードには、例のようにマルチスレッドクライアントにPingする方法と、単純なコールバックプロシージャを使用する方法の2つがあります。Indy10以降のバージョンのDelphi用に作成されています。

コードは、SynchronizedResponseメソッドを定義するTThreadedPingの子孫を使用することになります。

  TMyPingThread = class(TThreadedPing)
  protected
    procedure SynchronizedResponse(const ReplyStatus:TReplyStatus); override;
  end;

そして、いくつかのクライアントスレッドを起動するために、コードは次のようになります。

procedure TfrmThreadedPingSample.butStartPingClick(Sender: TObject);
begin
  TMyPingThread.Create('www.google.com');
  TMyPingThread.Create('127.0.0.1');
  TMyPingThread.Create('www.shouldnotresolvetoanythingatall.com');
  TMyPingThread.Create('127.0.0.1');
  TMyPingThread.Create('www.microsoft.com');
  TMyPingThread.Create('127.0.0.1');
end;

スレッド化された応答は、同期されたメソッドで呼び出されます。

procedure TMyPingThread.SynchronizedResponse(const ReplyStatus:TReplyStatus);
begin
  frmThreadedPingSample.Memo1.Lines.Add(TPingClient.FormatStandardResponse(ReplyStatus));
end;
于 2012-10-13T05:46:30.477 に答える
1

私はあなたのコードを試していなかったので、それはすべて架空のものですが、あなたはスレッドを台無しにしてクラシックになったと思いますrace condition。使用するためのアドバイスを言い換えます。AsyncCallsまたはOmniThreadLibrary、これらははるかに単純で、「自分の足を撃つ」試みをほとんど節約できます。

  1. スレッドは、メインスレッドの負荷を最小限に抑えるように作成されています。スレッドコンストラクターは、パラメーターを記憶する最小限の作業を実行する必要があります。個人的には、idICMPの作成を.Executeメソッドに移動しました。何らかの理由で、ウィンドウやメッセージキュー、シグナルなどの内部同期オブジェクトを作成したい場合は、新しい生成されたスレッドで既に発生させたいと思います。

  2. 「継承」の意味はありません。.Executeで。削除したほうがいいです。

  3. すべての例外をサイレンシングするのは悪いスタイルです。おそらくエラーがありますが、それを知る方法はありません。それらをメインスレッドに伝播して表示する必要があります。OTLとACはその点で役立ちますが、tThreadの場合は手動で行う必要があります。.Syncを呼び出さずにAsyncCalls関数でスローされた例外を処理する方法は?

  4. 例外ロジックに欠陥があります。例外がスローされた場合(成功したPingが設定されていない場合)、ループを作成する意味はありません。それでは、なぜ応答を待つのでしょうか。ループは、pingを発行するのと同じtry-exceptフレーム内に入る必要があります。

  5. あなたdoOnPingReplyはAFTERを実行しますが、の内部fIdIcmpClient.Freeにアクセスします。fIdIcmpClient変更してみました。FreeAndNilの場合は無料ですか?これは、デッドポインターを解放した後に使用するという典型的な間違いです。正しいアプローチは次のとおりです
    。5.1。doOnPingReply
    5.2でオブジェクトを解放します。または、両方を呼び出す前に、関連するすべてのデータdoOnPingReplyをTThreadのプライベートメンバー変数にコピーします(そして、これらの変数のみを使用します)。内部またはでのみ実行します。結局のところ、コンストラクターでオブジェクトを作成することを選択した場合は、一致する言語のコンストラクターでオブジェクトを解放する必要があります。SynchronizeidICMP.FreedoOnPingReplyfIdIcmpClient.FreeTMyThread.BeforeDestructionTMyThread.Destroy

  6. スレッドオブジェクトへの参照を保持しないため、そのWhile not Terminatedループは冗長に見えます。通常の無限ループを作成して、ブレークを呼び出します。

  7. 前述のループはCPUを大量に消費し、スピンループのようなものです。他のスレッドが作業を行う機会を増やすために、Sleep(0);またはループ内に電話してください。Yield();ここではOSスケジューラを使用しないでください。速度が重要なパスにいるわけではないので、spinlockここで作成する理由はありません。


全体的に、私は考えます:

  • あなたにとって重大なバグとしての4と5
  • 潜在的な落とし穴としての1と3は、影響を与える場合とそうでない場合があります。危険なことをして、それがうまくいくかどうかを調査するよりも、「安全にプレイする」ほうがよいでしょう。
  • 2と7-悪いスタイル、言語に関して2、プラットフォームに関して7
  • 6アプリを拡張する計画があるか、YAGNIの原則を破ったかどうかはわかりません。
  • OTLやAsyncCallsの代わりに複雑なTThreadに固執する-戦略的エラー。滑走路にルークを置いてはいけません。簡単な道具を使ってください。

おかしなことに、これは明らかになる可能性のあるバグの例ですが、FreeAndNilFreeAndNil嫌いな人はバグを「隠す」と主張しています。

于 2012-10-12T12:41:28.910 に答える
0
// This is my communication unit witch works well, no need to know its work but your
// ask   is in the TPingThread class.

UNIT UComm;

INTERFACE

USES
  Windows, Messages, SysUtils, Classes, Graphics, Controls, ExtCtrls, Forms, Dialogs,
  StdCtrls,IdIcmpClient, ComCtrls, DB, abcwav, SyncObjs, IdStack, IdException, 
  IdTCPServer, IdBaseComponent, IdComponent, IdTCPConnection, IdTCPClient, IdContext,
  UDM, UCommon;

TYPE
  TNetworkState = (nsNone, nsLAN, nsNoLAN, nsNet, nsNoNet);
  TDialerStatus = (dsNone, dsConnected, dsDisconnected, dsNotSync);

  { TBaseThread }

  TBaseThread = Class(TThread)
  Private
    FEvent : THandle;
    FEventOwned : Boolean;
    Procedure ThreadTerminate(Sender: TObject); Virtual;
  Public
    Constructor Create(AEventName: String);
    Property EventOwned: Boolean Read FEventOwned;
  End;

  .
  .
  .

  { TPingThread }

  TPingThread = Class(TBaseThread)
  Private
    FReply : Boolean;
    FTimeOut : Integer;
    FcmpClient : TIdIcmpClient;
    Procedure ReplyEvent(Sender: TComponent; Const AReplyStatus: TReplyStatus);
  Protected
    Procedure Execute; Override;
    Procedure ThreadTerminate(Sender: TObject); Override;
  Public
    Constructor Create(AHostIP, AEventName: String; ATimeOut: Integer);
    Property Reply: Boolean Read FReply;
  End;

  .
  .
  .


{ =============================================================================== }

IMPLEMENTATION

{$R *.dfm}

USES
  TypInfo, WinSock, IdGlobal, UCounter, UGlobalInstance, URemoteDesktop;
  {IdGlobal: For RawToBytes function 10/07/2013 04:18 }

{ TBaseThread }

//---------------------------------------------------------
Constructor TBaseThread.Create(AEventName: String);
Begin
  SetLastError(NO_ERROR);
  FEvent := CreateEvent(Nil, False, False, PChar(AEventName));
  If GetLastError = ERROR_ALREADY_EXISTS
    Then Begin
           CloseHandle(FEvent);
           FEventOwned := False;
         End
    Else If FEvent <> 0 Then
           Begin
             FEventOwned := True;
             Inherited Create(True);
             FreeOnTerminate := True;
             OnTerminate := ThreadTerminate;
           End;
End;

//---------------------------------------------------------
Procedure TBaseThread.ThreadTerminate(Sender: TObject);
Begin
  CloseHandle(FEvent);
End;

{ TLANThread }
 .
 .
 .

{ TPingThread }

//---------------------------------------------------------
Constructor TPingThread.Create(AHostIP: String; AEventName: String; ATimeOut: Integer);
Begin
  Inherited Create(AEventName);
  If Not EventOwned Then Exit;
  FTimeOut := ATimeOut;
  FcmpClient := TIdIcmpClient.Create(Nil);
  With FcmpClient Do
  Begin
    Host := AHostIP;
    ReceiveTimeOut := ATimeOut;
    OnReply := ReplyEvent;
  End;
End;

//---------------------------------------------------------
Procedure TPingThread.Execute;
Begin
  Try
    FcmpClient.Ping;
    FReply := FReply And (WaitForSingleObject(FEvent, FTimeOut) = WAIT_OBJECT_0);
  Except
    FReply := False;
  End;
End;

//---------------------------------------------------------
Procedure TPingThread.ReplyEvent(Sender: TComponent; Const AReplyStatus: TReplyStatus);
Begin
  With AReplyStatus Do
  FReply := (ReplyStatusType = rsEcho) And (BytesReceived <> 0);
  SetEvent(FEvent);
End;

//---------------------------------------------------------
Procedure TPingThread.ThreadTerminate(Sender: TObject);
Begin
  FreeAndNil(FcmpClient);
  Inherited;
End;

{ TNetThread }
.
.
.
于 2013-07-11T02:35:38.080 に答える