Im vorherigen Artikel habe ich das Grundgerüst und den Test der parallelen Verarbeitung beschrieben. Heute will ich den eigentlichen Scan des LAN-Segments zeigen.

Wie in einem früheren Beitrag bereits angesprochen verwenden die Methoden der TPL Delegaten, welche die eigentliche Aufgabe ausführen. Die Anforderungen an eine solche Methode sind also:

  1. Es wird kein Rückgabewert benötigt.
  2. Die jeweilige Hostadresse als Argument entgegen nehmen.
  3. Die jeweilige IP-Adresse in einen Hostnamen auflösen.
  4. Die Gewährleistung das jeder gesendete Ping an den sendenden Thread zurückgegeben wird.
  5. Das empfangene PingReply-Objekt auswerten.
  6. Das Ergebnis threadsicher an ein Objekt übergeben welches die Ergebnisse hält.

Da die Anforderungen nun dargelegt wurden, kann jetzt die Umsetzung in den Code beginnen. Zur Verwendung als Delegat einer TPL Methode, muss das Argument als Typ Object übergeben werden. Sollte dies vergessen werden, wird man vom Compiler sehr schnell daran erinnert. Um Punkt 4. umzusetzen muss der Zugriff auf die Betriebssystemthreads gewährleistet sein. Deshalb muss die Methode die entsprechende Sicherheitsberechtigung anfordern. Genau dafür ist das SecurityPermissionAttribute vorgesehen. Also muss die Signatur wie folgt aussehen:

[SecurityPermission(SecurityAction.Demand,
    Flags = SecurityPermissionFlag.ControlThread)]
private static void CheckHost(object hostNumber)
{
   // hier der Code
}

Zur Gewährleistung von Punkt 4. habe ich die gesamte Logik zwischen die Methoden Thread.BeginThreadAffinity und Thread.EndThreadAffinity eingeschlossen. Zur Auflösung der IP-Adresse in einen Hostnamen, stellt das .NET-Framework die Dns-Klasse zur Verfügung. Um jedoch die Methoden der Klasse nutzen zu können, muss zunächst die als Zeichenfolge vorliegende IP-Adresse in ein IPAddress-Objekt konvertiert werden.

// IPAdress-Objekt erzeugen
IPAddress ipAdress = IPAddress.Parse(netSegment + hostNumber.ToString());

// den HostName der IP-Adresse ermitteln
string hostName = Dns.GetHostEntry(ipAdress).HostName;

Falls die IP-Adresse nicht aufgelöst werden kann, gibt die Eigenschaft HostName der Dns-Klasse die IP-Adresse wieder zurück. Da mir dieses Verhalten nicht gefällt, überprüfe ich ob es sich bei der zurückgegebenen Zeichenfolge um eine IP-Adresse handelt. Mit der Regex-Klasse ist dies einfach zu bewerkstelligen.

private const string ipPattern = @"((2[0-4]\d|25[0-5]|[01]?\d\d?)\.){3}(2[0-4]\d|25[0-5]|[01]?\d\d?)";

public static bool ValidateIP(string ipAddress)
{
    if (Regex.IsMatch(ipAddress, ipPattern, RegexOptions.CultureInvariant))
    {
        return true;
    }

    return false;
}

Da die Verwendung der Anwendung nur in einem LAN Vorgesehen ist, können die PingOptions mit entsprechend niedrigen Werten konfiguriert werden. In einem LAN kommen eher selten mehr als zwei Weiterleitungen vor. Der Standard-Timeout der Ping-Klasse von 5 Sekunden ist für eine Verwendung in einem LAN wohl auch etwas zu hoch angesetzt.

Zum auswerten des Status des PingReply-Objekts reicht es im allgemeinen auf die Zustände Success, DestinationHostUnreachable und TimedOut der IPStatus Enumeration zu prüfen.

Um die einzelnen Ergebnisse threadsicher an das Objekt zu übergeben welches die Ergebnisse speichert, muss dieses Objekt einen Mechanismus zur Threadsicherheit bereitstellen. Dieses Objekt, in meinem Fall die Klasse ClientList auf die ich später noch eingehe werde, bietet die Eigenschaft AddedSuccessfull welche signalisiert, dass das Ergebnis erfolgreich gespeichert wurde. In dieser Klasse wird ein Mutex verwendet, der den Zugriff der Thread's regelt. Die genannte Eigenschaft, vom Typ bool, wird verwendet um zu überwachen wann das Sichern eines Ergebnis erfolgreich war.

// den ReplyStatus auswerten
switch (reply.Status)
{
    // Ping erfolgreich
    case IPStatus.Success:
        while (true)
        {
            clientList.AddOrReplace(ipAdress.ToString(), hostName, true);
            if (clientList.AddedSuccessfull)
            {break;
            }
            Thread.Sleep(10);
        }
        break;

        // weitere Code
}

Sollte der Zugriff zum Speichern durch einen anderen Thread kurzzeitig verhindert werden, wird nach einer kurzen Pause erneut versucht zu Sichern. Auf diese Weiße kann gewährleistet werden, dass die Ergebnisse tatsächlich so geschrieben werden wie sie vorliegen und nicht von einem zweiten Thread mit einem anderen Ergebnis überschrieben werden.

Der Vollständigkeit halber hier noch die komplette Methode:

[SecurityPermission(SecurityAction.Demand,
     Flags = SecurityPermissionFlag.ControlThread)]
 private static void CheckHost(object hostNumber)
 {
     // Pingprüfung an den physischen Betriebssystem-Thread binden
     Thread.BeginThreadAffinity();

     // neuen Ping initialisieren
     Ping pingSender = new Ping();

     // Ping-Optionen festlegen
     PingOptions options = new PingOptions(2, true);
     PingReply reply;
     int timeOut = 60;
     byte[] buffer = new byte[32];

     // IPAdress-Objekt erzeugen
     IPAddress ipAdress = IPAddress.Parse(netSegment + hostNumber.ToString());

     // den HostName der IP-Adresse ermitteln
     string hostName = Dns.GetHostEntry(ipAdress).HostName;

     // prüfe ob die IP-Adresse als HostName zurückgegeben wird.
     if (IPHelper.ValidateIP(hostName))
     {
         // HostName als unbekannt kennzeichnen
         hostName = "Unbekannt";

         // Ping senden und Antwort an das reply-Objekt übergeben
         reply = pingSender.Send(ipAdress, timeOut, buffer, options);

         // wenn Ping erfolgreich, IP-Adresse mit HostName unbekannt eintragen.
         if (reply.Status == IPStatus.Success)
         {
             while (true)
             { clientList.AddOrReplace(ipAdress.ToString(), hostName, true); if (clientList.AddedSuccessfull) {     break; } Thread.Sleep(10);
             }
         }
     }
     else
     {
         // Ping senden und Antwort an das reply-Objekt übergeben
         reply = pingSender.Send(hostName, timeOut, buffer, options);

         // den ReplyStatus auswerten
         switch (reply.Status)
         {
             // Ping erfolgreich
             case IPStatus.Success: while (true) {     clientList.AddOrReplace(ipAdress.ToString(), hostName, true);     if (clientList.AddedSuccessfull)     {         break;     }     Thread.Sleep(10); } break;

             // Host nicht erreichbar
             case IPStatus.DestinationHostUnreachable: while (true) {     clientList.AddOrReplace(ipAdress.ToString(), hostName, false);     if (clientList.AddedSuccessfull)     {         break;     }     Thread.Sleep(10); } break;

             // Zeit abgelaufen
             case IPStatus.TimedOut: while (true) {     clientList.AddOrReplace(ipAdress.ToString(), hostName, false);     if (clientList.AddedSuccessfull)     {         break;     }     Thread.Sleep(10); } break;
         }
     }

     // Thread-Bindung aufheben
     Thread.EndThreadAffinity();
 }

Die Klasse ClientList, welche die Ergebnisse zur weiteren Verwendung speichert, werde ich im dritten und letzten Teil vorstellen.

Wenn ihnen der Artikel gefallen hat oder er für sie hilfreich war, bitte "kicken" sie ihn.
kick it on dotnet-kicks.de