【问题标题】:What's the best way to ping many network devices in parallel?并行 ping 多个网络设备的最佳方法是什么?
【发布时间】:2011-06-20 16:15:32
【问题描述】:

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

程序按顺序轮询设备,所以速度很慢。 我想提高轮询速度。

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

  1. 每个设备都有一个线程执行 ping。手动管理话题。
  2. 学习和使用 Indy 10。需要示例。
  3. 使用基于窗口消息的重叠 I/O。
  4. 根据事件使用完成端口。

什么更快、更容易?请提供一些示例或链接。

【问题讨论】:

  • 我有类似的工具。它只是有很多线程在等待 ICMP 回显回复。
  • 如果你发出足够多的 ping,就会触发入侵检测系统。

标签: delphi network-programming polling ping iocp


【解决方案1】:

请尝试使用 Linux 的“chknodes”并行 ping,它将向您网络的所有节点发送单个 ping。如果指定,它还将执行 dns 反向查找和请求 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

【讨论】:

    【解决方案2】:

    直接 ICMP 访问在 Windows 上已弃用。对 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 操作,而无需任何探测。 (演示程序包含在下面的链接中)。

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

    更新更现代的 IPHLPAPI.DLL 示例位于 About.com here.

    【讨论】:

    【解决方案3】:

    我个人会选择 IOCP。我在 NexusDB 中非常成功地使用它来实现传输。

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

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

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

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

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

    如果您有 300 个线程和阻塞操作,那么您不仅会浪费至少 300 MB 地址空间(用于堆栈的保留空间),而且当一个线程进入等待时,您还会有持续的线程上下文切换状态(等待发送或接收完成)和完成发送或接收的下一个线程唤醒。 ——Thorsten Engler 12 小时前

    【讨论】:

    • 你能扩展一下吗? IO 完成端口如何帮助您进行 ping? Ping 所做的只是说“是的,我是一个网络设备,我决定承认我在这里,因为没有人禁用此设备上的 ICMP 请求响应”。如果您使用完成端口,您是否还需要使用原始套接字来滚动您自己的 ICMP? Win XP/7/Vista 中的原始套接字不是被阻止了吗?
    • Thorsten:有用的信息,但这应该在答案本身!如果没有链接或任何东西,“IOCP”对于原始提问者可能毫无意义,如果答案包含更多细节,它会有所帮助。您可以编辑您的答案以更新它。
    • @David M,最初的提问者列出了“根据事件使用完成端口”。那是 IOCP(= I/O 完成端口)。所以很明显,最初的提问者知道 IOCP。但是我现在已经将 cmets 移到了实际的答案中。感谢您指出这一点。
    • 我是一名发帖人,我正在寻找更好地实现跟踪设备可接受性的方法。
    • 我已经阅读了一些关于 IOCP 的信息,但我仍然没有在实践中使用它。
    【解决方案4】:

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

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

    现在每个人都应该明白什么 线程是,线程的原理 等等。对于有需要的人, 线程的简单功能是 从一个线程单独处理到 另一个,允许并发和 并行执行。主要原理 线程数也一样简单,内存 分配的在 必须对线程进行编组以确保 访问的安全性。有数 其他原则,但这确实是 关心的人。

    然后……

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

    线程池只是一个线程或一个 最多的线程数 通常用于管理一个队列 要求。例如网络服务器 这将有一个连续的队列 需要处理的请求使用 用于管理 http 的线程池 请求,或 COM+ 或 DCOM 服务器 使用线程池来处理 rpc 要求。这是这样做的,所以有 从一个处理的影响较小 请求另一个,说如果你跑了 3 同步请求和第一个 请求需要 1 分钟才能完成, 后两个请求将无法完成 在顶部添加至少 1 分钟 有自己的时间来处理,并为 大多数客户这不是 可以接受。

    那么如何做到这一点..

    从队列开始!!

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

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

    实现线程池

    线程池将非常 简单,将管理 x 个 所需的线程并通过每个队列 请求提供的事件 处理。很少需要 实现一个 TThread 类和你的 要实现的逻辑和 封装在执行事件中 类的,因此一个简单的 可以创建 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;
    

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

    现在我将把这个留给你, 享受..如果有的话发表评论 任何人们被困住的东西。

    克里斯

    【讨论】:

    【解决方案5】:

    您需要网络上每台机器的响应,还是这 300 台机器只是更大网络的一个子集?

    如果您需要每台机器的响应,您可以考虑使用broadcast addressmulticast address 来响应您的请求。

    【讨论】:

    • 设备分布在多个组中,每个组都有自己的子网。
    • 你也可以广播到子网。我的观点主要是,如果您确实希望得到许多机器的响应,那么这种方法效率低下。
    【解决方案6】:

    使用 ICMP 淹没网络不是一个好主意。

    您可能需要考虑某种线程池并将 ping 请求排队并让固定数量的线程执行请求。

    【讨论】:

    • 啊,回忆。一位同事曾经问过,几乎是对话式的,“我怎样才能同时发送多个 ping?”我的简短回答是“将它们放入线程中”。半小时后,一位系统管理员冲进我们的办公室,尖叫着谁在“入侵”我们的网络。
    • +1 表示自己造成的 DOS 失败,+1 表示莱昂纳多的战争故事。喜欢它。
    • 从原始问题很明显,提问者已经进行了 pinging(目前是连续的),所以告诉他他不应该做他已经在做的事情(并且似乎需要做)不是真的很有成效。
    • @Thorsten - ping 不是问题。洪水是。使用多线程 + ICMP 就足够了…… 把自己的脚射进去!
    • 我不是每秒都 ping 设备。列表中的每个设备都有一个超时。有什么好主意如何检查设备可访问性吗?我有一个想法。我的程序向设备发出请求,如果设备给出回复 - 它是活着的。所以可以重置 ping 超时,并且不会执行此 Ping,因此整体操作强度下降。
    猜你喜欢
    • 2010-10-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-01-26
    相关资源
    最近更新 更多