7

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

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

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

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

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

4

6 回答 6

9

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

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

于 2011-02-03T14:20:04.117 回答
6

我个人会选择 IOCP。我非常成功地将它用于 NexusDB 中的传输实现。

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

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

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

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

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

如果您将有 300 个线程和阻塞操作,那么您不仅会浪费至少 300 MB 地址空间(用于堆栈的保留空间),而且当一个线程进入等待状态(等待发送或接收完成)和下一个线程完成发送或接收唤醒。– 托尔斯滕·恩格勒 12 小时前

于 2011-02-03T14:56:33.490 回答
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 操作,而无需任何探测。(演示程序包含在下面的链接中)。

此处为基于 ICMP.DLL 的演示的完整示例源代码。

更新更现代的 IPHLPAPI.DLL 示例可在 About.com 上找到。

于 2011-02-03T14:46:17.477 回答
4

这是一篇来自 Delphi3000的文章,展示了如何使用 IOCP 创建线程池。我不是此代码的作者,但作者的信息在源代码中。

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

现在大家应该明白什么是线程,线程的原理等等。对于有需要的人来说,线程的简单功能是将处理从一个线程分离到另一个线程,以允许并发和并行执行。线程的主要原理很简单,线程之间引用的分配内存必须进行编组以确保访问安全。还有许多其他原则,但这确实是需要关心的。

并且在..

线程安全队列将允许多个线程在先到先关的基础上安全地向队列添加和删除、推送和弹出值。使用高效且编写良好的队列,您可以在开发线程应用程序时拥有一个非常有用的组件,从帮助线程安全日志记录到异步处理请求。

线程池只是一个线程或多个线程,最常用于管理请求队列。例如,具有需要处理的连续请求队列的 Web 服务器使用线程池来管理 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;

由于这是记录到一个文件,它会将所有请求处理到一个线程,但是您可以使用更高的线程数进行丰富的电子邮件通知,或者甚至更好地处理分析程序中正在发生的事情或步骤,我将演示在另一篇文章中,因为这篇文章现在已经很长了。

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

克里斯

于 2011-02-03T15:59:58.000 回答
3

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

如果您需要每台机器的响应,您可以考虑为您的回显请求使用广播地址或多播地址。

于 2011-02-03T14:27:52.483 回答
2

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

chknodes -h

chknodes ---- 快速并行 ping

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

-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

于 2014-01-15T18:58:29.707 回答