Каков наилучший способ параллельного эхо-тестирования множества сетевых устройств?

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

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

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

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

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


person Dr.eel    schedule 03.02.2011    source источник
comment
у меня есть подобный инструмент. это просто множество потоков, ожидающих эхо-ответов ICMP.   -  person Free Consulting    schedule 03.02.2011
comment
Если вы отправляете достаточно пингов, вы активируете системы обнаружения вторжений.   -  person Warren P    schedule 03.02.2011


Ответы (6)


Заливать сеть ICMP — не лучшая идея.

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

person Lloyd    schedule 03.02.2011
comment
Ах, воспоминания. Однажды коллега почти в разговоре спросил, как я могу отправить несколько пингов одновременно? Мой короткий ответ был помещен в темы. Через полчаса к нам в офис ворвался системный администратор с криками о том, кто взламывает нашу сеть. - person Leonardo Herrera; 03.02.2011
comment
+1 за провал DOS, нанесенный самому себе, и +1 к военной истории Леонардо. Любить это. - person Chris Thornton; 03.02.2011
comment
Из первоначального вопроса очевидно, что спрашивающий уже выполняет пингование (последовательно в настоящее время), поэтому говорить ему, что он не должен делать то, что он уже делает (и, кажется, должен делать), не очень продуктивно. - person Thorsten Engler; 04.02.2011
comment
@Thorsten - проблема не в пинге. Наводнение есть. Использование множества потоков + ICMP достаточно веревки... чтобы выстрелить себе в ногу! - person Leonardo Herrera; 05.02.2011
comment
Я не пингую устройства каждую секунду. Для каждого устройства в списке есть тайм-аут. Есть ли хорошая идея, как проверить доступность устройства? У меня есть одна идея. Моя программа делает запросы к устройствам, если устройство дает ответ - оно живое. Таким образом, время ожидания пинга может быть сброшено, и этот пинг не будет выполнен, поэтому общая интенсивность операций снизится. - person Dr.eel; 09.02.2011
comment
Наконец мы удалили Ping-запросы. Мы делаем TCP.Connect() в асинхронном режиме, если подключено, мы считаем устройство доступным. - person Dr.eel; 27.01.2014
comment
@Dr.eel Вам потребовалось ~ 3 года? :П - person Lloyd; 27.01.2014
comment
@Lloyd Это ответ на последнюю добавленную запись (от ajaaskel 16 января в 15:53). Я думаю, хорошо, чтобы люди знали о решении. - person Dr.eel; 03.02.2014

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

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

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

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

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

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

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

person Thorsten Engler    schedule 03.02.2011
comment
Можете ли вы расширить это? Как порты завершения ввода-вывода помогают пинговать? Все, что Ping делает, это говорит да, я сетевое устройство, и я решил подтвердить, что я здесь, потому что никто не отключил ответы на запросы ICMP на этом устройстве. Если бы вы использовали порты завершения, не нужно ли было бы вам также использовать необработанные сокеты для развертывания собственного ICMP? А не заблокированы ли необработанные сокеты в Win XP/7/Vista? - person Warren P; 03.02.2011
comment
Thorsten: полезная информация, но она должна быть в самом ответе! IOCP может ничего не значить для первоначального задавшего вопрос без ссылки или чего-либо еще, помогает, если ответы содержат немного больше деталей. Вы можете отредактировать свой ответ, чтобы обновить его. - person David; 04.02.2011
comment
@David M, первоначальный вопросник указал Использовать порты завершения на основе событий. Это IOCP (= порт завершения ввода/вывода). Таким образом, казалось совершенно очевидным, что первоначальный задавший вопрос знает о IOCP. Но теперь я переместил комментарии в фактический ответ. Спасибо что подметил это. - person Thorsten Engler; 04.02.2011
comment
Я постер, и я ищу лучшую реализацию приемлемости устройств слежения. - person Dr.eel; 09.02.2011
comment
Я читал некоторую информацию о IOCP, но я до сих пор не использую его на практике. - person Dr.eel; 09.02.2011
comment
Я ошибаюсь здесь? Вы не можете использовать IOCP с протоколом ICMP, потому что вам нужно использовать IP Helper API или ICMP DLL для доступа к ICMP из вашего программного обеспечения пользовательского режима (win32). Итак, какое значение имеет обсуждение преимуществ IOCP, если вы не можете получить доступ к протоколу ICMP с помощью winsock? - person Warren P; 13.02.2011
comment
Вы можете использовать необработанные сокеты для отправки/получения ICMP (это то, что делают API-интерфейс IP Helper и ICMP DLL). Необработанные сокеты отлично работают с IOCP. - person Thorsten Engler; 14.02.2011

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

Я использовал стандартную функциональность в ICMP.dll, что и делают некоторые компоненты ping Delphi, но комментарий ниже предупредил меня о том, что это считается «использованием недокументированного интерфейса API».

Вот пример вызова основного компонента ping для Delphi:

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 здесь.

person Warren P    schedule 03.02.2011
comment
icmp.dll не был задокументирован. С другой стороны, документирован IP Helper API (Iphlpapi.dll) с функцией IcmpSendEcho (msdn.microsoft.com/en-us/library/aa366050(v=VS.85).aspx) - person Ian Boyd; 09.02.2011
comment
Я использую реализацию Iphlpapi.dll. - person Dr.eel; 09.02.2011
comment
Спасибо за предупреждение об Iphlpapi.dll. - person Warren P; 30.09.2012

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

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

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

И на..

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

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

Итак, как это сделать..

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

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

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

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

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

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

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

Крис

person Mick    schedule 03.02.2011

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

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

person Leigh    schedule 03.02.2011
comment
Устройства расположены во многих группах, и каждая группа имеет свою подсеть. - person Dr.eel; 09.02.2011
comment
Ну, вы также можете транслировать в подсети. Моя точка зрения в основном заключалась в том, что если есть много машин, от которых вам не нужен ответ, то этот метод будет неэффективным. - person Leigh; 22.02.2011

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

chknodes -h

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

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

-л | --log Журнал в файл -h | --help Показать этот экран справки -H | --http Проверить также ответ http -n | --names Получить также имена хостов -u | --uninstall Удалить установку -v | --version Показать версию -V | --verbose Показать каждый пропингованный IP-адрес

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

chmod +x chknodes

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

./chknodes

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

chknodes

будет достаточно. Вы можете найти это здесь:

www.homelinuxpc.com/download/chknodes

person ajaaskel    schedule 15.01.2014