我有一个60台计算机/设备的房间(40台计算机和20台基于Windows的示波器),我想知道谁和每个人都在使用ping。首先,我编写了一个标准ping (参见这里的德尔菲印平误差10040),它现在工作正常,但大多数计算机离线需要很长时间。
所以,我想做的是写一个MultiThread平,但我很挣扎。我在互联网上只看到了很少的例子,没有人能满足我的需求,这就是为什么我试图自己写。
我使用XE2和Indy 10,表单只由备忘录和按钮组成。
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/,这是一个有效的基础。我将他的答案标记为“接受的答案”,但是用户应该参考这个开源项目(所有的功劳都属于他),因为将来肯定会扩展和更新它。
发布于 2012-10-13 05:46:30
雷米解释了问题..。我想在印第做这件事已经有一段时间了,所以我发布了一个可能的解决方案,我只是把它放在一个新的Google代码项目上,而不是在这里有一个长时间的评论。这是第一次尝试,如果您需要集成一些更改,请告诉我:https://code.google.com/p/delphi-vault/
这段代码有两种方法可以让多线程客户端(如您的示例中所示),或者使用简单的回调过程。为Indy10和更高版本的Delphi编写。
您的代码最终将使用定义SynchronizedResponse方法的SynchronizedResponse后代:
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-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。
发布于 2012-10-12 12:41:28
我没有尝试您的代码,所以这都是假设的,但是我认为您搞砸了线程,得到了经典的race condition。我重申了使用AsyncCalls或OmniThreadLibrary的建议--它们要简单得多,并且可以为您节省很少尝试“自己动手”的机会。
.Execute方法中。如果出于任何原因,它希望创建它的内部同步对象,比如窗口和消息队列或信号等等,我希望它已经发生在一个新生成的线程中。doOnPingReply在fIdIcmpClient.Free访问fIdIcmpClient的内部程序之后执行。尝试将.Free更改为FreeAndNil ?这是在释放它之后使用死指针的一个典型错误。正确的方法是:
5.1。要么释放doOnPingReply中的对象
5.2。或者在调用doOnPingReply和idICMP.Free之前,将所有相关数据从TThread的私有成员vars复制到TThread的私有成员vars (并且只在doOnPingReply中使用这些vars) 5.3。只在TMyThread.BeforeDestruction或TMyThread.Destroy中执行TMyThread.BeforeDestruction。毕竟,如果您选择在构造函数中创建对象,那么您应该在匹配的语言结构中释放它-析构函数。While not Terminated循环似乎是多余的。就像往常一样-循环和呼叫中断。Sleep(0);或Yield();内部循环,给其他线程更好的机会来完成他们的工作。不要在这里运行操作系统调度器--您不是在速度关键的路径上,没有理由在这里创建spinlock。总的来说,我认为:
有趣的是,这是FreeAndNil可以暴露并说明的bug的例子,而FreeAndNil的反对者则声称它“隐藏”了bug。
https://stackoverflow.com/questions/12858551
复制相似问题