Как лучше всего пинговать множество сетевых устройств параллельно? - PullRequest
7 голосов
/ 03 февраля 2011

Я опрашиваю множество устройств в сети (более 300) с помощью итеративного пинга.

Программа опрашивает устройства последовательно, поэтому идет медленно.Я бы хотел повысить скорость опроса.

В Delphi 7 есть несколько способов сделать это:

  1. Каждое устройство имеет поток, выполняющий пинг.Управление потоками вручную.
  2. Изучение и использование Indy 10. Нужны примеры.
  3. Использование перекрывающихся операций ввода-вывода на основе сообщений окна.
  4. Использование портов завершения на основе событий.

Что быстрее, проще?Пожалуйста, приведите несколько примеров или ссылок, например.

Ответы [ 6 ]

9 голосов
/ 03 февраля 2011

Заполнение сети с помощью ICMP не очень хорошая идея.

Возможно, вы захотите рассмотреть какой-нибудь пул потоков и поставить в очередь запросы ping и иметь фиксированное число потоков, выполняющих запросы.

6 голосов
/ 03 февраля 2011

Лично я бы пошел с IOCP.Я очень успешно использую это для реализации транспорта в NexusDB.

Если вы хотите выполнить 300 циклов отправки / получения, используя параллельные блокирующие сокеты и потоки, вам в конечном итоге понадобится 300 потоков.

С IOCP, после того, как вы связали сокеты с IOCP, вы можете выполнить 300 операций отправки, и они вернутся немедленно, прежде чем операция будет завершена.По завершении операций так называемые пакеты завершения будут поставлены в очередь в IOCP.Затем у вас есть пул потоков, ожидающих на IOCP, и ОС пробуждает их при поступлении пакетов завершения. В ответ на завершенные операции отправки вы можете затем выполнить операции получения.Операции приема также возвращаются мгновенно, а после фактического завершения они помещаются в очередь в IOCP.

Реальная особенность IOCP в том, что он знает, какие потоки принадлежат ему, и в настоящее время обрабатывает пакеты завершения.И IOCP активизирует новые потоки только в том случае, если общее количество активных потоков (не в состоянии ожидания режима ядра) меньше, чем число параллельных операций IOCP (по умолчанию, равное количеству логических ядер, доступных на машине).Кроме того, если есть потоки, ожидающие пакетов завершения на IOCP (которые еще не были запущены, несмотря на то, что пакеты завершения были поставлены в очередь, потому что число активных потоков было равно числу параллелизма), момент один из потоков, который в настоящее время обрабатываетПакет завершения переходит в состояние ожидания режима ядра по любой причине, один из ожидающих потоков запущен.

Потоки, возвращающиеся к IOCP, получают пакеты завершения в порядке LIFO.То есть, если поток возвращается к IOCP, и есть пакеты завершения, все еще ожидающие, этот поток непосредственно выбирает следующий пакет завершения, вместо того, чтобы быть переведенным в состояние ожидания и поток, ожидающий самое долгое время пробуждения.

В оптимальных условиях у вас будет количество потоков, равное количеству доступных ядер, работающих одновременно (по одному на каждое ядро), выбирая следующий пакет завершения, обрабатывая его, возвращаясь к IOCP и непосредственно выбираядо следующего пакета завершения, и все это без необходимости входить в состояние ожидания режима ядра или переключение контекста потока.

Если бы вместо этого у вас было 300 потоков и блокирующих операций, вы не только потратили бы по крайней мере 300Адресное пространство МБ (для зарезервированного пространства для стеков), но у вас также будут постоянные переключатели контекста потока, поскольку один поток переходит в состояние ожидания (ожидание завершения отправки или получения) и следующий поток с завершенным пробуждением отправки или получениявверх.- Торстен Энглер 12 часов назад

5 голосов
/ 03 февраля 2011

Прямой доступ ICMP устарел в Windows. Прямой доступ к протоколу ICMP в Windows контролируется. Я полагаю, что из-за злонамеренного использования необработанных сокетов в стиле ICMP / ping / traceroute в некоторых версиях Windows вам потребуется использовать собственный API-интерфейс Windows. В частности, Windows XP, Vista и Windows 7 не позволяют пользовательским программам получать доступ к необработанным сокетам.

Я использовал функцию canned в 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 .

4 голосов
/ 03 февраля 2011

Вот статья от Delphi3000 , показывающая, как использовать IOCP для создания пула потоков.Я не автор этого кода, но информация об авторе находится в исходном коде.

Я публикую здесь комментарии и код:

Каждый должен понимать, что такое поток, принципы потоков и так далее.Для тех, кто в этом нуждается, простая функция потока состоит в том, чтобы отделить обработку от одного потока к другому, чтобы обеспечить параллельное и параллельное выполнение.Основной принцип работы потоков так же прост: выделенная память, на которую ссылаются потоки, должна быть распределена для обеспечения безопасности доступа.Есть ряд других принципов, но это действительно тот, о котором нужно заботиться.

И еще ..

Потоково-безопасная очередь позволит нескольким потокам добавлять и удалять, выдвигать и извлекать значенияв очередь и из нее безопасно в порядке очереди.Имея эффективную и хорошо написанную очередь, вы можете иметь очень полезный компонент для разработки многопоточных приложений, от помощи в обеспечении многопоточного ведения журналов до асинхронной обработки запросов.

Пул потоков - это просто поток или несколько потоковкоторые чаще всего используются для управления очередью запросов.Например, веб-сервер, который должен иметь непрерывную очередь запросов, требующих обработки, использует пулы потоков для управления запросами http, или сервер COM + или DCOM использует пул потоков для обработки запросов rpc.Это сделано для того, чтобы уменьшить влияние обработки одного запроса на другой, скажем, если вы выполнили 3 запроса синхронно, а первый запрос занял 1 минуту, вторые два запроса не будут выполнены в течение по крайней мере 1 минуты, добавив сверхусобственное время для обработки, и для большинства клиентов это неприемлемо.

Так как это сделать ..

Начиная с очереди !!

Delphi предоставляет объект TQueue, который доступен, но, к сожалению, не безопасен для потоков и не слишком эффективен, но люди должны взглянуть на файл Contnrs.pas, чтобы увидеть, как Borland пишет там стеки и очереди.Для очереди требуются только две основные функции: добавление, удаление, отправка и удаление.Add / push добавит значение, указатель или объект в конец очереди.А remove / pop удалит и вернет первое значение в очереди.

Вы можете получить из объекта TQueue и переопределить защищенные методы и добавить в критические разделы, это поможет вам, но я бы хотелхочу, чтобы моя очередь ожидала, пока в очереди появятся новые запросы, и переведет поток в состояние покоя, пока он ожидает новых запросов.Это можно сделать, добавив мьютексы или сигнальные события, но есть более простой способ.Windows api предоставляет очередь завершения ввода-вывода, которая предоставляет нам потокобезопасный доступ к очереди и состояние покоя при ожидании нового запроса в очереди.

Реализация пула потоков

Пул потоков будет очень простым и будет управлять 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;

Поскольку это ведение журнала в файл, он будет обрабатывать все запросы вплоть до одного потока, но вы могли бы делать расширенные уведомления по электронной почте с большим числом потоков, илиеще лучше, процесс профилирования с тем, что происходит, или шаги в вашей программе, которые я покажу в другой статье, поскольку эта статья уже довольно длинная.

Пока я оставлю вас в этом, наслаждайтесь .. Оставьтепрокомментируйте, если есть что-то, с чем люди застряли.

Крис

3 голосов
/ 03 февраля 2011

Вам нужен ответ от каждой машины в сети, или эти 300 машин являются лишь частью более крупной сети?

Если вам нужен ответ от каждой машины, вы можете использовать широковещательный адрес или многоадресный адрес для вашего эхо-запроса.

2 голосов
/ 15 января 2014

Пожалуйста, попробуйте параллельный пинг "chknodes" для Linux, который отправит один пинг всем узлам вашей сети.Он будет также выполнять обратный поиск DNS и запрашивать HTTP-ответ, если указано так.Он полностью написан на bash, т.е. вы можете легко проверить его или изменить в соответствии со своими потребностями.Вот распечатка справки:

chknodes -h

chknodes ---- быстрый параллельный пинг

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 Показывать каждый ip-адрес pinged

Вам нужно дать право на выполнение (как и для любого скрипта sh / bash), чтобы запустить его:

chmod +x chknodes

При первом запускето есть

./chknodes

он предложит установить себя в / usr / local / bin / chknodes, после чего будет достаточно просто

chknodes

.Вы можете найти его здесь:

www.homelinuxpc.com / download / chknodes

...