C# Tipps, Teil 2 - Netzwerk und Internet

Veröffentlicht: 28. 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

 Die IP-Adresse eines Clients über eine Socket-Verbindung abrufen
 Socket-Optionen setzen
 Einen Multithreading-TCP-Netzwerkserver erstellen
 TCP asynchron verwenden
 Über UDP kommunizieren
 Eine Email über SMTP versenden
 Emails mit MAPI senden und empfangen

Die IP-Adresse eines Clients über eine Socket-Verbindung abrufen

Aufgabe
Die Serveranwendung möchte die IP-Adresse des Clients ermitteln, nachdem eine Verbindung hergestellt wurde.

Lösung
Benutzen Sie die AcceptSocket-Methode der Klasse TcpListener, um ein der Systemebene nahes System.Net.Sockets.Socket-Objekt anstelle eines TcpClient-Objekts abzurufen. Benutzen Sie die Eigenschaft Socket.RemoteEndPoint, um die IP-Adresse des Clients auszulesen.

Beschreibung

Mit der Klasse TcpClient können Sie nicht den zu Grunde liegenden Socket und auch keine Informationen über den Anschluss und die IP-Adresse des Clients abrufen. Das TcpClient-Objekt stellt eine Socket-Eigenschaft zur Verfügung, doch diese Eigenschaft ist geschützt (protected) und deshalb für Klassen, die nicht von TcpClient abgeleitet sind, nicht verfügbar. Um auf den zu Grunde liegenden Socket zuzugreifen, haben Sie zwei Möglichkeiten.

  • Erstellen Sie eine benutzerdefinierte Klasse, die von TcpClient abgeleitet ist. Diese Klasse kann auf die geschützte Socket-Eigenschaft zugreifen und diese über eine neue Eigenschaft offen legen. Sie müssen dann diese benutzerdefinierte Klasse anstelle von TcpClient verwenden.

  • Umgehen Sie die Klasse TcpClient, in dem Sie die Methode TcpListener.AcceptSocket verwenden. Sie können dann auch weiterhin die höheren Klassen BinaryWriter und BinaryReader benutzen, um Daten zu schreiben und zu lesen, doch Sie müssen zuerst das NetworkStream-Objekt unter Verwendung des Sockets erstellen.

Dieses Rezept nutzt das zweite Verfahren. Nachfolgend ist eine überarbeitete Version des Servercodes aus " Kommunizieren mit TCP" aufgeführt.

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 Socket zurückgeben. 
            Socket socket = listener.AcceptSocket();

            Console.WriteLine("Verbindung akzeptiert.");
            
            // Den Netzwerk-Stream erstellen.
            NetworkStream stream = new NetworkStream(socket);
            
            // 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.");
                
                // Die Client-IP abrufen.
                Console.Write("Die IP-Adresse des Clients lautet: ");
                Console.WriteLine(((IPEndPoint)socket.RemoteEndPoint).Address.ToString());
                Console.Write("Der Client benutzt den lokalen Anschluss: ");
                Console.WriteLine(((IPEndPoint)socket.RemoteEndPoint).Port.ToString());

                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.
            socket.Close();
            Console.WriteLine("Verbindung geschlossen.");
            
            // Den zu Grunde liegenden Socket schließen (das Warten auf neue Anforderungen beenden).
            listener.Stop();
            Console.WriteLine("Listener gestoppt.");
        }
        catch (Exception err)
        {
            Console.WriteLine(err.ToString());
        }
        Console.ReadLine();
    }
}

 

Socket-Optionen setzen

Aufgabe
Sie möchten Optionen für Sockets auf der Systemebene setzen, um beispielsweise Timeout-Werte für den Datenversand und den Datenempfang festzulegen.

Lösung
Benutzen Sie die Methode Socket.SetSocketOption. Sie können die Eigenschaften eines Sockets setzen, der auf eingehende Anforderungen wartet, oder die eines Sockets, der für eine spezifische Clientsitzung verwendet wird.

Beschreibung

Sie können die Methode Socket.SetSocketOption verwenden, um einige Eigenschaften von Sockets auf der Systemebene zu setzen. Wenn Sie diese Methode aufrufen, übergeben Sie die folgenden drei Parameter:

  • Einen Wert aus der Enumeration SocketOptionLevel, der den Typ des Sockets angibt, auf den die Einstellung angewandt werden soll (z.B. TCP, UDP usw.).

  • Einen Wert aus der Enumeration SocketOptionName, der die Socket-Einstellung angibt, die Sie ändern möchten. Die .NET-Framework-Dokumentation enthält eine vollständige Liste der SocketOptionName-Werte.

  • Einen Wert, der die neue Einstellung repräsentiert. Dies ist gewöhnlich eine Ganzzahl, es kann sich jedoch ebenfalls um ein Byte-Array oder einen Objekttyp handeln.

Nachfolgend ist ein Beispiel aufgeführt, das den Timeout eines Sockets festlegt, der zum Senden von Daten verwendet wird:

// Der Timout der Übermittlung wird erreicht, wenn die Bestätigung nicht innerhalb von 1000 Millisekunden eingeht..
socket.SetSocketOption(SocketOptionLevel.Socket,
  SocketOptionName.SendTimeout, 1000);

Um auf einen Socket zugreifen zu können, der eine Client-/Serververbindung repräsentiert, müssen Sie, wie im vorigen Kapitel beschrieben, die TcpListener.AcceptSocket-Methode anstelle von TcpListener.AcceptTcpClient verwenden.

Sie können ebenfalls die Socket-Optionen eines Sockets setzen, der von TcpListener verwendet wird, um die eingehenden Verbindungsanforderung zu überwachen. Dafür sind jedoch einige zusätzliche Schritte erforderlich. TcpListener stellt eine Socket-Eigenschaft zur Verfügung, die jedoch geschützt ist, sodass ein direkter Zugriff darauf nicht möglich ist. Sie müssen stattdessen wie folgt eine neue Klasse von TcpListener ableiten.

public class CustomTcpListener : TcpListener {

    public Socket Socket {
        get {return base.Server;}
    }

    public CustomTcpListener(IPAddress ip, int port) : base(ip, port) {}
}

Sie können nun diese Klasse verwenden, wenn Sie ein TcpListener-Objekt erstellen. Hier ist ein Beispiel, das dieses Verfahren verwendet, um eine Socket-Option zu setzen:

CustomTcpListener listener = new CustomTcpListener(IPAddress.Parse(
    "127.0.0.1"), 8000);
listener.Socket.SetSocketOption(SocketOptionLevel.Socket,
  SocketOptionName.ReceiveTimeout, 1000);

// (Nun kann CustomTcpListener in derselben Weise wie TcpListener verwendet werden.)

 

Einen Multithreading-TCP-Netzwerkserver erstellen

Aufgabe
Sie möchten einen TCP-Server erstellen, der mehrere TCP-Clients gleichzeitig bearbeiten kann.

Lösung
Benutzen Sie die Methode AcceptTcpClient der Klasse TcpListener. Starten Sie immer dann, wenn ein Client eine Verbindung herstellen möchte, einen neuen Thread, um die Anforderung zu bearbeiten. Rufen Sie in diesem Fall außerdem TcpListener.AcceptTcpClient erneut auf.

Beschreibung

Ein einzelner TCP-Endpunkt (IP-Adresse und Anschluss) kann mehrere Verbindungen gleichzeitig bedienen. Das Betriebssystem übernimmt dabei den größten Teil der Arbeit für Sie. Sie müssen lediglich auf dem Server ein Worker-Objekt starten, das die Verbindung in einem neuen Thread bearbeitet.

Betrachten Sie beispielsweise die Klassen für den TCP-Client und -Server, die in "Kommunizieren mit TCP" vorgestellt wurden. Sie können diesen Server einfach in einen Multithreading-Server konvertieren, der mehrere gleichzeitige Verbindungen unterstützt. Erstellen Sie zunächst eine Klasse, die mit einem einzelnen Client interagiert. Der folgende Code zeigt diese Klasse:

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

public class ClientHandler 
{

    private TcpClient client;
    private string ID;

    public ClientHandler(TcpClient client, string ID) 
    {

        this.client = client;
        this.ID = ID;
    }

    public void Start() 
    {

        // 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(ID + ": Verbindung beendet.");
            while (r.ReadString() != ClientMessages.Disconnect) {}

            Console.WriteLine(ID + ": Verbindungstrennungsanforderung empfangen.");
            w.Write(ServerMessages.Disconnect);
        }
        else 
        {
            Console.WriteLine(ID + ": Verbindung konnte nicht beendet werden.");
        }

        // Den Verbindungs-Socket schließen.
        client.Close();
        Console.WriteLine(ID + ": Verbindung geschlossen.");

        Console.ReadLine();
    }
}

Modifizieren Sie anschließend den Servercode, sodass dieser eine Endlosschleife ausführt und neue ClientHandler-Instanzen erstellt, wenn dies notwendig ist. Diese Instanzen werden dann in neuen Threads ausgeführt. Hier ist der überarbeitete Code:

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("Server: Initialisiere Anschluss.");
        listener.Start();
        Console.WriteLine("Server: Warte auf eingehende Verbindungsanforderungen...");
        
        int clientNum = 0;
        while (true)
        {
            try 
            {
                // Auf eingehende Verbindungsanforderung warten 
                // und einen für die Kommunikation initalisierten TcpClient zurückgeben. 

                TcpClient client = listener.AcceptTcpClient();
                Console.WriteLine("Server: Verbindung akzeptiert.");

                // Ein neues Objekt erstellen, um diese Verbindung zu bearbeiten.
                clientNum++;
                ClientHandler handler = new ClientHandler(client, "Client " +
                    clientNum.ToString());

                // Dieses Objekt in einem anderen Thread ausführen.
                Thread handlerThread = 
                    new Thread(new ThreadStart(handler.Start));
                handlerThread.IsBackground = true;
                handlerThread.Start();

                // (Sie könnten Handler und HandlerThread auch zu einer Auflistung hinzufügen,
                // um die Client-Sitzungen zu überwachen.)
            }
            catch (Exception err) 
            {
                Console.WriteLine(err.ToString());
            }
        }
    }
}

Nachfolgend ist das serverseitige Protokoll für eine Sitzung mit zwei Clients aufgeführt:

Server: Initialisiere Anschluss.
Server: Warte auf eingehende Verbindungsanforderungen...
Server: Verbindung akzeptiert.
Client 1: Verbindung beendet.
Server: Verbindung akzeptiert.
Client 2: Verbindung beendet.
Client 2: Verbindungstrennungsanforderung empfangen.
Client 2: Client-Verbindung geschlossen.
Client 1: Verbindungstrennungsanforderung empfangen.
Client 1: Client-Verbindung geschlossen.

Sie können dem Netzwerkserver zusätzlichen Code hinzufügen, sodass dieser die aktuellen Worker-Objekte in einer Auflistung überwacht. Auf diese Weise könnte der Server diese Tasks abbrechen, sofern erforderlich, um möglichst viele gleichzeitige Clientverbindungen zu erzielen.

 

TCP asynchron verwenden

Aufgabe
Sie möchten Daten in Form von Segmenten in den Netzwerk-Stream schreiben, ohne den Rest Ihres Codes zu blockieren. Diese Technik kann verwendet werden, wenn Sie eine große Datei im Netzwerk übermitteln möchten.

Lösung
Erstellen Sie eine gesonderte Klasse, die für das asynchrone Streaming der Daten sorgt. Sie können das Streaming mit einem Datenblock beginnen, indem Sie die Methode NetworkStream.BeginWrite verwenden und eine Rückrufmethode zur Verfügung stellen. Sobald der Rückruf ausgelöst wird, senden Sie den nächsten Datenblock.

Beschreibung

Das NetworkStream-Objekt kann über die Methoden BeginRead und BeginWrite asynchron genutzt werden. Sie können mit diesen Methoden einen Datenblock in einem der Threads versenden oder empfangen, die vom Thread-Pool der .NET-Laufzeit zur Verfügung gestellt werden. Ihr Code wird dann nicht blockiert. Dieses Rezept demonstriert das asynchrone Schreiben.

Wenn Sie Daten asynchron versenden, muss dies mit reinen Binärdaten (ein aus Bytes bestehendes Array) geschehen. Sie legen die Datenmenge fest, die gleichzeitig gesendet oder empfangen werden soll. Das folgende Beispiel überarbeitet den Threading-Server aus dem vorhergehenden Kapitel, sodass jede ClientHandler-Klasse eine große Datenmenge sendet, die aus einer Datei ausgelesen wird. Die Daten werden asynchron gesendet, was bedeutet, dass ClientHandler gleichzeitig andere Aufgaben ausführen kann. (In diesem Beispiel wird der Netzwerk-Stream lediglich daraufhin überprüft, ob der Client Nachrichten versendet hat.)

Ein Vorteil des in diesem Rezept verwendeten Verfahrens besteht darin, dass niemals der
gesamte Dateiinhalt im Speicher verwahrt wird. Stattdessen wird die jeweilige Datenmenge abgerufen, bevor der nächste Block gesendet wird. Ein weiterer Vorteil ist, dass der Server die Operation zu jeder Zeit abbrechen kann. In diesem Beispiel liest der Client nur ein Drittel der Daten, bevor die Verbindungstrennung erfolgt. Der Server setzt dann eine boolesche Membervariable namens fileStop, um der Rückrufmethode mitzuteilen, dass keine weiteren Daten gesendet werden sollen.

Nachfolgend ist die überarbeitete Klasse ClientHandler aufgeführt. Beachten Sie, dass NetworkStream und FileStream nun als Membervariablen geführt werden, sodass die Rückrufmethode darauf zugreifen kann. Die Klasse TcpServerTest muss nicht verändert werden.

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

public class ClientHandler 
{
    private TcpClient client;
    private string ID;

    // Die Menge, die in einen Block geschrieben wird (2 KB).
    private int bufferSize = 2048;

    // Der Puffer mit den Daten, die geschrieben werden sollen.
    private byte[] buffer;

    // Wird verwendet, um Daten aus einer Datei zu lesen.
    private FileStream fileStream;

    // Wird für die Kommunikation mit dem Client verwendet.
    private NetworkStream networkStream;

    // Ein Signal zum Stoppen der Datenübermittlung.
    private bool fileStop = false;

    public ClientHandler(TcpClient client, string ID) 
    {
        this.buffer = new byte[bufferSize];
        this.client = client;
        this.ID = ID;
    }

    public void Start() 
    {
        // Den Netzwerk-Stream abrufen.
        networkStream = client.GetStream();

        // Objekte zum Senden und Empfangen von Text erstellen.
        BinaryWriter w = new BinaryWriter(networkStream);
        BinaryReader r = new BinaryReader(networkStream);

        if (r.ReadString() == ClientMessages.RequestConnect) 
        {
            w.Write(ServerMessages.AcknowledgeOK);
            Console.WriteLine(ID + ": Verbindung beendet.");

            string message = "";
            while (message != ClientMessages.Disconnect)
            {
                message = r.ReadString();
                if (message == ClientMessages.RequestData)
                {
                    // Der Dateiname könnte vom Client zur Verfügung gestellt werden,
                    // doch in diesem Beispiel wird die Testdatei direkt im Code angegeben.
                    fileStream = new FileStream("test.bin", FileMode.Open);

                    // Die Dateigröße senden. (Auf diese Weise weiß der Client, wie
                    // viele Daten gelesen werden müssen).
                    w.Write(fileStream.Length.ToString());

                    // Diese Methode startet eine asynchrone Operation.
                    StreamData(null);                        
                }
            }
            fileStop = true;
            Console.WriteLine(ID + ": Verbindungstrennungsanforderung empfangen.");
        }
        else 
        {
            Console.WriteLine(ID + ": Verbindung konnte nicht beendet werden.");
        }

        // Bereinigung.
        client.Close();
        Console.WriteLine(ID + ": Client-Verbindung geschlossen.");

        Console.ReadLine();
    }


    private void StreamData(IAsyncResult asyncResult) 
    {
        // Abbrechen, wenn der Client getrennt wurde. 
        if (fileStop == true) {
            fileStop = false;
            return;
        }

        if (asyncResult != null)
        {
            // Ein Block wurde asynchron geschrieben.
            networkStream.EndWrite(asyncResult);
        }

        // Den nächsten Block aus der Datei abrufen.
        int bytesRead = fileStream.Read(buffer, 0, buffer.Length);

        // Wenn keine Bytes gelesen wurde, befindet sich der Stream am Ende der Datei.
        if (bytesRead > 0) 
        {
            Console.WriteLine("Neuen Block in den Stream schreiben.");

            // Den nächsten Block in den Netzwerk-Stream schreiben.
            networkStream.BeginWrite(buffer, 0, buffer.Length,
                new AsyncCallback(StreamData), null);
        }
        else 
        {
            // Operation beenden.
            Console.WriteLine("Streaming beendet.");
            fileStream.Close();
        }
    }
}

 

Über UDP kommunizieren

Aufgabe
Sie müssen über ein Netzwerk Daten zwischen zwei Computern übermitteln und möchten dazu einen UDP-Stream (User Datagram Protocol) verwenden.

Lösung
Benutzen Sie die Klasse System.Net.Sockets.UdpClient sowie zwei Threads: einen, um die Daten zu senden, und einen, um die Daten zu empfangen.

Beschreibung

UDP ist ein verbindungsloses Protokoll, das über keine Datenflusssteuerung und Fehlerbehandlung verfügt. Im Gegensatz zu TCP sollte UDP nicht dort verwendet werden, wo die Kommunikation wichtig ist. Da UDP einfacher aufgebaut ist, wird es häufig für Chat-Anwendungen benutzt, bei denen der gelegentliche Verlust einiger Nachrichten akzeptabel ist. Stellen Sie sich beispielsweise vor, dass Sie ein Netzwerk erstellen möchten, in dem die einzelnen Clients in Abständen von einigen Minuten Informationen über die aktuelle Temperatur ihres Standorts zu einem Server senden. Sie könnten in diesem Fall UDP benutzen, da die Kommunikationsfrequenz sehr hoch wäre, während der gelegentliche Verlust eines Paketes kaum ins Gewicht fiele (da der Server immer die zuletzt empfangene Temperatur verwenden könnte).

Die im folgenden Code gezeigte UDP-Beispielanwendung benutzt zwei Threads: einen, um Nachrichten zu empfangen, und einen, um Nachrichten zu senden. Wenn Sie diese Anwendung testen möchten, laden Sie zwei Instanzen gleichzeitig. Übergeben Sie Computer A die IP-Adresse von Computer B und Computer B die Adresse von Computer A. Sie können dann Textnachrichten beliebig hin- und herschicken. (Um das Programm an einem einzelnen Computer zu testen, benutzen Sie zwei verschiedene Anschlüsse und die Rückschleifen-IP-Adresse 127.0.0.1 ).

using System;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Threading;

public class UdpTest 
{
    private static int localPort;

    private static void Main() 
    {

        // Endpunkt definieren, von dem die Nachrichten gesendet werden.
        Console.Write("Verbinden mit IP: ");
        string IP = Console.ReadLine();
        Console.Write("Verbinden mit Anschluss: ");
        int port = Int32.Parse(Console.ReadLine());
        

        IPEndPoint remoteEndPoint = new IPEndPoint(IPAddress.Parse(IP), 
            port);

        // Lokalen Endpunkt definieren (wo Nachrichten empfangen werden).
        Console.Write("Lokaler Anschluss zum Abhören: ");
        localPort = Int32.Parse(Console.ReadLine());
        
        Console.WriteLine();

        // Einen neuen Thread für den Empfang eingehender Nachrichten erstellen.
        Thread receiveThread = new Thread(
            new ThreadStart(ReceiveData));
        receiveThread.IsBackground = true;
        receiveThread.Start();

        UdpClient client = new UdpClient();

        try 
        {
            string text;
            do 
            {
                text = Console.ReadLine();

                // Den Text zum Remote-Client senden.
                if (text != "") 
                {

                    // Daten mit der UTF8-Kodierung in das Binärformat kodieren.
                    byte[] data = Encoding.UTF8.GetBytes(text);

                    // Den Text zum Remote-Client senden.
                    client.Send(data, data.Length, remoteEndPoint);
                }
            } while (text != "");
        }
        catch (Exception err)
        {
            Console.WriteLine(err.ToString());
        }

        Console.ReadLine();
    }

    private static void ReceiveData() 
    {

        UdpClient client = new UdpClient(localPort);
        while (true) 
        {

            try 
            {
                // Bytes empfangen.
                IPEndPoint anyIP = new IPEndPoint(IPAddress.Any, 0);
                byte[] data = client.Receive(ref anyIP);

                // Bytes mit der UTF8-Kodierung in das Textformat kodieren.
                string text = Encoding.UTF8.GetString(data);

                // Den abgerufenen Text anzeigen.
                Console.WriteLine(">> " + text);
            }
            catch (Exception err) 
            {
                Console.WriteLine(err.ToString());
            }
        }
    }
}

Beachten Sie, dass UDP-Anwendungen im Gegensatz zu TCP-Anwendungen die NetworkStream-Abstraktion nicht nutzen können. Stattdessen müssen sie alle Daten mithilfe einer Kodierungsklasse in einen Byte-Stream konvertieren.

Sie können diese Anwendung mit Clients auf dem lokalen Computer testen, wenn Sie die Rückschleifenadresse 127.0.0.1 verwenden. Voraussetzung hierfür ist, dass Sie verschiedene Anschlüsse benutzen. Stellen Sie sich beispielsweise eine Situation mit zwei UDP-Clients vor: Client A und Client B. Nachfolgend ist das Protokoll von Client A aufgeführt:

Verbinden mit IP: 127.0.0.1
Verbinden mit Anschluss: 8001
Lokaler Anschluss zum Abhören: 8080

Hallo!

Und hier ist das entsprechende Protokoll von Client B (mit der erhaltenen Nachricht):

Verbinden mit IP: 127.0.0.1
Verbinden mit Anschluss: 8080
Lokaler Anschluss zum Abhören: 8001

>> Hallo!

 

Eine Email über SMTP versenden

Aufgabe
Sie möchten eine Nachricht an eine Email-Adresse versenden und dazu einen SMTP-Server (Simple Mail Transfer Protocol) benutzen.

Lösung
Benutzen Sie die Klassen SmtpMail und MailMessage, die sich im Namespace System.Web.Mail befinden.

Beschreibung

Die Klassen im Namespace System.Web.Mail stellen einen einfachen Wrapper für die CDOSYS-Komponente (Collaboration Data Objects) von Windows 2000 zur Verfügung. Sie können mit diesen Klassen, die SMTP verwenden, formatierte Email-Nachrichten erstellen und versenden.

Der Einsatz dieser Typen ist nicht kompliziert. Sie erstellen einfach ein MailMessage-Objekt, geben die Email-Adressen des Absenders und Empfängers an und ordnen den Nachrichteninhalt in der Eigenschaft Body an:

MailMessage myMessage = new MailMessage();
myMessage.To = "jemand@irgendwo.com";
myMessage.From = "ich@irgendwo.com";
myMessage.Subject = "Hallo";
myMessage.Priority = MailPriority.High;
myMessage.Body = "Dies ist die Nachricht!";

Wenn Sie möchten, können Sie sogar eine HTML-Nachricht versenden, in dem Sie das Nachrichtenformat ändern und HTML-Tags benutzen:

myMessage.BodyFormat = MailFormat.Html;
myMessage.Body = @"<HTML><HEAD></HEAD>" + 
  @"<BODY>Dies ist die Nachricht!</BODY></HTML>";

Sie können auch mithilfe der Auflistung MailMessage.Attachments und der Klasse MailAttachment Dateien an Emails hängen.

MailAttachment myAttachment = new MailAttachment("c:\\mypic.gif");
myMessage.Attachments.Add(myAttachment);

Um die Nachricht zu versenden, geben Sie einfach den Namen des SMTP-Servers an und rufen die Methode SmtpMail.Send auf.

SmtpMail.SmtpServer = "test.mailserver.com";
SmtpMail.Send(myMessage);

Der Einsatz der Klasse SmtpMail ist mit einer wesentlichen Einschränkung verbunden, wenn Sie eine Email-Nachricht versenden möchten. Die Klasse verlangt einen lokalen SMTP-Server oder einen Relay-Server, der sich in Ihrem Netzwerk befindet. Sie unterstützt außerdem nicht die Authentifizierung. Wenn Ihr SMTP-Server somit einen Benutzernamen und ein Kennwort verlangt, können Sie keine Email versenden. Um dieses Problem zu umgehen, können Sie die CDOSYS-Komponente direkt über COM Interop verwenden (sofern Sie über eine Server Version von Windows oder Microsoft Exchange verfügen).

HINWEIS: Beachten Sie, dass das SMTP-Protokoll nicht verwendet werden kann, um Emails zu empfangen. Hierfür benötigen Sie das Protokoll POP3 oder IMAP. Keines dieser Protokolle wird jedoch vom .NET Framework unterstützt.

 

Emails mit MAPI senden und empfangen

Aufgabe
Sie möchten eine Email-Nachricht versenden, aber Sie haben keinen SMTP-Server (Simple Mail Transfer Protocol) für Ihren Computer konfiguriert.

Lösung
Benutzen Sie Simple MAPI (Messaging Application Programming Interface), indem Sie die benötigte Funktionalität aus der nicht verwalteten Systembibliothek Mapi32.dll importieren.

Beschreibung

MAPI ist eine Schnittstelle, die es Ihnen ermöglicht, mit den Mailing-Features zu interagieren, die in das Windows-Betriebssystem integriert sind. Sie können MAPI (entweder über die entsprechende nicht verwalteten API oder die MAPI-COM-Komponente von Visual Studio 6) verwenden, um mit dem Standard-Mail-Client (in der Regel Microsoft Outlook oder Outlook Express) zu interagieren. Sie können auf diese Weise verschiedene Aufgaben ausführen, wie zum Beispiel das Abrufen von Kontaktinformationen aus dem Adressbuch, das Lesen von Nachrichten im Posteingang und das programmgesteuerte Zusammenstellen und Versenden von Nachrichten. Leider gibt es keine .NET-Klassen für die Verwendung von MAPI. Sie können jedoch die nicht verwaltete Bibliothek Mapi32.dll verwenden.

Die größte Herausforderung, die mit dem Einsatz von Simple MAPI in .NET verbunden ist, ist das Marshalling der .NET-Strukturen, in die Strukturen, die Simple MAPI erwartet, und das Marshalling der Simple MAPI-Strukturen, die den Weg zurück in die .NET-Anwendung finden müssen. Dies ist keine einfache Aufgabe. Microsoft bietet jedoch eine Lösung in Form einer generischen C#-Komponente an, die kostenlos aus dem Internet bezogen werden kann. Die folgenden beiden Projekte sind verfügbar:

Der vollständige Code beider Komponenten ist einfach aufgebaut, aber zu lang, um hier aufgeführt zu werden. Sie finden ihn im Beispielcode zu diesem Kapitel.

HINWEIS: Ein komplexeres Beispiel, das die Simple MAPI-Bibliothek von Microsoft verwendet, um eine umfassende Windows Forms-Anwendung zu erstellen, wird in Form eines C#- Beispielprojekts von Thomas Scheidegger kostenlos (aber ohne Support) unter der folgenden Adresse angeboten: http://www.codeproject.com/csharp/simplemapidotnet.asp.