Максимальный параллелизм задач - использование TCP / IP с определенной фильтрацией портов - PullRequest
0 голосов
/ 05 июля 2018

Это дополнительный вопрос к этому вопросу. На следующем уровне я теперь хочу использовать максимальный параллелизм задач для подключения к ожидаемым хостам на большом наборе IP-адресов, используя TCP / IP на конкретном порту.

Мои собственные исследования, а также рекомендации сообщества привели меня к ключевым статьям, например:

Это позволило мне настроить свой собственный код, который работает нормально, но в настоящее время занимает полные 30 секунд, чтобы завершить сканирование 255 IP-адресов, используя только один конкретный порт. Учитывая тест, машина имеет 8 логических ядер, это наблюдение показывает, что моя конструкция действительно порождает максимум 8 одновременных задач (255/8 = 31,85).

Функция, которую я написал, возвращает список отвечающих IP-адресов {IPs}, который является подмножеством списка всех IP-адресов {IP_Ports}, которые необходимо проверить. Это мой текущий код, который работает нормально, но еще не подходит для использования в больших сетях из-за того, что я подозреваю, из-за отсутствия эффективного параллелизма задач:

// Check remote host connectivity
public static class CheckRemoteHost
{

    // Private Class members
    private static bool AllDone     = false;
    private static object lockObj   = new object();
    private static List<string> IPs;


    // Wrapper: manage async method <TCP_check>
    public static List<string> TCP(Dictionary<string, int> IP_Ports, int TimeoutInMS = 100)
    {
        // Locals
        IPs = new List<string>();

        // Perform remote host check
        AllDone = false;
        TCP_check(IP_Ports, TimeoutInMS);
        while (!AllDone) { Thread.Sleep(50); }

        // Finish
        return IPs;
    }
    private static async void TCP_check(Dictionary<string, int> IP_Ports, int timeout)
    {// async worker method:  check remote host via TCP-IP


        // Build task-set for parallel IP queries
        var tasks = IP_Ports.Select(host => TCP_IPAndUpdateAsync(host.Key, host.Value, timeout));

        // Start execution queue
        await Task.WhenAll(tasks).ContinueWith(t =>
        {
            AllDone = true;
        });
    }
    private static async Task TCP_IPAndUpdateAsync(string ip, int port, int timeout)
    {// method to call IP-check

        // Run method asynchronously
        await Task.Run(() =>
        {
            // Locals
            TcpClient client;
            IAsyncResult result;
            bool success;

            try
            {
                client  = new TcpClient();
                result  = client.BeginConnect(ip, port, null, null);
                success = result.AsyncWaitHandle.WaitOne(TimeSpan.FromMilliseconds(timeout));

                if (success)
                {
                    lock (lockObj)
                    {
                        IPs.Add(ip);
                    }
                }
            }
            catch (Exception e)
            {
                // do nothing
            }
        });
    }


}// end     public static class CheckRemoteHost

Итак, мой вопрос: как я могу максимизировать параллелизм задачи запроса ответа с использованием TCP / IP на порту X, чтобы я мог получить очень быстрое сканирование сети IP-порта в больших внутренних сетях?

1 Ответ

0 голосов
/ 06 июля 2018

Подробнее

Планировщиком задач по умолчанию обычно является планировщик ThreadPool. Это означает, что количество одновременных задач будет ограничено доступными потоками в пуле.

Примечания

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

(Источник: https://msdn.microsoft.com/en-us/library/system.threading.threadpool.getminthreads(v=vs.110).aspx)

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

* Решения 1018 * 1. Использование ConnectAsync с таймаутом. Вместо создания отдельной задачи, которая блокирует ожидание соединения. Вы можете вызвать ConnectAsync и присоединиться к нему с задержкой, чтобы создать тайм-аут. Похоже, что ConnectAsync не блокирует потоки пула потоков. public static async Task<bool> ConnectAsyncWithTimeout(this Socket socket, string host, int port, int timeout = 0) { if (timeout < 0) throw new ArgumentOutOfRangeException("timeout"); try { var connectTask = socket.ConnectAsync(host, port); var res = await Task.WhenAny(connectTask, Task.Delay(timeout)); await res; return connectTask == res && connectTask.IsCompleted && !connectTask.IsFaulted; } catch(SocketException se) { return false; } } Пример использования private static async Task TCP_IPAndUpdateAsync(string ip, int port, int timeout) {// method to call IP-check client = new TcpClient(); var success = await client.Client.ConnectAsyncWithTimeout(ip, port, timeout); if (success) { lock (lockObj) { IPs.Add(ip); } } } 2. Использование длительных задач. Используя Task.Factor.StartNew , вы можете указать, что задача LongRunning . Планировщик задач пула потоков, в частности, создаст новый поток для задачи вместо использования пула потоков. Это позволит преодолеть 8-ниточный лимит, которого вы достигнете. Однако следует отметить, что это не очень хорошее решение, если вы планируете наивно создавать тысячи задач. Поскольку в этот момент узким местом будет переключение контекста потока. Однако вы можете разделить всю работу, например, на 100 задач. 3. Использовать неблокирующее соединение Этот метод не требует создания нескольких задач. Вместо этого вы можете вызвать несколько соединений в одном потоке и проверить состояние нескольких сокетов одновременно. Этот метод немного сложнее, хотя. Если вы предпочитаете использовать этот подход и хотите получить более полный пример, тогда оставьте комментарий. Вот краткий пример использования API. var socket = new Socket(SocketType.Stream, ProtocolType.Tcp); socket.Blocking = false; try { socket.Connect("127.0.0.1", 12345); } catch(SocketException se) { //Ignore the "A non-blocking socket operation could not be completed immediately" error if (se.ErrorCode != 10035) throw; } //Check the connection status of the socket. var writeCheck = new List<Socket>() { socket }; var errorCheck = new List<Socket>() { socket }; Socket.Select(null, writeCheck, errorCheck, 0); if (writeCheck.Contains(socket)) { //Connection opened successfully. } else if (errorCheck.Contains(socket)) { //Connection refused } else { //Connect still pending }

...