并行ping多个网络设备的最佳方法是什么?

时间:2011-02-03 13:27:53

标签: delphi network-programming polling ping iocp

我通过迭代ping轮询网络中的很多设备(超过300个)。

程序按顺序轮询设备,因此速度很慢。 我想提高民意调查的速度。

在Delphi 7中有一些方法可以做到这一点:

  1. 每个设备都有一个执行ping的线程。手动管理线程。
  2. 学习和使用Indy 10.需要举例。
  3. 根据窗口消息使用重叠I / O.
  4. 根据事件使用完成端口。
  5. 什么更快,更容易?请举例说明一些例子或链接。

6 个答案:

答案 0 :(得分:9)

使用ICMP充斥网络不是一个好主意。

您可能需要考虑某种线程池并对ping请求进行排队,并且有一定数量的线程来处理请求。

答案 1 :(得分:6)

就个人而言,我会选择IOCP。我在NexusDB中非常成功地使用它进行传输实现。

如果要使用并行阻塞套接字和线程执行300个发送/接收周期,则最终需要300个线程。

使用IOCP,在将套接字与IOCP关联后,您可以执行300次发送操作,它们将在操作完成之前立即返回。操作完成后,所谓的完成包将排队到IOCP。然后,您有一个等待IOCP的线程池,操作系统会在完成数据包进入时唤醒它们。为了完成发送操作,您可以执行接收操作。接收操作也会立即返回,一旦实际完成,就会排队到IOCP。

关于IOCP的真正特殊之处在于它知道哪些线程属于它并且当前正在处理完成包。如果活动线程的总数(不在内核模式等待状态中)低于IOCP的并发数(默认情况下等于计算机上可用的逻辑核心数),则IOCP仅唤醒新线程。此外,如果有线程在IOCP上等待完成包(由于活动线程的数量等于并发数,尽管由于完成包排队而尚未启动),当前正在处理的其中一个线程完成包因任何原因进入内核模式等待状态,其中一个等待线程启动。

返回IOCP的线程以LIFO顺序获取完成包。也就是说,如果一个线程正在返回IOCP并且仍有等待的完成包,那么该线程直接获取下一个完成包,而不是进入等待状态,并且该线程等待最长时间唤醒。

在最佳条件下,您将拥有多个线程,这些线程等于并发运行的可用核心数(每个核心上一个),拾取下一个完成包,处理它,返回IOCP并直接拾取下一个完成包,都没有进入内核模式等待状态或必须进行线程上下文切换。

如果你有300个线程和阻塞操作,不仅会浪费至少300 MB的地址空间(对于堆栈的保留空间),但是当一个线程进入等待时你也会有不断的线程上下文切换状态(等待发送或接收完成)和完成发送或接收唤醒的下一个线程。 - Thorsten Engler 12小时前

答案 2 :(得分:5)

Windows上不推荐使用直接ICMP访问。可以直接访问Windows上的ICMP协议。由于恶意使用ICMP / ping / traceroute样式的原始套接字,我相信在某些版本的Windows上你需要使用Windows自带的api。特别是Windows XP,Vista和Windows 7不允许用户程序访问原始套接字。

我已经使用了ICMP.dll中的预制功能,这是一些Delphi ping组件所做的,但下面的评论提醒我这被认为是“使用未记录的API接口”这一事实。

以下是主要的delphi ping组件调用本身的示例:

function TICMP.ping: pIcmpEchoReply;
{var  }
begin
  // Get/Set address to ping
  if ResolveAddress = True then begin
    // Send packet and block till timeout or response
    _NPkts := _IcmpSendEcho(_hICMP, _Address,
                            _pEchoRequestData, _EchoRequestSize,
                            @_IPOptions,
                            _pIPEchoReply, _EchoReplySize,
                           _TimeOut);
    if _NPkts = 0 then begin
      result := nil;
      status := CICMP_NO_RESPONSE;
    end else begin
      result := _pIPEchoReply;
    end;
  end else begin
    status := CICMP_RESOLVE_ERROR;
    result := nil;
  end;
end;

我相信大多数现代Ping组件实现都将基于与上面相似的代码,并且我已经使用它在后台线程中运行此ping操作,没有任何probems。 (演示程序包含在下面的链接中)。

基于ICMP.DLL的演示的完整示例源代码是here

更新在About.com here.找到更现代的IPHLPAPI.DLL示例

答案 3 :(得分:4)

这是一个显示如何使用IOCP创建线程池的article from Delphi3000。我不是此代码的作者,但作者的信息在源代码中。

我在这里重新发布评论和代码:

  

现在每个人都应该明白什么   线程是,线程的原则   等等。对于那些有需要的人,   一个线程的简单功能就是   将处理从一个线程分离到   另一个,允许并发和   并行执行。主要原则   线程就像内存一样简单   已分配的,在之间引用   必须编组线程以确保   访问的安全性。有一些   其他原则,但这是真的   要关心的人。

     

然后......

     

线程安全队列将允许   要添加和删除的多个线程,   推送和弹出值   在First on First off上安全地排队   基础。有效率和良好   书面排队你可以有一个高度   发展中的有用成分   线程应用程序,从帮助   与线程安全日志记录,到   异步处理请求。

     

线程池只是一个线程或一个   最多的线程数   常用于管理队列   要求。例如,Web服务器   这将有一个连续的队列   需要处理的请求使用   线程池来管理http   请求,或COM +或DCOM服务器   使用线程池来处理rpc   要求。这样就完成了   处理一个人的影响较小   请求另一个,如果你跑了3   请求同步和第一个   请求花了1分钟完成,   第二个请求无法完成   至少1分钟加在上面   有时间处理,并为   这不是大多数客户   可以接受的。

     

那怎么做..

     

从队列开始!!

     

Delphi确实提供了一个TQueue对象   哪个可用但是   不幸的是,也没有线程安全   真的太有效了,但人   应该看看Contnrs.pas文件   看看borland怎么写堆栈和   队列。主要只有两个   这些是队列所需的功能   添加和删​​除/推送和弹出。   添加/推送将添加值,指针或   对象到队列的末尾。和   remove / pop将删除并返回   队列中的第一个值。

     

您可以从TQueue对象派生   并覆盖受保护的方法和   添加关键部分,这将   给你一些方法,但我愿意   希望我的队列等到新的   请求在队列中,然后放入   线程进入休息状态时   等待新的请求。这可能是   通过添加互斥锁或信令来完成   事件,但有一个更简单的方法。该   windows api提供IO完成   为我们提供线程的队列   安全访问队列和状态   在等待新的请求时休息   队列。

     

实施线程池

     

线程池将是非常的   简单并将管理x号   所需的线程并传递每个队列   请求提供的活动   处理。很少需要   实现一个TThread类和你的   要实施的逻辑和   封装在execute事件中   这个班,因此很简单   可以创建TSimpleThread类   这将执行任何方法   另一个上下文中的对象   线。一旦人们明白这一点,   所有你需要关心的   分配内存。

     

以下是它的实施方式。

     

TThreadQueue和TThreadPool   实施

(* Implemented for Delphi3000.com Articles, 11/01/2004
        Chris Baldwin
        Director & Chief Architect
        Alive Technology Limited
        http://www.alivetechnology.com
*)
unit ThreadUtilities;

uses Windows, SysUtils, Classes;

type
    EThreadStackFinalized = class(Exception);
    TSimpleThread = class;

    // Thread Safe Pointer Queue
    TThreadQueue = class
    private
        FFinalized: Boolean;
        FIOQueue: THandle;
    public
        constructor Create;
        destructor Destroy; override;
        procedure Finalize;
        procedure Push(Data: Pointer);
        function Pop(var Data: Pointer): Boolean;
        property Finalized: Boolean read FFinalized;
    end;

    TThreadExecuteEvent = procedure (Thread: TThread) of object;

    TSimpleThread = class(TThread)
    private
        FExecuteEvent: TThreadExecuteEvent;
    protected
        procedure Execute(); override;
    public
        constructor Create(CreateSuspended: Boolean; ExecuteEvent: TThreadExecuteEvent; AFreeOnTerminate: Boolean);
    end;

    TThreadPoolEvent = procedure (Data: Pointer; AThread: TThread) of Object;

    TThreadPool = class(TObject)
    private
        FThreads: TList;
        FThreadQueue: TThreadQueue;
        FHandlePoolEvent: TThreadPoolEvent;
        procedure DoHandleThreadExecute(Thread: TThread);
    public
        constructor Create( HandlePoolEvent: TThreadPoolEvent; MaxThreads: Integer = 1); virtual;
        destructor Destroy; override;
        procedure Add(const Data: Pointer);
    end;

implementation

{ TThreadQueue }

constructor TThreadQueue.Create;
begin
    //-- Create IO Completion Queue
    FIOQueue := CreateIOCompletionPort(INVALID_HANDLE_VALUE, 0, 0, 0);
    FFinalized := False;
end;

destructor TThreadQueue.Destroy;
begin
    //-- Destroy Completion Queue
    if (FIOQueue <> 0) then
        CloseHandle(FIOQueue);
    inherited;
end;

procedure TThreadQueue.Finalize;
begin
    //-- Post a finialize pointer on to the queue
    PostQueuedCompletionStatus(FIOQueue, 0, 0, Pointer($FFFFFFFF));
    FFinalized := True;
end;

(* Pop will return false if the queue is completed *)
function TThreadQueue.Pop(var Data: Pointer): Boolean;
var
    A: Cardinal;
    OL: POverLapped;
begin
    Result := True;
    if (not FFinalized) then
//-- Remove/Pop the first pointer from the queue or wait
        GetQueuedCompletionStatus(FIOQueue, A, Cardinal(Data), OL, INFINITE);

    //-- Check if we have finalized the queue for completion
    if FFinalized or (OL = Pointer($FFFFFFFF)) then begin
        Data := nil;
        Result := False;
        Finalize;
    end;
end;

procedure TThreadQueue.Push(Data: Pointer);
begin
    if FFinalized then
        Raise EThreadStackFinalized.Create('Stack is finalized');
    //-- Add/Push a pointer on to the end of the queue
    PostQueuedCompletionStatus(FIOQueue, 0, Cardinal(Data), nil);
end;

{ TSimpleThread }

constructor TSimpleThread.Create(CreateSuspended: Boolean;
  ExecuteEvent: TThreadExecuteEvent; AFreeOnTerminate: Boolean);
begin
    FreeOnTerminate := AFreeOnTerminate;
    FExecuteEvent := ExecuteEvent;
    inherited Create(CreateSuspended);
end;

procedure TSimpleThread.Execute;
begin
    if Assigned(FExecuteEvent) then
        FExecuteEvent(Self);
end;

{ TThreadPool }

procedure TThreadPool.Add(const Data: Pointer);
begin
    FThreadQueue.Push(Data);
end;

constructor TThreadPool.Create(HandlePoolEvent: TThreadPoolEvent;
  MaxThreads: Integer);
begin
    FHandlePoolEvent := HandlePoolEvent;
    FThreadQueue := TThreadQueue.Create;
    FThreads := TList.Create;
    while FThreads.Count < MaxThreads do
        FThreads.Add(TSimpleThread.Create(False, DoHandleThreadExecute, False));
end;

destructor TThreadPool.Destroy;
var
    t: Integer;
begin
    FThreadQueue.Finalize;
    for t := 0 to FThreads.Count-1 do
        TThread(FThreads[t]).Terminate;
    while (FThreads.Count > 0) do begin
        TThread(FThreads[0]).WaitFor;
        TThread(FThreads[0]).Free;
        FThreads.Delete(0);
    end;
    FThreadQueue.Free;
    FThreads.Free;
    inherited;
end;

procedure TThreadPool.DoHandleThreadExecute(Thread: TThread);
var
    Data: Pointer;
begin
    while FThreadQueue.Pop(Data) and (not TSimpleThread(Thread).Terminated) do begin
        try
            FHandlePoolEvent(Data, Thread);
        except
        end;
    end;
end;

end. 
  

你可以看到它很直接   向前,你可以   很容易实现任何排队   请求线程,真的任何   需要的要求类型   可以使用这些来完成线程化   对象并为您节省大量时间   努力。

     

您可以使用它来排队请求   从一个线程到多个线程,   或来自多个的队列请求   线程到一个线程,使得   这是一个非常好的解决方案。

     

以下是使用这些的一些示例   对象。

     

线程安全日志记录

     

允许多个   线程以异步方式写入   日志文件。

uses Windows, ThreadUtilities,...;

type
    PLogRequest = ^TLogRequest;
    TLogRequest = record
        LogText: String;
    end;

    TThreadFileLog = class(TObject)
    private
        FFileName: String;
        FThreadPool: TThreadPool;
        procedure HandleLogRequest(Data: Pointer; AThread: TThread);
    public
        constructor Create(const FileName: string);
        destructor Destroy; override;
        procedure Log(const LogText: string);
    end;

implementation

(* Simple reuse of a logtofile function for example *)
procedure LogToFile(const FileName, LogString: String);
var
    F: TextFile;
begin
    AssignFile(F, FileName);
    if not FileExists(FileName) then
        Rewrite(F)
    else
        Append(F);
    try
        Writeln(F, DateTimeToStr(Now) + ': ' + LogString);
    finally
        CloseFile(F);
    end;
end;

constructor TThreadFileLog.Create(const FileName: string);
begin
    FFileName := FileName;
    //-- Pool of one thread to handle queue of logs
    FThreadPool := TThreadPool.Create(HandleLogRequest, 1);
end;

destructor TThreadFileLog.Destroy;
begin
    FThreadPool.Free;
    inherited;
end;

procedure TThreadFileLog.HandleLogRequest(Data: Pointer; AThread: TThread);
var
    Request: PLogRequest;
begin
    Request := Data;
    try
        LogToFile(FFileName, Request^.LogText);
    finally
        Dispose(Request);
    end;
end;

procedure TThreadFileLog.Log(const LogText: string);
var
    Request: PLogRequest;
begin
    New(Request);
    Request^.LogText := LogText;
    FThreadPool.Add(Request);
end;
  

由于这是记录到文件,它会   将所有请求处理为单个   线程,但你可以做丰富的电子邮件   具有更高线程的通知   过程,甚至更好的过程   剖析正在发生的事情或   在我的程序中的步骤,我会   在另一篇文章中证明了这一点   一个人现在已经很久了。

     

现在我会告诉你这个,   享受..如果有的话发表评论   任何人都被困在了。

     

克里斯

答案 4 :(得分:3)

您是否需要网络上每台机器的响应,或者这300台机器只是大型网络的一部分?

如果您需要每台计算机的回复,可以考虑使用broadcast addressmulticast address作为回应请求。

答案 5 :(得分:2)

请尝试Linux的“chknodes”并行ping,它会向您网络的所有节点发送一次ping。如果指定的话,它也会执行反向查找并请求http响应。它完全用bash编写,即您可以轻松检查或根据您的需要进行修改。这是帮助的打印输出:

chknodes -h

chknodes ----快速并行ping

chknodes [-l | --log] [-h | --help] [-H | --http] [-u | --uninstall] [-v | --version] [-V | - 详细]

-l | --log记录到文件   -h | --help显示此帮助屏幕   -H | --http检查http响应   -n | --names获取主机名   -u | --uninstall删除安装   -v | --version显示版本   -V | --verbose显示每个被ping的IP地址

你需要为它执行权限(就像使用任何sh / bash脚本一样)才能运行它:

chmod +x chknodes

第一次运行即

./chknodes

它会建议将自己安装到/ usr / local / bin / chknodes,之后只需

chknodes

就够了。你可以在这里找到它:

www.homelinuxpc.com/download/chknodes