C# Tipps, Teil 1 - Netzwerk und Internet

Veröffentlicht: 05. Sep 2005

Auszug aus dem Buch "Microsoft Visual C#.NET - Programmier-Rezepte - Hunderte von Lösungen und Codebeispielen aus der Praxis".

Das nächste Mal, wenn Sie auf ein Problem in Ihrem C#-Code stoßen, das schwierig zu lösen scheint, werfen Sie doch einen Blick in dieses Buch. Hier finden Sie Lösungen und Best Practices zum Schreiben effizienten Codes in C#.

´Microsoft Visual C#.NET Programmierrezepte´ ist Ihre Quelle für hunderte von C#- und .NET Framework-Programmier-Szenarien und -Aufgaben, die mit Hilfe eines konsistenten Problem/Lösungs-Ansatzes erläutert und gelöst werden.

Der logische Aufbau des Buchs erlaubt Ihnen, schnell die Themen zu finden, die Sie interessieren und damit praktische Beispiele, Code-Schnipsel, Best Practices und undokumentierte Vorgehensweisen, die Ihnen helfen, Ihre Arbeit schnell und effizient zu erledigen.

Sämtliche Codebeispiele sind von der Microsoft Press-Website downloadbar.

Microsoft Press


Diesen Artikel können Sie dank freundlicher Unterstützung von Microsoft Press auf MSDN Online lesen. Microsoft Press ist ein Partner von MSDN Online.


Zur Partnerübersichtsseite von MSDN Online

Das Microsoft .NET Framework bietet Klassen für die Netzwerkprogrammierung an, die sich in zwei Namespaces befinden: System.Net und System.Net.Sockets. Diese Klassen unterstützen jede Funktionalität, von der auf Sockets basierenden Programmierung mit TCP/IP bis hin zum Download von Dateien und HTML-Seiten aus dem Web oder über HTTP.

Auf dieser Seite

 Eine Datei über HTTP herunterladen
 Eine Datei herunterladen und mit einem Stream verarbeiten
 Eine HTML-Seite von einer Site abrufen, die eine Authentifizierung verlangt
 Eine Webseite in einer Windows-Anwendung anzeigen
 Die IP-Adresse des aktuellen Computers ermitteln
 Eine IP-Adresse anhand des Host-Namens ermitteln
 Ein Ping-Signal an eine IP-Adresse senden
 Kommunizieren mit TCP

Eine Datei über HTTP herunterladen

Aufgabe
Sie möchten eine Datei mithilfe von HTTP von einer Website herunterladen.

Lösung
Benutzen Sie die statische Methode DownloadFile der Klasse System.Net.WebClient.

Beschreibung

Das .NET Framework bietet mehrere Mechanismen an, um Daten über HTTP zu versenden. Eines der einfachsten Verfahren ist der Einsatz der Hilfsklasse System.Net.WebClient. Diese Klasse bietet höhere Methoden wie DownloadFile und UploadFile an, die über keine integrierte Unterstützung der asynchronen Kommunikation oder Authentifizierung verfügen. Wenn Sie diese Funktionalität benötigen, können Sie die weiter entwickelten Klassen WebRequest und WebResponse verwenden, die in den Rezepten 11.2 und 11.3 beschrieben sind.Die folgende Anwendung lädt von der Microsoft-Website eine Grafik namens banner.gif herunter und speichert diese lokal.

using System;
using System.Net;
using System.IO;

public class Download
{
    private static void Main()
    {
        string remoteUri = "http://www.microsoft.com/mspress/images/banner.gif";
        string localFileName = "banner.gif";
        
        WebClient client = new WebClient();
        Console.WriteLine("Download der Datei " + 
            remoteUri + " nach " + Path.GetFullPath(localFileName));
        
 
        // Download durchführen.
        client.DownloadFile(remoteUri, localFileName);        
        Console.WriteLine("Download abgeschlossen.");

        Console.ReadLine();
    }
}

Eine Datei herunterladen und mit einem Stream verarbeiten

Aufgabe
Sie möchten eine Datei von einer Website abrufen, die jedoch nicht direkt auf der Festplatte gespeichert werden soll. Stattdessen möchten Sie die Daten in Ihrer Anwendung verarbeiten.

Lösung
Benutzen Sie die Klasse WebRequest, um ihre Anforderung zu erstellen, die Klasse WebResponse, um die Antwort vom Webserver abzurufen, und einen Leser (in der Regel ein StreamReader-Objekt für HTML oder Textdaten bzw. ein BinaryReader-Objekt für Binärdateien), um die erhaltenen Daten auszuwerten.

Beschreibung

Das Herunterladen einer Datei aus dem Web erfordert die folgenden vier grundlegenden Schritte:

  1. Benutzen Sie die statische Methode Create der Klasse System.Net.WebRequest, um die gewünschte Seite anzugeben. Diese Methode gibt, abhängig von dem von Ihnen verwendeten URI (Uniform Resource Identifier), ein von WebRequest abgeleitetes Objekt zurück. Wenn Sie beispielsweise einen HTTP-URI benutzen (http://), wird eine HttpWebRequest-Instanz generiert. Wenn Sie einen Dateisystem-URI verwenden (file://), wird eine FilewebRequest-Instanz erzeugt. Sie können mit der Eigenschaft WebRequest.Timeout einen Timeout festlegen.

  2. Benutzen Sie die GetResponse-Methode des Objekts WebRequest, um ein WebResponse-Objekt für die Seite zurückzugeben. Wenn der Timer der Anforderung abgelaufen ist, wird die Ausnahme WebException ausgelöst.

  3. Erstellen Sie ein StreamReader- oder BinaryReader-Objekt für den WebResponse-Stream.

  4. Verarbeiten Sie den Stream, um ihn zum Beispiel in eine Datei zu schreiben, in Ihrer Anwendung anzuzeigen usw.

Der folgende Code ruft eine Grafik und den HTML-Code einer Webseite ab und zeigt beides an. Das Ergebnis ist in Abbildung 11.2 dargestellt.

using System;
using System.Net;
using System.IO;
using System.Drawing;
using System.Windows.Forms;

public class DownloadForm : System.Windows.Forms.Form
{
    private System.Windows.Forms.PictureBox picBox;
    private System.Windows.Forms.TextBox textBox;

    // (Der Designer-Code ist nicht aufgeführt.)

    private void DownloadForm_Load(object sender, System.EventArgs e)
    {

        string picUri =
            "http://www.microsoft.com/mspress/images/banner.gif";
        string htmlUri =
            "http://www.microsoft.com/default.asp";

        // Die Anforderung erstellen.
        WebRequest requestPic = WebRequest.Create(picUri);
        WebRequest requestHtml = WebRequest.Create(htmlUri);

        // Die Antworten abrufen.
        // Hierfür ist besonders dann sehr viel Zeit erforderlich,
        // wenn die Datei groß ist, da die gesamte Antwort abgerufen wird.
        WebResponse responsePic = requestPic.GetResponse();
        WebResponse responseHtml = requestHtml.GetResponse();

        // Den Antwort-Stream lesen.
        Image downloadedImage = Image.FromStream(responsePic.GetResponseStream());
        StreamReader r = new StreamReader(responseHtml.GetResponseStream());
        string htmlContent = r.ReadToEnd();
        r.Close();

        // Das Bild anzeigen.
        picBox.Image = downloadedImage;

        // Den Text anzeigen.
        textBox.Text = htmlContent;
    }
}

Um größere Dateien effizient aus dem Web herunterzuladen, können Sie die im vierten Kapitel beschriebenen asynchronen Techniken verwenden. Sie können ebenfalls die Methode WebRequest.BeginGetResponse einsetzen, die Ihren Code nicht blockiert und eine Rückrufprozedur aufruft, sobald die Antwort eingeht.

Inhalte aus dem Web herunterladen
Abbildung 11.1: Inhalte aus dem Web herunterladen

Eine HTML-Seite von einer Site abrufen, die eine Authentifizierung verlangt

Aufgabe
Sie möchten eine Datei von einer Website abrufen, doch diese verlangt Authentifizierungs¬informationen.

Lösung
Benutzen Sie die Klassen WebRequest und WebResponse. Bevor Sie jedoch das Anforderungsobjekt verwenden, konfigurieren Sie die Eigenschaft WebRequest.Credentials mit den Authentifizierungsinformationen.

Beschreibung

Einige Websites verlangen Benutzerauthentifizierungsinformationen. Wenn Sie eine Verbindung über einen Browser herstellen, können diese Informationen transparent übermittelt werden (z.B. bei einer lokalen Intranet-Site, die die integrierte Windows-Authentifizierung verwendet). Der Browser könnte diese Informationen jedoch auch über ein Anmeldedialogfeld entgegennehmen. Wenn Sie programmgesteuert auf eine Webseite zugreifen, muss Ihr Code diese Informationen manuell übermitteln.

Für welchen Ansatz Sie sich entscheiden, ist von der Art der Authentifizierung abhängig, die von der Website verwendet wird.

  • Wenn die Website die Standard- oder Digestauthentifizierung nutzt, können Sie einen Benutzernamen und ein Kennwort übermitteln, indem Sie ein neues System.Net.NetworkCredential-Objekt erstellen und der Eigenschaft WebRequest.Credentials zuweisen. Im Falle der Digestauthentifizierung müssen Sie möglicherweise ebenfalls einen Domänennamen angeben.

  • Wenn die Website die integrierte Windows-Authentifizierung benutzt, können Sie ebenfalls ein neues System.Net.NetworkCredential-Objekt erstellen. Alternativ hierzu können Sie die Anmeldeinformationen des aktuellen Benutzers vom System.Net.CredentialCache-Objekt abrufen.

  • Wenn die Website ein Client-Zertifikat verlangt, können Sie dieses Zertifikat aus einer Datei laden. Dazu verwenden Sie die Klassen System.Security.Cryptography.X509Certificates.X509Certificate. Das Zertifikat fügen Sie der Auflistung HttpWebRequest.ClientCertificates hinzu.

Das folgende Codebeispiel demonstriert alle drei Verfahren:

using System;
using System.Net;
using System.Security.Cryptography.X509Certificates;

public class DownloadWithAuthentication {

    private static void Main() {

        string uriBasic, uriIntegrated, uriCertificate;
        // (Diese drei URIs zuweisen.)

        // Den Benutzer über die Standardauthentifizierung mit einem Benutzernamen
        // und einem Kennwort authentifizieren.
        WebRequest requestA = WebRequest.Create(uriBasic);
        requestA.Credentials = CredentialCache.DefaultCredentials;
        requestA.PreAuthenticate = true;

        // Aktuellen Benutzer mit der integrierten Windows-Authentifizierung anmelden.
        WebRequest requestB = WebRequest.Create(uriIntegrated);
        requestB.Credentials = new NetworkCredential("userName", "password");
        requestB.PreAuthenticate = true;

        // Benutzer mit einem Client-Zertifikat authentifzieren.
        HttpWebRequest requestC = (HttpWebRequest)
          WebRequest.Create(uriCertificate);
        X509Certificate cert =
          X509Certificate.CreateFromCertFile(@"c:\user.cer");
        requestC.ClientCertificates.Add(cert);

        // (Antwort abrufen und verarbeiten.)
    }
}

HINWEIS:
Wenn Sie die Zertifikatauthentifizierung verwenden möchten, sollten Sie daran denken, dass Sie das Zertifikat aus einer Datei laden müssen. Es gibt keine Möglichkeit, ein X509Certificate-Objekt aus einem Zertifikat zu erstellen, das im Zertifikatspeicher des Computers abgelegt ist.

Eine Webseite in einer Windows-Anwendung anzeigen

Aufgabe
Sie möchten eine HTML-Seite (oder ein anderes vom Microsoft Internet Explorer unterstütztes Dokument) in einer Windows-Anwendung anzeigen lassen.

Lösung
Benutzen Sie das ActiveX-Steuerelement Webbrowser, das ein Bestandteil des Internet Explorers ist. Verwenden Sie dazu einen RCW (Runtime Callable Wrapper; einen Wrapper, der von der Laufzeit aufgerufen werden kann).

Beschreibung

Das .NET Framework enthält keine Steuerelemente für die Ausgabe von HTML-Inhalten. Eine solche Funktionalität ist jedoch oft erforderlich, um beispielsweise lokale HTML-Inhalte (wie z.B. Hilfedokumente) oder Informationen aus dem Web anzuzeigen (zum Beispiel eine Webseite, die Download-Komponenten auflistet, die von einem Benutzer zur Aktualisierung einer Anwendung benutzt werden können).

Um eine HTML-Seite anzeigen zu lassen, können Sie Ihrer Windows-Anwendung ein Internet Explorer-Fenster hinzufügen. Ein solches Fenster unterstützt nicht nur HTML, sondern auch JavaScript- und VBScript-Code (Microsoft Visual Basic Scripting Edition), ActiveX-Steuerelemente und abhängig von Ihrer Systemkonfiguration (Microsoft Word, Microsoft Excel und Adobe Acrobat Reader) verschiedene Plug-Ins. Sie können das Webbrowser-Steuerelement sogar benutzen, um durch die Ordner eines lokalen Laufwerks zu navigieren oder die Dateien einer FTP-Site (File Transfer Protocol) anzeigen zu lassen.

Um in Microsoft Visual Studio .NET einem Projekt das Webbrowser-Steuerelement hinzuzufügen, wählen Sie im Menü Projekt den Befehl Verweis hinzufügen und öffnen im anschließend angezeigten Dialogfeld die Registerkarte COM. Wählen Sie das Microsoft Webbrowser-Steuerelement (shdocvw.dll) aus. Das Steuerelement wird daraufhin Ihrer Toolbox hinzugefügt. Wenn Sie dieses Steuerelemente auf ein Formular ziehen, werden die erforderlichen Interop-Assemblies automatisch generiert und Ihrem Projekt hinzugefügt. Wenn Sie Visual Studio .NET nicht verwenden, können Sie mit dem Hilfsmittel zum Importieren von Typbibliotheken (Tlbimp.exe) einen Wrapper erzeugen.

Wenn Sie das Webbrowser-Steuerelement einsetzen, verwenden Sie in der Regel die folgenden Methoden:

  • Navigate (und Navigate2) leitet die Seite zu dem von Ihnen angegebenen URL um.

  • GoBack und GoForward wechseln zu den entsprechenden Seiten in der Verlaufsliste.

  • GoHome wechselt zu der Homepage, die auf dem aktuellen Computer eingerichtet ist, und GoSearch zeigt die Suchseite an.

Der Benutzer kann außerdem Hyperlinks (sofern vorhanden) anklicken, um zu anderen Seiten zu gelangen. Sie können den aktuellen URL von der Eigenschaft LocationURL abrufen und ermitteln, ob das Steuerelement eine Seite noch ausgibt, indem Sie die Eigenschaft Busy untersuchen. Sie können außerdem auf verschiedene Ereignisse reagieren. Dazu zählen auch die Ereignisse, die ausgelöst werden, wenn der Benutzer mit der Navigation beginnt oder diese beendet.

Abbildung 11.2 zeigt einer Anwendung, die es dem Benutzer ermöglicht, zwei Seiten zu besuchen (und den Links zu folgen, die von diesen Seiten angeboten werden).

Das Webbrowser-Steuerelement verwenden
Abbildung 11.2: Das Webbrowser-Steuerelement verwenden

using System;
using System.Drawing;
using System.Windows.Forms;

public class WebBrowser : System.Windows.Forms.Form
{
    private AxSHDocVw.AxWebBrowser explorer;
    private System.Windows.Forms.Button cmdBack;
    private System.Windows.Forms.Button cmdHome;
    private System.Windows.Forms.Button cmdForward;

    // (Der Designer-Code ist nicht aufgeführt.)

    private void WebBrowser_Load(object sender, System.EventArgs e)
    {
        object nullObject = null;
        object uri = "http://www.microsoft.com";
        explorer.Navigate2(ref uri, ref nullObject, ref nullObject, ref nullObject, ref nullObject);
    }

 
    private void cmdHome_Click(object sender, System.EventArgs e)
    {
        explorer.GoHome();
    }

    private void cmdForward_Click(object sender, System.EventArgs e)
    {
        try
        {
            explorer.GoForward();
        }
        catch
        {
            MessageBox.Show("Letzte Seite wird bereits angezeigt.");
        }
    }

    private void cmdBack_Click(object sender, System.EventArgs e)
    {
        try
        {
            explorer.GoBack();
        }
        catch
        {
            MessageBox.Show("Erste Seite wird bereits angezeigt.");
        }
    }
}

Die meisten Methoden des Webbrowser-Steuerelements verlangen die Angabe mehrerer Parameter. Da diese Methoden nicht überladen sind und C# keine optionalen Parameter unterstützt, müssen Sie einen Wert für jeden Parameter angeben. Sie können jedoch nicht einfach eine null-Referenz benutzen, da es sich um ref-Parameter handelt. Es ist in der Regel einfacher, eine Objektvariable zu erstellen, die eine null-Referenz enthält. Diese Variable kann dann für die Parameter angegeben werden, die Sie nicht benötigen.

Die IP-Adresse des aktuellen Computers ermitteln

Aufgabe
Sie möchten die IP-Adresse des aktuellen Computers abrufen, um diese zum Beispiel später in Ihrem Netzwerkcode zu verwenden.

Lösung
Benutzen Sie die Klasse System.Net.Dns, die die statischen Methoden GetHostName und GetHostByName zur Verfügung stellt.

Beschreibung

Mit der Klasse Dns können Sie Domänennamen auflösen. Sie rufen die GetHostName-Methode dieser Klasse auf, um den Host-Namen des aktuellen Computers abzurufen. Danach können Sie diesen Host-Namen mit GetHostByName in eine IP-Adresse umwandeln. Dazu ein Beispiel:

using System;
using System.Net;

public class GetIPAddress
{
    private static void Main()
    {
        // Hostnamen und IP-Adresse abrufen.
        string hostName = Dns.GetHostName();

        // Die erste passende IP-Adresse abrufen.
        string ipAddress = Dns.GetHostByName(hostName).AddressList[0].ToString();

        Console.WriteLine("Hostname: " + hostName);
        Console.WriteLine("IP-Adresse: " + ipAddress);

        Console.ReadLine();
    }
}

Beachten Sie, dass die Methode GetHostByName eine Liste verwendbarer IP-Adressen zurückgibt. In den meisten Fällen enthält diese Adressliste lediglich einen Eintrag.

Wenn Sie diesen Code ausführen, erhalten Sie eine ähnliche Ausgabe wie die folgende:

Hostname:notebook
IP-Adresse:192.168.1.100

HINWEIS:
Wenn Sie in der Netzwerkkommunikation IP-Adressen verwenden, können Sie immer die Rückschleifenadresse 127.0.0.1 verwenden, um auf den aktuellen Computer zu verweisen. Sie müssen somit nicht die für den Computer spezifische IP-Adresse benutzen.

Eine IP-Adresse anhand des Host-Namens ermitteln

Aufgabe
Sie möchten mit einer DNS-Abfrage (Domain Name System) die IP-Adresse eines Computers über dessen Domänennamen ermitteln.

Lösung
Benutzen Sie die Klasse System.Net.Dns, die diese Funktionalität über die Methode GetHostByName zur Verfügung stellt. Übergeben Sie den Domänennamen als String-Parameter.

Beschreibung

Im Web werden öffentlich zugängliche IP-Adressen oft Host-Namen zugeordnet, die einprägsamer sind. Die IP-Adresse 207.171.185.16 könnte beispielsweise dem Domänennamen www.amazon.com zugeordnet sein. Um die IP-Adresse zu einem Namen zu ermitteln, kontaktiert der Computer einen DNS-Server.

Dieser gesamte Vorgang bleibt vor Ihnen verborgen, wenn Sie die Klasse System.Net.Dns verwenden. Mit dieser Klasse können Sie die IP-Adresse zu einem Host-Namen ermitteln, indem Sie GetHostByName aufrufen. Das folgende Beispiel zeigt, wie Sie die Liste der IP-Adressen abrufen können, die www.microsoft.com zugeordnet sind:

using System;
using System.Net;

public class ResolveIP
{
    private static void Main()
    {
        foreach (IPAddress ip in Dns.GetHostByName("www.microsoft.com").AddressList)
        {
            Console.Write(ip.AddressFamily.ToString() + ": ");
            Console.WriteLine(ip.ToString());
        }
        Console.ReadLine();
    }
}

Dieses Programm generiert eine ähnliche Ausgabe wie die folgende:

InterNetwork: 207.46.144.222
InterNetwork: 207.46.156.220
InterNetwork: 207.46.156.252
InterNetwork: 207.46.245.92
InterNetwork: 207.46.249.252
InterNetwork: 207.46.250.222
InterNetwork: 207.46.250.252
InterNetwork: 207.46.144.188

Ein Ping-Signal an eine IP-Adresse senden

Aufgabe
Sie möchten überprüfen, ob ein Computer online ist und wie schnell dieser Computer antwortet.

Lösung
Senden Sie eine Ping-Nachricht. Diese Nachricht wird mithilfe des ICMP-Protokolls (Internet Control Message Protocol) über einen Raw-Socket gesendet.

Beschreibung

Eine Ping-Nachricht kontaktiert ein Gerät mit der angegebenen IP-Adresse und sendet eine Testnachricht sowie eine Anforderung, die das entfernte Gerät dazu veranlasst, das erhaltene Paket wieder zurückzusenden. Sie können die Zeit bis zum Erhalt der Ping-Antwort messen, um die Latenzzeit für die Verbindung zwischen zwei Computern zu ermitteln.

Verglichen mit anderen Arten der Netzwerkkommunikation ist eine Ping-Nachricht sehr einfach strukturiert. Die Implementierung eines Ping-Hilfsmittels in .NET erfordert jedoch nicht gerade wenig komplexen Netzwerk-Systemcode. Die .NET-Klassenbibliothek bietet keine vorgefertigte Lösung an. Sie müssen stattdessen direkt mit so genannten Raw-Sockets (Sockets, die die Standardprotokolle umgehen) arbeiten, was zu sehr vielen Codezeilen führen kann.

Es gibt aber einen Entwickler, der das Ping-Problem gelöst hat. Lance Olson, ein Microsoft-Entwickler, stellt C#-Code zur Verfügung, mit dem Sie einem Host über dessen Namen oder IP-Adresse ein Ping-Signal senden und die Antwortzeit in Millisekunden messen können. Dieser Code wurde für die nachfolgend aufgeführte Klasse Pinger adaptiert. (Einige Details und der Fehlerbehandlungscode sind dort jedoch nicht aufgeführt.) Der vollständige Code befindet sich in den Beispieldateien zu diesem Buch.

using System;
using System.Net;
using System.Net.Sockets;

public class Pinger {

    public static int GetPingTime(string host) {
     
           int dwStart = 0, dwStop = 0;

        // Einen Raw-Socket erstellen.
        Socket socket = new Socket(AddressFamily.InterNetwork, 
          SocketType.Raw, ProtocolType.Icmp);

        // Den IPEndPoint des Servers in einen EndPoint konvertieren.
        IPHostEntry serverHE = Dns.GetHostByName(host);
        IPEndPoint ipepServer = new IPEndPoint(serverHE.AddressList[0], 0);
        EndPoint epServer = (ipepServer);

        // Den empfangenen Endpunkt für den Client-Rechner setzen.
        IPHostEntry fromHE = Dns.GetHostByName(Dns.GetHostName());
        IPEndPoint ipEndPointFrom = new IPEndPoint(fromHE.AddressList[0], 0);        
        EndPoint EndPointFrom = (ipEndPointFrom);

        // Das zu sendende Paket erstellen.
        int PacketSize = 0;
        IcmpPacket packet = new IcmpPacket();
        for (int j = 0; j < 1; j++) {
            packet.Type = ICMP_ECHO;
            packet.SubCode = 0;
            packet.CheckSum = UInt16.Parse("0");
            packet.Identifier   = UInt16.Parse("45"); 
            packet.SequenceNumber  = UInt16.Parse("0"); 

            int PingData = 32; 
            packet.Data = new Byte[PingData];

            for (int i = 0; i < PingData; i++)
                packet.Data[i] = (byte)'#';

            PacketSize = PingData + 8;

            Byte [] icmp_pkt_buffer = new Byte [PacketSize]; 
            int index = 0;          
            index = Serialize(packet, icmp_pkt_buffer, PacketSize, PingData);

            // Die Prüfsumme für das Paket berechnen.
            double double_length = Convert.ToDouble(index);
            double dtemp = Math.Ceiling(double_length / 2);
            int cksum_buffer_length = Convert.ToInt32(dtemp);
            UInt16[] cksum_buffer = new UInt16[cksum_buffer_length];
            int icmp_header_buffer_index = 0;

            for (int i = 0; i < cksum_buffer_length; i++) {
                cksum_buffer[i] = BitConverter.ToUInt16(icmp_pkt_buffer, 
                  icmp_header_buffer_index);
                icmp_header_buffer_index += 2;
            }

            UInt16 u_cksum = checksum(cksum_buffer, cksum_buffer_length);
            packet.CheckSum  = u_cksum; 
            
            // Nachdem nun die Prüfsumme vorhanden ist, das Paket erneut serialisieren.
            byte[] sendbuf = new byte[PacketSize]; 
            index = Serialize(packet, sendbuf, PacketSize, PingData);

            // Stoppen der Zeit starten.
            dwStart = System.Environment.TickCount; 
            socket.SendTo(sendbuf, PacketSize, 0, epServer);

            // Antwort empfangen und das Stoppen der Zeit beenden.
            byte[] ReceiveBuffer = new byte[256]; 
            socket.ReceiveFrom(ReceiveBuffer, 256, 0, ref EndPointFrom);
            dwStop = System.Environment.TickCount - dwStart;
        }

        // Bereinigung und Rückgabe der berechneten Ping-Zeit in Sekunden.
        socket.Close();     
        return (int)dwStop;
    }

    private static int Serialize(IcmpPacket packet, byte[] buffer,
      int packetSize, int pingData) {

        // (Private Methode zur Serialisierung des Pakets ist nicht aufgeführt.)
    }

    private static UInt16 checksum(UInt16[] buffer, int size) {

        // (Private Methode zur Berechnung der Prüfsumme ist nicht aufgeführt.)
    } 
}

public class IcmpPacket { 
    public byte Type; // Nachrichtentyp
    public byte SubCode; // Subcodetyp
    public UInt16 CheckSum; // Einerkomplement-Prüfsumme für Struktur 
    public UInt16 Identifier; // Bezeichner
    public UInt16 SequenceNumber; // Sequenznummer
    public byte[] Data;
}

Sie können die statische Methode Pinger.GetPingTime mit einer IP-Adresse oder einem Domänennamen aufrufen. Diese Methode gibt die Millisekunden zurück, die bis zum Eingang einer Antwort vergangen sind. Nachfolgend ist der Code aufgeführt, der drei Websites testet:

public class PingTest
{
    private static void Main()
    {
        Console.WriteLine("Millisekunden für den Kontakt zu www.yahoo.com: " +
            Pinger.GetPingTime("www.yahoo.com").ToString());
        Console.WriteLine("Millisekunden für den Kontakt zu  www.seti.org: " +
            Pinger.GetPingTime("www.seti.org").ToString());
        Console.WriteLine("Millisekunden für den Kontakt zum lokalen Computer: " +
            Pinger.GetPingTime("127.0.0.1").ToString());

        Console.ReadLine();
    }
}

Der Ping-Test ermöglicht es Ihnen, zu überprüfen, ob andere Computer online sind. Er kann ebenfalls nützlich sein, wenn Ihre Anwendung mehrere verschiedene Remote-Computer mit denselben Inhalten auswerten muss, um zu ermitteln, welcher dieser Computer die niedrigste Latenzzeit für die Netzwerkkommunikation bietet.

HINWEIS:
Ein Ping-Versuch verläuft nicht erfolgreich, wenn eine Firewall dies verhindert. Viele stark frequentierte Sites ignorieren beispielsweise Ping-Anforderungen, um sich vor einer Flut gleichzeitiger Ping-Versuch zu schützen, die den Server lahm legen würden (so genannte DoS-Angriffe; engl. für Denial Of Service).

Kommunizieren mit TCP

Aufgabe
Sie möchten in einem Netzwerk Daten zwischen zwei Computern übermitteln und dazu eine TCP/IP-Verbindung verwenden.

Lösung
Ein Computer (der Server) muss mithilfe der Klasse System.Net.Sockets.TcpListener auf eine eingehende Kommunikationsanforderung warten. Sobald eine Verbindung hergestellt wurde, können beide Computer über die Klasse System.Net.Sockets.TcpListener kommunizieren.

Beschreibung

TCP ist ein bewährtes Verbindungsprotokoll, das es zwei Computern ermöglicht, über ein Netzwerk miteinander zu kommunizieren. Es bietet eine integrierte Datenflusssteuerung, Sequencing und die Möglichkeit der Fehlerbehandlung. TCP ist deshalb ein äußerst verlässliches Protokoll, das einfach zu programmieren ist.

Um eine TCP-Verbindung zu erstellen, muss ein Computer als Server agieren und einen bestimmten Endpunkt abhören. (Ein Endpunkt ist eine IP-Adresse, die einen Computer und eine Anschlussnummer identifiziert.) Der andere Computer muss als Client agieren und eine Verbindungsanforderung zu dem Endpunkt senden, wo der erste Computer wartet. Sobald die Verbindung hergestellt wurde, können die beiden Computer Nachrichten austauschen. .NET vereinfacht diesen Vorgang mithilfe der so genannten Stream-Abstraktion. Beide Computer schreiben und lesen einfach in einen und aus einem Netzwerk-Stream (System.Net.Sockets.NetworkStream), um Daten zu übermitteln.

HINWEIS:
Obwohl für eine TCP-Verbindung immer ein Server und ein Client erforderlich sind, spricht nichts dagegen, dass eine einzelne Anwendung beide Rollen übernimmt. In einer Peer-To-Peer-Anwendung kann ein Thread beispielsweise eingehende Anforderungen abhören (als Server agieren), während ein anderer Thread ausgehende Verbindungsanforderung sendet (als Client agiert). In den Beispielen dieses Kapitels bilden der Client und der Server gesonderte Anwendungen, die sich in separaten Unterverzeichnissen befinden. Lesen Sie die Datei liesmich.htm, die sich im Beispielcode dieses Buchs befindet, um weitere Informationen zu erhalten.

Wenn eine TCP-Verbindung hergestellt wurde, können beide Computer beliebige Daten senden, indem sie diese in ein NetworkStream-Objekt schreiben. Es ist jedoch empfehlenswert, die Entwicklung einer Netzwerkanwendung damit zu beginnen, das in der gesamten Anwendung gültige Protokoll zu definieren, nach dem sich Client und Server richten, um miteinander zu kommunizieren. Dieses Protokoll enthält Konstanten, die die zulässigen Befehle repräsentieren. Dies gewährleistet, dass Ihr Anwendungscode die Kommunikationszeichenfolgen nicht direkt kodieren muss.

namespace SharedComponent
{
    public class ServerMessages
    {
        public const string AcknowledgeOK = "OK";
        public const string AcknowledgeCancel = "Cancel";
        public const string Disconnect = "Bye";
    }

    public class ClientMessages
    {
        public const string RequestConnect = "Hello";
        public const string Disconnect = "Bye";
    }
}

Das Vokabular dieses Beispiels ist sehr einfach. Abhängig vom Typ Ihrer Anwendung können Sie natürlich weitere Konstanten hinzufügen. In einer FTP-Anwendung könnten Sie beispielsweise zusätzlich eine Client-Nachricht zur Anforderung einer Datei verwenden. Der Server könnte dann mit einer Bestätigung reagieren und Dateidetails zurückgeben, wie zum Beispiel die Dateigröße. Diese Konstanten müssen in eine separate Klassenbibliothek-Assembly kompiliert werden, die wiederum sowohl vom Client als auch vom Server referenziert werden muss.

Der folgende Code ist eine Vorlage für einen einfachen TCP-Server. Er hört den angegebenen Anschluss ab, nimmt die erste eingehende Verbindungsanforderung entgegen und wartet anschließend darauf, dass der Client eine Verbindungstrennung anfordert. An diesem Punkt könnte der Server erneut die Methode TcpListener.AcceptTcpClient aufrufen, um auf den nächsten Client zu warten. Stattdessen wird die Ausführung einfach beendet.

using System;
using System.Net;
using System.Net.Sockets;
using System.IO;
using SharedComponent;


public class TcpServerTest
{
    private static void Main()
    {
        // Erstellen eines neuen Listeners (Abhörer) an Anschluss 8000
        TcpListener listener = new TcpListener(IPAddress.Parse("127.0.0.1"), 
            8000);
        
        Console.WriteLine("Initialisiere Anschluss.");
        listener.Start();
        Console.WriteLine("Warte auf eingehende Verbindungsanforderungen...");
        
        try
        {
            // Auf eingehende Verbindungsanforderung warten 
            // und einen für die Kommunikation initalisierten TcpClient zurückgeben. 
            TcpClient client = listener.AcceptTcpClient();
            Console.WriteLine("Connection accepted.");
            
            // Den Netzwerk-Stream abrufen.
            NetworkStream stream = client.GetStream();
            
            // Einen BinaryWriter erstellen, um in den Stream zu schreiben.
            BinaryWriter w = new BinaryWriter(stream);
            
            // Einen BinaryReader erstellen, um aus dem Stream zu lesen.
            BinaryReader r = new BinaryReader(stream);
            
            if (r.ReadString() == ClientMessages.RequestConnect)
            {
                w.Write(ServerMessages.AcknowledgeOK);
                Console.WriteLine("Verbindung beendet.");
                
                while (r.ReadString() != ClientMessages.Disconnect)
                {}
                
                Console.WriteLine();
                Console.WriteLine("Verbindungstrennungsanforderung empfangen.");
                w.Write(ServerMessages.Disconnect);
            }
            else
            {
                Console.WriteLine("Verbindung konnte nicht beendet werden.");
            }
            
            // Den Verbindungs-Socket schließen.
            client.Close();
            Console.WriteLine("Connection closed.");
            
            // Den zugrundeliegenden Socket schließen (das Abhören neuer Anforderungen stoppen).
            listener.Stop();
            Console.WriteLine("Listener gestoppt.");
        }
        catch (Exception err)
        {
            Console.WriteLine(err.ToString());
        }
    
        Console.ReadLine();
    }
}

Der folgende Code ist eine Vorlage für einen einfachen TCP-Client. Er kontaktiert den Server, der sich an der angegebenen IP-Adresse und dem angegebenen Anschluss befindet. In diesem Beispiel wird die Rückschleifenadresse (127.0.0.1) verwendet, die immer auf den aktuellen Computer verweist. Denken Sie daran, dass eine TCP-Verbindung zwei Anschlüsse erfordert: einen auf der Serverseite und einen auf der Clientseite. Doch nur der Serveranschluss muss angegeben werden. Der Clientanschluss kann zur Laufzeit dynamisch aus der Liste der verfügbaren Anschlüsse gewählt werden, was die Klasse TcpClient standardmäßig tut.

using System;
using System.Net;
using System.Net.Sockets;
using System.IO;
using SharedComponent;

public class TcpClientTest
{
    private static void Main()
    {
        TcpClient client = new TcpClient();
        
        try
        {
            Console.WriteLine("Verbindungsversuch mit Server " +
                "über Anschluss 8000.");
            client.Connect(IPAddress.Parse("127.0.0.1"), 8000);
            Console.WriteLine("Verbindung hergestellt.");
            
            // Den Netzwerk-Stream abrufen.
            NetworkStream stream = client.GetStream();
            
            // Einen BinaryWriter erstellen, um in den Stream zu schreiben.
            BinaryWriter w = new BinaryWriter(stream);
            
            // Einen BinaryReader erstellen, um aus dem Stream zu lesen.
            BinaryReader r = new BinaryReader(stream);
            
            // Einen Dialog starten.
            w.Write(ClientMessages.RequestConnect);
            
            if (r.ReadString() == ServerMessages.AcknowledgeOK)
            {
                Console.WriteLine("Verbunden.");
                Console.WriteLine("Enter drücken, um Verbindung zu trennen.");
                Console.ReadLine();
                Console.WriteLine("Trenne Verbindung...");
                w.Write(ClientMessages.Disconnect);
            }
            else
            {
                Console.WriteLine("Verbindung nicht beendet.");
            }

            // Den Verbindungs-Socket schließen.
            client.Close();
            Console.WriteLine("Anschluss geschlossen.");
        }
        catch (Exception err)
        {
            Console.WriteLine(err.ToString());
        }
    
        Console.ReadLine();

    }
}

Nachfolgend ist das Verbindungsprotokoll des Servers aufgeführt:

Initialisiere Anschluss.
Warte auf eingehende Verbindungsanforderungen...
Verbindung akzeptiert.
Verbindung beendet.

Verbindungstrennungsanforderung empfangen.
Verbindung geschlossen.
Listener gestoppt.

Und hier ist das Verbindungsprotokoll des Clients:

Verbindungsversuch mit Server über Anschluss 8000.
Verbindung hergestellt.
Verbunden.
Enter drücken, um Verbindung zu trennen.

Trenne Verbindung...
Anschluss geschlossen.

Anzeigen: