首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >Delphi (XE2) Indy (10)多线程Ping

Delphi (XE2) Indy (10)多线程Ping
EN

Stack Overflow用户
提问于 2012-10-12 11:56:46
回答 4查看 12.7K关注 0票数 11

我有一个60台计算机/设备的房间(40台计算机和20台基于Windows的示波器),我想知道谁和每个人都在使用ping。首先,我编写了一个标准ping (参见这里的德尔菲印平误差10040),它现在工作正常,但大多数计算机离线需要很长时间。

所以,我想做的是写一个MultiThread平,但我很挣扎。我在互联网上只看到了很少的例子,没有人能满足我的需求,这就是为什么我试图自己写。

我使用XE2和Indy 10,表单只由备忘录和按钮组成。

代码语言:javascript
复制
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.

我的问题是,当我取消对“睡眠(10)”的评论时,它“似乎”起作用,而“似乎”没有它就无法工作。这无疑意味着我在我所写的线程中遗漏了一点。

换句话说。当睡眠(10)在代码中时。每次我点击按钮来检查连接时,结果都是正确的。

如果没有睡眠(10),它的工作时间是“大部分”的,但有时结果是错误的,在离线计算机上给我一个ping回送,而在联机计算机上没有ping回送,因为ping答复没有分配给正确的线程。

欢迎任何意见或帮助。

-编辑/重要

作为对这个问题的普遍跟进,达里安·米勒创建了一个这里的Google代码项目 https://code.google.com/p/delphi-stackoverflow/,这是一个有效的基础。我将他的答案标记为“接受的答案”,但是用户应该参考这个开源项目(所有的功劳都属于他),因为将来肯定会扩展和更新它。

EN

回答 4

Stack Overflow用户

回答已采纳

发布于 2012-10-13 05:46:30

雷米解释了问题..。我想在印第做这件事已经有一段时间了,所以我发布了一个可能的解决方案,我只是把它放在一个新的Google代码项目上,而不是在这里有一个长时间的评论。这是第一次尝试,如果您需要集成一些更改,请告诉我:https://code.google.com/p/delphi-vault/

这段代码有两种方法可以让多线程客户端(如您的示例中所示),或者使用简单的回调过程。为Indy10和更高版本的Delphi编写。

您的代码最终将使用定义SynchronizedResponse方法的SynchronizedResponse后代:

代码语言:javascript
复制
  TMyPingThread = class(TThreadedPing)
  protected
    procedure SynchronizedResponse(const ReplyStatus:TReplyStatus); override;
  end;

为了触发一些客户端线程,代码类似于:

代码语言:javascript
复制
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;

线程响应在同步方法中被调用:

代码语言:javascript
复制
procedure TMyPingThread.SynchronizedResponse(const ReplyStatus:TReplyStatus);
begin
  frmThreadedPingSample.Memo1.Lines.Add(TPingClient.FormatStandardResponse(ReplyStatus));
end;
票数 5
EN

Stack Overflow用户

发布于 2012-10-13 00:16:57

根本的问题是pings是无连接的通信量。如果有多个TIdIcmpClient对象同时敲击网络,则一个TIdIcmpClient实例可以接收实际上属于另一个TIdIcmpClient实例的答复。您正在尝试通过检查SequenceId值在线程循环中解释这一点,但是您没有考虑到TIdIcmpClient已经在内部执行了相同的检查。它在循环中读取网络回复,直到收到预期的答复,或者直到ReceiveTimeout发生为止。如果它收到了它不期望的答复,它就会丢弃该答复。因此,如果一个TIdIcmpClient实例丢弃另一个TIdIcmpClient实例所期望的答复,则该答复将不会被您的代码处理,而另一个TIdIcmpClient可能会收到另一个TIdIcmpClient的答复,依此类推。通过添加Sleep(),可以减少(但不是消除)页面重叠的可能性。

对于您想要做的事情,您将无法使用TIdIcmpClient,因为-就是让多个ping并行运行,对不起。它根本不是为此而设计的。它无法按照您需要的方式来区分答复数据。您必须序列化您的线程,以便一次只能调用一个线程。

如果您不能选择序列化pings,则可以尝试将TIdIcmpClient的部分源代码复制到您自己的代码中。运行41个线程--40个设备线程和1个响应线程。创建一个所有线程都共享的套接字。让每个设备线程准备并使用该套接字向网络发送其单独的ping请求。然后让响应线程从同一个套接字中连续读取应答,并将它们路由回适当的设备线程进行处理。这是一项更多的工作,但它将给您提供您正在寻找的多重并行处理。

如果你不想遇到这些麻烦,另一种选择就是使用一个第三方应用程序,它已经支持同时使用多台机器,比如FREEPing

票数 11
EN

Stack Overflow用户

发布于 2012-10-12 12:41:28

我没有尝试您的代码,所以这都是假设的,但是我认为您搞砸了线程,得到了经典的race condition。我重申了使用AsyncCallsOmniThreadLibrary的建议--它们要简单得多,并且可以为您节省很少尝试“自己动手”的机会。

  1. 线程是用来最小化主线程负载的.线程构造函数应该尽可能少地记住参数。就我个人而言,我已经将idICMP创建转移到了.Execute方法中。如果出于任何原因,它希望创建它的内部同步对象,比如窗口和消息队列或信号等等,我希望它已经发生在一个新生成的线程中。
  2. 在.Execute中“继承”是没有意义的。最好把它拿掉。
  3. 沉默所有的异常都是糟糕的风格。你可能有错误--但你无法知道它们。您应该将它们传播到主线程并显示它们。OTL和AC在这方面帮助您,而对于tThread,您必须手动完成。如何处理AsyncCalls函数中抛出的不调用.Sync的异常?
  4. 异常逻辑有缺陷。如果抛出异常--如果没有设置成功的Ping --那么为什么要等待响应?您的循环应该在相同的尝试范围内-除了发出ping的帧。
  5. 您的doOnPingReplyfIdIcmpClient.Free访问fIdIcmpClient的内部程序之后执行。尝试将.Free更改为FreeAndNil ?这是在释放它之后使用死指针的一个典型错误。正确的方法是: 5.1。要么释放doOnPingReply中的对象 5.2。或者在调用doOnPingReplyidICMP.Free之前,将所有相关数据从TThread的私有成员vars复制到TThread的私有成员vars (并且只在doOnPingReply中使用这些vars) 5.3。只在TMyThread.BeforeDestructionTMyThread.Destroy中执行TMyThread.BeforeDestruction。毕竟,如果您选择在构造函数中创建对象,那么您应该在匹配的语言结构中释放它-析构函数。
  6. 由于不保留对线程对象的引用-- While not Terminated循环似乎是多余的。就像往常一样-循环和呼叫中断。
  7. 上面提到的循环需要CPU,就像自旋循环.请调用Sleep(0);Yield();内部循环,给其他线程更好的机会来完成他们的工作。不要在这里运行操作系统调度器--您不是在速度关键的路径上,没有理由在这里创建spinlock

总的来说,我认为:

  • 4和5对你来说是关键的错误
  • 1和3是一个潜在的问题,可能会影响,也可能不会。你最好‘安全’,而不是做冒险的事情和调查他们是否会工作。
  • 2和7-糟糕的风格,2关于语言和7关于平台
  • 6你要么计划扩展你的应用程序,要么你违反了YAGNI原则,不知道。
  • 坚持复杂的TThread,而不是OTL或AsyncCalls -战略错误。别把钩子放在跑道上,用简单的工具。

有趣的是,这是FreeAndNil可以暴露并说明的bug的例子,而FreeAndNil的反对者则声称它“隐藏”了bug。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/12858551

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档