communityWir suchen ständig neue Tutorials und Artikel! Habt ihr selbst schonmal einen Artikel verfasst und seid bereit dieses Wissen mit der Community zu teilen? Oder würdet ihr gerne einmal über ein Thema schreiben das euch besonders auf dem Herzen liegt? Dann habt ihr nun die Gelegenheit eure Arbeit zu veröffentlichen und den Ruhm dafür zu ernten. Schreibt uns einfach eine Nachricht mit dem Betreff „Community Articles“ und helft mit das Angebot an guten Artikeln zu vergrößern. Als Autor werdet ihr für den internen Bereich freigeschaltet und könnt dort eurer literarischen Ader freien Lauf lassen.

TCP/IP Socket-Programmierung in C# - Ping, Threads und asynchrone Sockets Drucken E-Mail
Benutzerbewertung: / 982
SchwachPerfekt 
Geschrieben von: Kristian   
Sonntag, den 12. März 2006 um 02:20 Uhr
Beitragsseiten
TCP/IP Socket-Programmierung in C#
Daten senden und empfangen
Höhere Socketklassen
Multicast und Broadcast
Ping, Threads und asynchrone Sockets
Formulare im Internet senden
E-Mail verschicken
Alle Seiten

Ping mit C#

Eines der trivialsten Dinge im Netzwerk ist das sogenannte Anpingen eines Host. Damit kann überprüft werden ob ein Host überhaupt online ist und wie groß die Verzögerung zwischen ausgehenden Paketen und der Antwort ist. Diese Zeit wird auch Latenzzeit genannt. Ping sendet dazu ein ICMP-Echo-Request-Paket an die Zieladresse des zu überprüfenden Hosts. Der Empfänger muss, sofern er das Protokoll unterstützt, laut Protokollspezifikation eine Antwort zurücksenden, das sogenannte ICMP Echo-Reply. Aus der Differenz errechnet sich die Latenzzeit.

Ein Ping ist relativ einfach strukturiert. Im Gegensatz zu komplexen Netzwerkkommunikationen werden nur einfache genormte Pakete versendet. Umso überraschender ist, dass die .NET-Klassenbibliothek bis zur Version 2.0 keine vorgefertigte Klasse bzw. Lösung für das Anpingen eines Hosts bereitstellte. Stattdessen mussten Sie das selbst mit C# realisieren. Die Socket Klasse stellt dafür den SocketType Raw zur Verfügung. Mithilfe von Raw Sockets ist es möglich unter Verwendung des ICMP ein eigens erstelltes Ping Paket über das Netzwerk zu senden.
Bedenken Sie aber das alle Windows Versionen der NT-Serie seit den von Microsoft zur Verfügung gestellten Service Packs einen vollständigen Zugriff auf Raw Sockets nicht gestatten. Im Gegensatz zur gängigen Berkeley Unix Raw Socket Implementierung, sind Anwendungen vom "lower-level" Zugriff zum unterliegenden physikalischen Internet abgeschnitten. Beispielsweise ist es nicht möglich die Quell-IP-Adresse eines Paketes (spoofing) zu beeinflussen.

Microsoft hat sich für diese Einschränkung entschlossen um Missbrauch entgegenzuwirken, nachdem führende Sicherheitsexperten die unter Windows XP/2000 anfangs noch vollständig vorhandene Raw Socket Implementierung stark kritisiert hatten. Wenn Sie dennoch in den vollen Genuss von Berkeley Unix Raw Sockets unter Windows kommen möchten, sind Sie gezwungen einen eigenen Kernel-Mode Treiber zu schreiben der sich in den NDIS Stack einklinkt und direkt mit dem Gerätetreiber der Netzwerkkarte kommuniziert. Auf der Rawether for Windows Webseite von PCAUSA finden Sie mehrere vorgefertigte Lösungen zu diesem Problem.

Da der Code für einen selbst erstellten Ping vollständig von Grund auf implementiert wird, führt das oft zu sehr vielen Codezeilen. Als erster Programmierer hat der Microsoft-Entwickler Lance Olson das Problem gelöst und ein komplettes Programm im MSDN Magazine veröffentlicht. Seitdem wurde dieses in die verschiedensten Varianten abgeändert und erweitert. Das Prinzip ist jedoch das gleiche geblieben. Das folgende Programm zeigt den Aufbau der Klasse Ping.

using System;
using System.Net;
using System.Net.Sockets;
 
namespace CodePlanet.Articles.ProgrammingSockets
{
    /// <summary>
    /// Implementierung eines Pings in C#.
    /// </summary>
    class Ping
    {
        const int SOCKET_ERROR = -1;
        const int ICMP_ECHO = 8;
 
        public int GetPingTime(string host)
        {
            int nBytes = 0, dwStart = 0, dwStop = 0, PingTime = 0;
 
            IPHostEntry serverHE, fromHE;
            IcmpPacket packet = new IcmpPacket();
 
            if (host == null)
                return -1;
 
            // Einen Raw-Socket erstellen.
            Socket socket = new Socket(AddressFamily.InterNetwork,
                                       SocketType.Raw,
                                       ProtocolType.Icmp);
 
            serverHE = Dns.GetHostEntry(host);
 
            if (serverHE == null) {
                return -1; // Fehler
            }
 
            // Den IPEndPoint des Servers in einen EndPoint konvertieren.
            IPEndPoint ipepServer = new IPEndPoint(serverHE.AddressList[0], 0);
            EndPoint epServer = (ipepServer);
 
            // Den empfangenen Endpunkt für den Client-Rechner setzen.
            fromHE = Dns.GetHostEntry(Dns.GetHostName());
            IPEndPoint ipEndPointFrom = new IPEndPoint(fromHE.AddressList[0], 0);
            EndPoint EndPointFrom = (ipEndPointFrom);
 
            int PacketSize = 0;
 
            for (int j = 0; j < 1; j++) {
                // Das zu sendende Paket erstellen.
                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;
 
 
                // Stelle sicher dass das icmp_pkt_buffer Byte Array 
                // eine gerade Zahl ist.
                if (PacketSize % 2 == 1)
                    ++PacketSize;
 
                byte[] icmp_pkt_buffer = new byte[PacketSize];
 
                int index = 0;
 
                index = Serialize(packet,
                                  icmp_pkt_buffer,
                                  PacketSize,
                                  PingData);
 
                if (index == -1)
                    return -1;
 
                // 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);
 
                if (index == -1)
                    return -1;
 
                dwStart = System.Environment.TickCount; // Starte den Timer
 
                if ((nBytes = socket.SendTo(sendbuf, PacketSize, 0, epServer)) == SOCKET_ERROR) {
                    Console.WriteLine("Error calling sendto");
                    return -1; // Fehler
                }
 
                // Initialisiere den Buffer. Der Empfänger-Buffer ist die Größe des
                // ICMP Header plus den IP Header (20 bytes)
                byte[] ReceiveBuffer = new byte[256];
 
                nBytes = 0;
                nBytes = socket.ReceiveFrom(ReceiveBuffer, 256, 0, ref EndPointFrom);
 
                if (nBytes == SOCKET_ERROR) {
                    dwStop = SOCKET_ERROR;
                } else {
                    // Stoppe den Timer
                    dwStop = System.Environment.TickCount - dwStart;
                }
            }
 
            socket.Close();
            PingTime = (int)dwStop;
            return PingTime;
        }
 
        private static int Serialize(IcmpPacket packet, byte[] Buffer, int PacketSize, int PingData)
        {
            int cbReturn = 0;
 
            // Serialisiere den struct in ein Array
            int Index = 0;
 
            byte[] b_type = new byte[1];
            b_type[0] = (packet.Type);
 
            byte[] b_code = new byte[1];
            b_code[0] = (packet.SubCode);
 
            byte[] b_cksum = BitConverter.GetBytes(packet.CheckSum);
            byte[] b_id = BitConverter.GetBytes(packet.Identifier);
            byte[] b_seq = BitConverter.GetBytes(packet.SequenceNumber);
 
            // Console.WriteLine("Serialize type ");
            Array.Copy(b_type, 0, Buffer, Index, b_type.Length);
            Index += b_type.Length;
 
            // Console.WriteLine("Serialize code ");
            Array.Copy(b_code, 0, Buffer, Index, b_code.Length);
            Index += b_code.Length;
 
            // Console.WriteLine("Serialize cksum ");
            Array.Copy(b_cksum, 0, Buffer, Index, b_cksum.Length);
            Index += b_cksum.Length;
 
            // Console.WriteLine("Serialize id ");
            Array.Copy(b_id, 0, Buffer, Index, b_id.Length);
            Index += b_id.Length;
 
            Array.Copy(b_seq, 0, Buffer, Index, b_seq.Length);
            Index += b_seq.Length;
 
            // Kopiere die Daten
 
            Array.Copy(packet.Data, 0, Buffer, Index, PingData);
 
            Index += PingData;
 
            if (Index != PacketSize /* sizeof(IcmpPacket) */) {
                cbReturn = -1;
                return cbReturn;
            }
 
            cbReturn = Index;
            return cbReturn;
        }
 
        private static UInt16 checksum(UInt16[] buffer, int size)
        {
            int cksum = 0;
            int counter;
 
            counter = 0;
 
            while (size > 0) {
                UInt16 val = buffer[counter];
 
                cksum += Convert.ToInt32(buffer[counter]);
                counter += 1;
                size -= 1;
            }
 
            cksum = (cksum >> 16) + (cksum & 0xffff);
            cksum += (cksum >> 16);
            return (UInt16)(~cksum);
        }
    }
 
    public class IcmpPacket
    {
        public byte Type;               // Message Typ
        public byte SubCode;            // Subcode Typ
        public byte[] Data;             // Byte Array
        public UInt16 CheckSum;         // Checksumme
        public UInt16 Identifier;       // Identifizierer
        public UInt16 SequenceNumber;   // Sequenznummer 
    }
}

Viele DOS-Angriffe, auch Denial of Service Attacks genannt, werden ähnlich wie beim Ping über das ICMP realisiert. Im Gegensatz zum "normalen" Ping werden hierbei korrupte Pakete in sehr großer Anzahl an den Server gesendet. Bei entsprechender Menge gelingt es diesen durch Überlastung zum Absturz zu bringen.

Achten Sie darauf das ihr Programm durch Abänderung nicht versehentlich korrupte Datenpakete versendet, so dass ihre IP-Adresse bei Tests eventuell auf die Blacklist des Servers gelangt.

Seit der .NET Framework Version 2.0 steht ein neuer Namensraum zur Verfügung der zahlreiche neue Funktionen für den Zugriff auf Traffic Daten, Netzwerk Adress Informationen, Benachrichtigungsmethoden usw. bereitstellt. Der Namespace heißt System.Net.NetworkInformation und erweitert den Namespace System.Net. Der Namensraum enthält nun endlich auch eine Klasse für das Anpingen von Hosts mit dem Namen Ping. Das folgende Beispiel stammt aus der MSDN und zeigt den prinzipiellen Aufbau eines C#-Programmes das die Klasse Ping nutzt:

using System;
using System.Net;
using System.Net.NetworkInformation;
using System.Text;
 
namespace CodePlanet.Articles.ProgrammingSockets
{
    /// <summary>
    /// Das folgende Programm pingt einen gegebenen Host an, indem es
    /// die Klasse Ping aus dem Namensraum System.Net.NetworkInformation
    /// verwendet. Das Beispiel stammt aus der MSDN Library -
    /// ©2006 Microsoft Corporation.
    /// </summary>
    public class PingClass
    {
        // Das Argument args[0] kann eine IPaddress ode ein Host-Name sein.
        public static void Main(string[] args)
        {
            if (args.Length < 1) {
                throw new ArgumentException("Parameters: [<Uri>]");
            }
 
            Ping pingSender = new Ping();
            PingOptions options = new PingOptions();
 
            // Benutze den Standard TTL Wert (Time To Live) der bei 128ms liegt,
            // aber ändere das Fragmentationsverhalten.
            options.DontFragment = true;
 
            // Erzeuge einen Puffer mit der Länge von 32 Bytes 
            // die versendet werden sollen.
            string data = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
            byte[] buffer = Encoding.ASCII.GetBytes(data);
            int timeout = 120;
 
            PingReply reply = pingSender.Send(args[0], timeout, buffer, options);
            if (reply.Status == IPStatus.Success) {
                Console.WriteLine("Address: {0}", reply.Address.ToString());
                Console.WriteLine("RoundTrip time: {0}", reply.RoundtripTime);
                Console.WriteLine("Time to live: {0}", reply.Options.Ttl);
                Console.WriteLine("Don't fragment: {0}", reply.Options.DontFragment);
                Console.WriteLine("Buffer size: {0}", reply.Buffer.Length);
            }
        }
    }
}

Der oben gezeigte Quellcode verwendet die Klasse Ping synchron. Falls Ihre Applikation nicht blocken soll, müssen Sie die asynchrone Methode SendAsync benutzen. Ein Aufruf von SendAsync wird in einem separaten Thread ausgeführt der automatisch Teil eines Thread Pools ist. Sobald die asynchrone Operation vervollständigt ist, wird ein PingCompleted Ereignis ausgelöst. Näheres zum Thema Blocking, synchrone und asynchrone I/O erfahren Sie in den nachfolgenden Abschnitten. Eine asynchrone Implementierung der Klasse Ping ist ebenfalls im Dateianhang dieses Tutorials enthalten.

Blocking

In unseren bisherigen Programmen haben wir Sockets dazu verwendet Daten zu empfangen, Daten zu senden, Hosts anzupingen uvm. Unsere Programme waren einfach aufgebaut und erfüllten ihren Zweck. Die bisherige Nutzung der Sockets hat allerdings einen entscheidenden Nachteil. Methoden wie beispielsweise Send, Receive und viele weitere blockieren die Programmausführung solange sie nicht ordnungsgemäß abgelaufen sind. Dieses sogenannte Blocking (engl. für blockieren) kann beispielsweise in der Setup Phase während des Verbindungsaufbau's sehr lang sein, wenn der Server nur sehr langsam auf Anfragen reagiert. Während dieser Zeit ist das Programm praktisch gelähmt und kann keine weiteren Prozesse ausführen. Wird eine Blocking-Methode aufgerufen die Daten vom Socket Buffer einliest, diese jedoch nicht vorhanden sind, kann die Methode im schlechtesten Fall das Programm vollständig einfrieren. Dies passiert z.B. bei verloren gegangenen UDP-Datagramms.

In unseren Kommandozeilen-Programmen ist das noch relativ unspektakulär und auch nebensächlich. Doch was passiert wenn wir ausgefeiltere Programme schreiben möchten? Womöglich mit Fenstern und Eingabefeldern? Würden wir die bisherigen Wege verwenden über Sockets zu kommunizieren, so hätten wir ein Problem. Solange die Methoden das Programm blockieren, kann dieses nämlich weder auf Mausbewegungen noch auf Tastatureingaben reagieren!

Nonblocking I/O, Threads und asynchrone I/O

Doch wie sieht die Lösung diese Problems aus? Es existieren einige Möglichkeiten in .NET damit vernünftig umzugehen. Die erste triviale Möglichkeit ist einfach keine Aufrufe zu tätigen die das Blockieren des Programmes zur Folge hätten. Eigenschaften und Methoden der Klassen können hierbei genutzt werden um den Status festzustellen bevor eine Block Methode aufgerufen wird:

TcpClient client = new TcpClient(server, port);
NetworkStream netstream = client.GetStream();
 
// Befinden sich überhaupt Daten im Stream?
if(netstream.DataAvailable)
    int len = netstream.Read(buffer, 0, buffer.Length);
 
...

Eine weitere Möglichkeit besteht darin blockierende Methoden mit einem Timeout zu versehen. Wird eine bestimmte Zeitspanne überschritten, so übergibt die Methode die Ausführung wieder an das Programm. Dieser Ansatz wird auch als Polling (engl. zyklische Abfrage) bezeichnet. Sinngemäß existiert auch eine Methode namens Poll.

// Erzeugt einen Socket der Daten über TCP versendet.
Socket sock = new Socket(AddressFamily.InterNetwork, 
                  SocketType.Stream,
                  ProtocolType.Tcp);
 
// Verbinde zum Endpunkt
sock.Connect(EPhost);
 
if (!sock.Connected) {
    strRetPage = "Unable to connect to host";
}
 
// Benutze die SelectWrite Enumeration um den Socket Status zu erhalten.
// Kein bestimmtes Timeout!
if(sock.Poll(-1, SelectMode.SelectWrite)) {
    Console.WriteLine("This Socket is writable.\n");
} else if (sock.Poll(1000000, SelectMode.SelectRead)) {
    // Warte diesmal 1 Sekunde lang, breche dann ab.
    Console.WriteLine("This should not print. Because this is not a listening Socket,"
                      + "no incoming connecton requests are expected.\n" );
} else if (sock.Poll(-1, SelectMode.SelectError)) {
    Console.WriteLine("This Socket has an error.");
}

Das Polling ist jedoch sehr ineffizient. Es werden in gleichmäßigen Intervallen ständig Aufrufe zur Überprüfung des aktuellen Status durchgeführt. Ihr Programm läuft also in einer Warteschleife und wartet auf Ereignisse die sich womöglich in nur sehr unregelmäßigen Abständen ereignen. Ein klassischer Fall von Performanceverschwendung.

Ähnlich zum Polling über fest gesetzte Timeouts ist das Setzen eines bestimmten Socket-Flags. Wird dieses Flag auf false gesetzt, so blockieren die Methoden dieses Sockets bei ihrem Aufruf das Programm nicht mehr. Mithilfe von Exceptions ist es möglich durch eine entsprechende Fehlerbehandlung darauf zu reagieren.

// Erstelle einen Raw Socket und benutze ICMP.
Socket sock = new Socket(AddressFamily.InterNetwork, 
                         SocketType.Raw, 
                         ProtocolType.Icmp);
 
// Setze das Flag Blocking auf false und versetze
// den Socket auf diese Weise in den Non-Blocking-Modus.
sock.Blocking = false;
 
// Send() befindet sich nun im Non-Blocking-Modus
// und wird nicht mehr blockieren.
try {
    sock.Send(buffer, buffer.Length, 0)
    // ...
} catch(SocketException se) {
    // ...
}

Wir haben gesehen wie in .NET Nonblocking Verfahren verwendet werden können um anderweitig Code auszuführen während wir gleichzeitig auf die Socket Methoden warten. Da diese fast auschliesslich auf Polling beruhen sind sie jedoch oft einfach ungeeignet. Neben dem zeitlichen Aspekt hat das Polling auch noch einen weiteren entscheidenden Nachteil. Es kann immer nur eine bestimmte Anzahl an Verbindungen abgefertigt werden. Ein als Server agierendes Programm wird einen neuen Clienten erst dann bedienen wenn es mit dem aktuellen Clienten abgeschlossen hat. Dies kann bei bestimmten Anwendungen sogar erwünscht sein, insbesondere dann wenn Clienten sequentiell abgefertigt werden sollen, ist aber in den meisten Fällen unzureichend.

Hilfreich wäre ein Verfahren in dem die Prozesse separat agieren und sich nicht gegenseitig behindern. Als C# Programmierer werden Sie wahrscheinlich sofort an Threads denken. Und mit dieser Vermutung liegen Sie goldrichtig! In der Tat lässt sich das Problem des Blockings am effektivsten mit Threads lösen.

Thread t1 = new Thread(new ThreadStart(ClientHandle));

Threads ermöglichen Multitasking innerhalb der Anwendung. Konkret gesagt ist das die Fähigkeit mehrere Dinge im Programm gleichzeitig zu machen. Sie können mithilfe von Threads einen Echo-Server schreiben der jeden Clienten in einem separaten Thread verwaltet. Ein einfaches Beispiel das sich Threads zu Nutze macht finden Sie im Dateianhang.

Die Common Language Runtime fasst einen Großteil der Thread-Unterstützung in Klassen zusammen. Auf diese Weise kapseln viele Klassen in .NET Threads in ihrer eigenen Ausführung und Sie als Nutzer bekommen wenig davon mit. So ist es auch beim dritten und letzten Verfahren mit dem es möglich ist das Problem des Blockings effektiv zu lösen. Nämlich bei den asynchronen Inputs und Outputs. In diesem gängigen Verfahren werden Methoden verwendet die asynchron ablaufen. Wenn das Programm beispielsweise Daten über einen Socket sendet, wird parallel ein Thread etabliert der das Senden überwacht und durchführt. Sobald der Aufruf beendet ist übergibt der Thread die Daten zurück an das Programm. Während dieser Zeit läuft der Hauptprozess weiter und führt entsprechende Operationen aus. Die Übergabe der Daten sowie die Erzeugung des Threads werden vollautomatisch durchgeführt.

asynccalls

Neben der BeginSend Methode, die Daten asynchron an einen verbundenen Socket sendet, ist es auch möglich Daten mit BeginWrite direkt zu einem NetworkStream zu schreiben. Hierbei wird eine separate Klasse erzeugt, die für das asynchrone Streaming der Daten sorgt. Das Streaming beginnt mit einem Datenblock, indem es die Methode BeginWrite der Klasse NetworkStream aufruft und eine Rückrufmethode (engl. Callback) zur Verfügung stellt. Zunächst startet die Methode BeginWrite das asynchrone Senden an den Host. Sobald die Anwendung BeginWrite aufruft, generiert das System einen separaten Thread um dort die entsprechende Rückrufmethode auszuführen. Der Callback implementiert die Methode EndWrite die den Thread solange blockiert bis sämtliche Bytes vom NetworkStream übertragen wurden. Wenn Sie möchten das der aufrufende Thread blockiert wird nachdem die Methode BeginWrite aufgerufen wurde, benutzen Sie WaitOne. Soll der aufrufende Thread weiterhin ausgeführt werden, können Sie auf ManualResetEvent.Set in der Rückrufmethode zurückgreifen. Sobald der Rückruf ausgelöst und ein entsprechendes Signal an WaitHandle gesendet wird, können Sie den nächsten Datenblock senden oder bei einem Echo-Server die Daten wieder empfangen.

NetworkStream netStream = client.GetStream();
 
// Gekapseltes Objekt für den Callback
ClientState cs = new ClientState(netStream, "SomeString");
 
// Sende den kodierten String asynchron an den Server
IAsyncResult result = netStream.BeginWrite(cs.ByteBuffer, 0,
                                           cs.ByteBuffer.Length,
                                           new AsyncCallback(WriteCallback),
                                           cs);
 
// Mache irgend etwas anderes
DoOtherStuff();
 
result.AsyncWaitHandle.WaitOne(); // blockiere bis EndWrite aufgerufen wird
 
...
 
// Die Klasse IAsyncResult repräsentiert den Status der
// asynchronen Operation und kann dazu verwendet werden
// um die Rückgabe der Operation zu blocken
// oder zu pollen (zyklische Abfrage).
public static void WriteCallback(IAsyncResult asyncResult)
{
    ClientState cs = (ClientState)asyncResult.AsyncState;
 
    // Behandelt das Ende des asynchronen Schreibens.
    cs.NetStream.EndWrite(asyncResult);
 
    ...
}

Threads können in Anwendungen in denen viele asynchrone Prozesse ablaufen ziemlich kostenintensiv werden. Bedenken Sie das für einen neuen Thread zunächst einmal ein Kernel Objekt allokiert werden muss, der zugehörige Stack initialisiert wird und anschließend sendet Windows® allen DLL's im Prozess eine entsprechende Benachrichtigung. Wird der Thread wieder zerstört so geht das Spiel in entgegengesetzter Richtung wieder von vorne los. Deshalb können Sie dieses gesamte Prozedere auch mithilfe der .NET ThreadPool Klasse realisieren. Unter Umständen sparen Sie auf diese Weise effektiv Ressourcen ein.

Nachfolgend sehen Sie ein grafisch anspruchsloses Client-Server Programm in bekannter Windows Fenster Form. Während der Server Operationen asynchron durchführt, arbeitet der Client sequentiell. Sie finden beide Programme im Anhang. Eine weiteres Beispiel das vollständig asynchrone I/O verwendet ist ebenfalls enthalten.

server-and-client


Zuletzt aktualisiert am Donnerstag, den 02. Januar 2014 um 23:03 Uhr
 
AUSWAHLMENÜ