Client-Server-Modell - Ereignisorientierte Programmierung |
Dienstag, den 24. August 2010 um 11:20 Uhr | ||||||||||||||
Seite 2 von 2
Ereignisorientierte ProgrammierungUm entsprechend auf eingehende Pakete reagieren zu können, bieten sich Ereignisbehandlungsroutinen (engl. Event Handler) an. Bei der ereignisorientierten Programmierung (engl. event-driven programming) wird das Programm nicht linear durchlaufen, sondern es werden spezielle Ereignisbehandlungsroutinen (engl. listener, observer, event handler) immer dann ausgeführt, wenn ein bestimmtes Ereignis auftritt. C# stellt für die ereignisorientierte Programmierung die sogenannten events und delegates zur Verfügung. DelegateEin public class TestDelegate { // Declaration public delegate void SimpleDelegate(); public static void MyFunc() { Console.WriteLine("I was called by a delegate..."); } public static void Main(string[] args) { // Instantiation SimpleDelegate simpleDelegate = new SimpleDelegate(MyFunc); // Invocation simpleDelegate(); } } Auf diese Weise kann das Protokoll Delegates bereitstellen, die dann vom Server und Client benutzt werden können. EventGrafische Benutzeroberflächen (GUIs) machen es erforderlich, dass Programme auf Andere Klassen können auf diese Ereignisse reagieren. Jedes Objekt in C# kann eine Reihe von Events publizieren, die andere Klassen abonnieren können. Wenn die publizierende Klasse ein Event auslöst, werden alle Abonnentenklassen benachrichtigt. So kann ein Objekt eine beliebige Anzahl interessierter Beobachter benachrichtigen, wenn das Objekt angesteuert wird. Das Objekt wird dann als Publizierer (engl. Publisher) bezeichnet, denn er publiziert das Ereignis, und die anderen Klassen sind dann die Abonnenten (engl. Subscriber), denn sie abonnieren das Ereignis. In dem Beispiel aus der MSDN löst eine eigene Collection beim Hinzufügen von Elementen mit der Methode using System; namespace MyCollections { using System.Collections; // A class that works just like ArrayList, but sends event // notifications whenever the list changes: public class ListWithChangedEvent : ArrayList { // An event that clients can use to be notified whenever the // elements of the list change: public event EventHandler Changed; // Invoke the Changed event; called whenever list changes: protected virtual void OnChanged(EventArgs e) { if (Changed != null) Changed(this, e); } // Override some of the methods that can change the list; // invoke event after each: public override int Add(object value) { int i = base.Add(value); OnChanged(EventArgs.Empty); return i; } public override void Clear() { base.Clear(); OnChanged(EventArgs.Empty); } public override object this[int index] { set { base[index] = value; OnChanged(EventArgs.Empty); } } } } using System; namespace TestEvents { using MyCollections; public class EventListener { private ListWithChangedEvent List; public EventListener(ListWithChangedEvent list) { List = list; // Add "ListChanged" to the Changed event on "List": List.Changed += new EventHandler(ListChanged); } // This will be called whenever the list changes: private void ListChanged(object sender, EventArgs e) { Console.WriteLine("This is called when the event fires."); } public void Detach() { // Detach the event and delete the list: List.Changed -= new EventHandler(ListChanged); List = null; } } public class Test { // Test the ListWithChangedEvent class: public static void Main() { // Create a new list: ListWithChangedEvent list = new ListWithChangedEvent(); // Create a class that listens to the list's change event: EventListener listener = new EventListener(list); // Add and remove items from the list: list.Add("item 1"); list.Clear(); listener.Detach(); } } } Das ereignisorientierte Modell mit Events in C# wird sehr häufig in der asynchronen Programmierung verwendet. Das Client-Server-Modell ist prädestiniert für diese Art der Programmierung, da mit asynchronen Vorgängen üblicherweise Aufgaben ausgeführt werden, die längere Zeit in Anspruch nehmen, z. B. das Öffnen großer Dateien oder die Herstellung einer Verbindung und Übertragung von Daten mit Remotecomputern. Eigene EreignisbehandlungsroutinenIn der Klasse Server werden nun entsprechende Ereignisbehandler mittels Multicasting zugewiesen. So sollen bestimmte Methoden aufgerufen werden, wenn beispielsweise ein neues Datenpaket eintrifft oder der Client die Verbindung unterbricht. public void CreateNewClient(Socket socket) { ClientWorker newClient = new ClientWorker(socket); newClient.PacketReceivedEvent += new PacketReceivedEventHandler(PacketReceived); newClient.ClientDisconnectedEvent += new ClientDisconnectedEventHandler(ClientDisconnected); Sobald ein Datenpaket empfangen wird, wird die Methode /// <summary> /// Stores all possible packet types in the protocol. /// </summary> public void PacketReceived(object sender, PacketEventArgs e) { // Switch on message types switch(e.Packet.Type) { case PacketType.ClientLoginInformation: // On login SetClientData(e.Packet); break; case PacketType.ContactListRequest: // Send a partial contact list SendContactList(e.Packet); break; case PacketType.ContactRequest: // Send detailled contact information SendContact(e.Packet); break; case PacketType.Route: // Route message Send(e.Packet); break; case PacketType.Broadcast: // Broadcast to all BroadCast(e.Packet); break; default: // Unknown type LogConsole("unknown message type.", e.Packet.Source); break; } } Der Server verwendet für jeden Clienten eine neue Instanz der Klasse /// <summary> /// This method triggers an event, when we receive a new packet. /// </summary> /// <param name="e"></param> protected virtual void OnPacketReceived(PacketEventArgs e) { // Invoke the event; called whenever we receive a packet if (PacketReceivedEvent != null) PacketReceivedEvent(this, e); } Daten über Netzwerk transferierenDas 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. Der Transfer von Daten basiert im Internet und auch in den meisten anderen Netzwerken auf Sockets, mithilfe derer sich Daten senden und empfangen lassen. Datenpakete sendenDas Senden der Daten wird von einem separaten Thread durchgeführt, um ein Blockieren der Anwendung zu vermeiden. Das ist insbesondere bei Programmen mit einer grafischen Benutzeroberfläche notwendig. Bei der Programmierung mit vielen konkurrierenden Threads sind entsprechende Synchronisierungsmaßnahmen zu ergreifen, siehe „Multithreading in C#“. Um sicherzustellen das immer nur ein aktiver Thread Daten über das Netzwerk sendet, wird der Codeabschnitt mithilfe eines Mutex geschützt. Auf diese Weise darf zu einem Zeitpunkt jeweils nur ein Thread Daten über den Socket senden. Dazu wird das Datenpaket serialisiert und anschließend mit der Methode /// <summary> /// Sends a packet to a client. /// </summary> /// <param name="packet"></param> /// <returns></returns> private bool SendPacketToClient(IPacket packet) { try { // Allow only one thread to operate on the socket _mutex.WaitOne(); NetworkStream networkStream = new NetworkStream(_socket); byte[] packed = packet.Serialize(); networkStream.Write(packed, 0, packed.Length); return true; } catch { return false; } finally { _mutex.ReleaseMutex(); } } Datenpakete empfangenUm den Empfang der Daten kümmert sich stets nur ein einzelner Thread, eine Synchronisation ist daher nicht erforderlich. Die in der Schnittstelle IPacket definierte Methode /// <summary> /// Receives a packet from a client. /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void ReceivePacketFromClient(object sender, DoWorkEventArgs e) { bool blockingState = _socket.Blocking; try { while (true) { IPacket packet = Protocol.CreatePacket(SocketType.Stream); packet.Deserialize(new NetworkStream(_socket)); if (packet.Corrupted) { // If the packet is corrupted, check if we still are connected, // make a nonblocking, zero-byte Send call. If we aren't // connected, it will throw a Exception. _socket.Blocking = false; _socket.Send(new byte[1], 0, 0); } else { // Packet successfully received OnPacketReceived(new PacketEventArgs(packet)); } } } catch (SocketException ex) { Debug.WriteLine(ex.Message); } catch (ArgumentException ex) { Debug.WriteLine(ex.Message); } catch (IOException ex) { Debug.WriteLine(ex.Message); } finally { _socket.Blocking = blockingState; } // Socket was closed OnClientDisconnected(new ClientEventArgs(socket_)); Disconnect(); } Datenpakete komprimierenDas Netzwerkprotokoll stellt eine rudimentäre Datenkompression zur Verfügung. Dabei können besonders große Datenpakete mit GZipStream komprimiert werden. GZip basiert auf dem Deflate-Algorithmus, der eine Kombination aus LZ77 und Huffman-Kodierung ist. Im Protokoll werden lediglich die Nutzdaten komprimiert, der Header im Datenpaket bleibt davon unberührt. Die Klasse
Wenn die Nutzdaten also sehr zufällig im Sinne von unerwartet verteilt sind, können die mit dem Deflate-Algorithmus komprimierten Daten unter Umständen sogar größer sein, als die nicht komprimierten Daten. Die Klasse /// <summary> /// Compresses a raw byte array. /// </summary> /// <param name="raw">The raw data, which should be compressed</param> /// <returns>Returns the compressed byte array</returns> public static byte[] Compress(byte[] raw) { using (MemoryStream memoryStream = new MemoryStream()) { using (GZipStream gzipStream = new GZipStream(memoryStream, CompressionMode.Compress, true)) { gzipStream.Write(raw, 0, raw.Length); } return memoryStream.ToArray(); } } Der Aufruf gestaltet sich denkbar einfach. Nach der Erzeugung eines neuen Datenpakets kann mit der Eigenschaft (engl. Property) byte[] data = new byte[65536]; // Random data with a high entropy should not be compressed //Random rand = new Random(); //rand.NextBytes(data); // Fill with some data for (int i = 0; i < data.Length; i++) { data[i] = (byte)i; } IPacket packet = Protocol.CreatePacket(SocketType.Stream); packet.Type = PacketType.ClientLoginInformation; packet.Source = myIPEndPoint; packet.Destination = remoteIPEndPoint; packet.Data = data; // Compress packet packet.Compressed = true; Mit dem Getter lässt sich auch leicht abfragen, ob die Nutzdaten komprimiert wurden oder nicht. Sender legen selbst fest, ob ihre Pakete komprimiert werden. Die Überprüfung und Interpretation der Daten liegt in der Hand des Empfängers. Die Indikation der Kompression ist Bestandteil der Flags im Header. Anhand des Indikators kann der Empfänger jederzeit prüfen, ob die Nutzdaten komprimiert vorliegen. Datenpakete verschlüsselnAlle Nutzdaten in den Paketen können bei Bedarf auch verschlüsselt werden. Im Protokoll steht dafür der Advanced Encryption Standard (AES) zur Verfügung. Es handelt sich um ein symmetrisches Kryptosystem, das als Nachfolger für DES bzw. 3DES im Oktober 2000 vom National Institute of Standards and Technology (NIST) als Standard bekannt gegeben wurde. AES ist in den USA für staatliche Dokumente mit höchster Geheimhaltungsstufe zugelassen. string data = "Never give up, keep your dreams alive."; IPacket packet = Protocol.CreatePacket(SocketType.Stream); packet.Type = PacketType.Message; packet.Source = myIPEndPoint; packet.Destination = remoteIPEndPoint; packet.Encoding = Encoding.Unicode; packet.Data = Encoding.Unicode.GetBytes(data); packet.Password = "Secret"; // Set password // Encrypt packet packet.Encrypted = true; Für die Verschlüsselung muss zuvor ein Passwort gesetzt werden. Anschließend werden die Daten mit dem Property ApplikationDie fertige Applikation mit weiteren nützlichen Klassen und Funktionen können Sie sich in gewohnter Weise in der Download-Rubrik herunterladen. Das Programm kann sehr leicht modifiziert und um weitere Nachrichtentypen erweitert werden. Auf diese Weise lässt sich auch schnell ein Chat-Server realisieren, der Nachrichten an alle eingeloggten Clienten weiterleitet, beliebige Dateien überträgt und direkte Verbindungen zwischen den Clienten herstellt. Das Anwendungsprotokoll ist dahingehend sehr flexibel konzipiert. |
||||||||||||||
Zuletzt aktualisiert am Mittwoch, den 28. März 2012 um 18:02 Uhr |
AUSWAHLMENÜ | ||||||||
|