Die Forderung nach automatischen Benachrichtigungen, sobald ein anderer Benutzer angezeigte Daten ändert, ist aus Benutzersicht sehr verständlich. Niemand will schließlich Zeit darin investieren, einen Datensatz zu aktualisieren, nur um im Nachhinein eine Meldung zu erhalten, dass der betroffene Datensatz in der Zwischenzeit von einem anderen Benutzer ebenfalls geändert wurde und nun die aktuellen eigenen Änderungen daher verworfen wurden. Solche und ähnliche Anforderungen treten immer dann auf, wenn geänderte Informationen an eine große Zahl an Empfängern übermittelt werden müssen.
Aus technischer Sicht handelt es sich dabei um einen Problemkreis ähnlich dem des Broadcasting oder Multicasting, also der Übermittlung der gleichen Information an alle Empfänger in einer bestimmten Gruppe (beispielsweise im gleichen IP-Subnetz) oder an einen vorher definierten Empfängerkreis, der sein Interesse an einer bestimmten Informationsart bekundet hat. Interessanterweise gibt es trotz der immer wiederkehrenden Notwendigkeit der Realisierung dieser Anforderungen keine Standardtechnologie, mit deren Hilfe sich diese ein für allemal und in allen Infrastrukturumgebungen lösen lassen würden. Die gute Nachricht ist aber: Es gibt Lösungswege. Sie müssen sie nur selbst implementieren.
Synchrone oder asynchrone Übermittlung
Doch lassen Sie mich Ihre Aufmerksamkeit zuerst auf einige Herausforderungen in diesem Problemkreis lenken. Eine der ersten Fragen, die Sie sich stellen müssen ist jene, ob die Übermittlung von Informationen synchron oder asynchron erfolgen soll. Im ersten Fall würde der Server sofort nach Übermittlung des Ereignisses wissen, dass die empfangenden Clients die Information erfolgreich abgearbeitet haben. Im zweiten Fall würde die sendende Anwendung davon nicht ausgehen können, da eventuell sogar eine Message-Queueing-Infrastruktur verwendet wird, die den erfolgreichen Versand von Notifikationen auch dann erlaubt, wenn die empfangende Anwendung (und sogar die Empfängermaschine) nicht laufen oder nicht erreichbar sind. In diesem Fall würden die Informationen einfach zwischengespeichert werden.Obwohl der erste Ansatz auf den zunächst vielleicht reizvoller erscheint - schließlich erfolgt die Zustellung anscheinend sicherer - birgt er viele Problemquellen, die die Stabilität und Skalierbarkeit Ihrer Applikationen entscheidend negativ beeinflussen können. Stellen Sie sich einfach vor, dass Ihre serverseitige Methode zur Speicherung von Kundendaten auch einige hundert Clientanwendungen benachrichtigen muss. In diesem Fall würde Ihre Serveranwendung entweder seriell oder parallel in mehreren Threads die Clients kontaktieren, die Informationen übermitteln und anschließend auf eine Bestätigung warten. Sollte nur eine einzige Clientanwendung nicht entsprechend schnell reagieren - oder zum Beispiel fehlerhafterweise eine MessageBox anzeigen - so kann Ihre Serveranwendung die ursprüngliche Anforderung zur Speicherung der Daten so lange nicht bestätigen, bis das jeweilige Problem behoben ist. Im konkreten Fall würde das bedeuten, dass so lange kein einziger Benutzer Ihrer Applikation Daten speichern kann, bis die MessageBox in der fehlerhaften Applikation bestätigt wurde. Dieses Verhalten - und eine derart große Abhängigkeit zu Clientanwendungen - ist im Normalfall nicht gewünscht, sodass synchrone Benachrichtigungsvarianten im allgemeinen ungeeignet sind. Die schließt die Verwendung von Events per .NET Remoting für Broadcast-Zwecke mit ein.
Erlaubter Informationsverlust
Auf Seite der asynchronen Protokolle gibt es verschiedene Möglichkeiten, um dieses Problem anzugehen. Die wichtigste Frage vor der Entscheidung für ein Protokoll ist die, mit welcher Sicherheit die Zustellung jedes einzelnen Ereignisses an jeden Empfänger erfolgen muss beziehungsweise soll. Ist es unter Umständen ausreichend, wenn die Benachrichtigung lediglich mit einer hohen Wahrscheinlichkeit erfolgt und Nachrichten in Einzelfällen aber verloren gehen können? Obwohl es auf den ersten Blick meist so aussieht, als ob eine Zustellungsgarantie in fast allen Anwendungsfällen notwendig ist, kann ein Verzicht darauf in vielen Fällen eine Vereinfachung der Applikationsinfrastruktur ermöglichen, da keine Notwendigkeit zur Installation von weiteren Infrastrukturkomponenten besteht.Zusätzlich zur Frage nach der Verwendung von zuverlässigen Protokollen stellt sich bei der Zustellung von asynchronen Nachrichten immer die Frage, ob die Sender- und Empfängerapplikationen zur gleichen Zeit laufen müssen oder ob Informationen in einem Queueing-System zwischengespeichert werden sollen. Doch lassen Sie mich ein paar Beispiele bringen, um diese Fragestellungen etwas zu konkretisieren. Für die oben angeführte Anforderung zur Aktualisierung von geänderten Daten reicht zumeist die Verwendung von nicht zuverlässigen Protokollen aus. Dadurch können derartige Applikationen in allen Netzwerktypen ohne weitere Voraussetzungen installiert werden. Im Normalfall können Sie sich für diese Art der Benachrichtigung immer dann entscheiden, wenn der Verlust einer Nachricht keine weiteren Auswirkungen auf die Korrektheit des Systems hat, sondern lediglich der Benutzerfreundlichkeit dient.
Bei der Übertragung von sensiblen Daten auf der anderen Seite - beispielsweise von Änderungen an Sicherheitseinstellungen und Benutzerberechtigungen - ist meist eine zuverlässige Übertragung notwendig, da der Verlust einer einzelnen Nachricht drastische Auswirkungen auf die Systemsicherheit haben könnte. Die letzte Gruppe der zuverlässigen Nachrichten, die zwischengespeichert werden, kommt meist dann zum Tragen, wenn die asynchronen Benachrichtigungen zur Synchronisation von Informationen eingesetzt werden. So können beispielsweise Änderungen an Preislisten auf diese Art an eine Vielzahl von mobilen Geräten von Außendienstmitarbeiter übermittelt werden. Hier ist die angeführte Zwischenspeicherung notwendig, da die Zielmaschinen bzw. Zielapplikationen zum Zeitpunkt des Informationsversands nicht zwangsläufig in jenem Moment mit dem Netzwerk verbunden und daher unter Umständen nicht erreichbar sind.
UDP für nichtkritische Anwendungen
Das der IP-Familie zugehörige UDP (User Datagram Protocol) bietet eine sehr einfache Möglichkeit, Nachrichten an mehrere Empfänger zu senden. Hierzu sind keine weiteren Infrastrukturkomponenten notwendig, da dieses Protokoll von allen TCP/IP-Implementierungen direkt angeboten wird. Das Besondere an UDP ist, dass es die Übermittlung von Nachrichten an das gesamte IP-Subnetz (LAN) des Senders ermöglicht. Auf Seite des Servers reicht es daher aus, eine einzelne Nachricht zu erstellen und zu senden. Diese Nachricht kann in Folge von allen horchenden Maschinen empfangen werden. Die dafür notwendigen Klassen finden Sie im .NET Framework im Namespace System.Net und System.Net.Sockets. Der einfache Versand von Nachrichten, basierend auf beliebigen Objekten, die mit [Serializable] markiert sind, kann wie in Listing 1 gezeigt erfolgen.Listing 1
Versand von Nachrichten an ein gesamtes IP-Subnetz
using System;using System.IO;using System.Net;using System.Net.Sockets;using System.Runtime.Serialization.Formatters.Binary;public class Sender{public static void Main(){Console.Write("Enter string to broadcast: ");String str = Console.ReadLine();SendMessageToSubnet(str, 10000);Console.ReadLine();}private static void SendMessageToSubnet(object msg, int port){MemoryStream ms = new MemoryStream();BinaryFormatter fmt = new BinaryFormatter();fmt.Serialize(ms, msg);ms.Seek(0, SeekOrigin.Begin);Socket sck = new Socket(AddressFamily.InterNetwork,SocketType.Dgram,ProtocolType.Udp);sck.Connect(new IPEndPoint( IPAddress.Broadcast,10000));sck.Send(ms.GetBuffer());sck.Close();}}
Auf Seite des Empfängers kann ähnlich verfahren werden, um die Nachrichten wieder zu deserialisieren. Sie finden ein Beispiel dazu in Listing 2.
Listing 2
Eine einfache Empfängeranwendung
using System;using System.IO;using System.Net.Sockets;using System.Net;using System.Runtime.Serialization.Formatters.Binary;public class Receiver{public static void Main(){int port = 10000;Socket sck = new Socket(AddressFamily.InterNetwork,SocketType.Dgram,ProtocolType.Udp);sck.Bind(new IPEndPoint( IPAddress.Any, port));BinaryFormatter fmt = new BinaryFormatter();byte[] buf = new byte[65000];while (true){int size = sck.Receive(buf);MemoryStream ms = new MemoryStream(buf,0,size);object msg = fmt.Deserialize(ms);Console.WriteLine("Received: {0}", msg.ToString());}}}
Beachten Sie aber, dass diese Art des Nachrichtenversandes nur funktioniert, wenn der Sender und alle Empfänger im gleichen IP-Subnetz sind, die Nachrichtengröße 64 KB nicht überschritten wird und der Verlust von einzelnen Nachrichten keine kritische Auswirkung auf Ihre Applikation hat. Sollten Sie mehr als ein Subnetz überbrücken wollen, können Sie aber einfach einen Rechner im zweiten Subnetz als Zwischenstelle verwenden. Dort könnte dann ein kleines Programm laufen, das direkt an diesen Rechner gesandte Nachrichten vom ersten Server annimmt und diese in sein lokales Subnetz weitersendet.
MSMQ, wenn nichts verloren gehen darf
Wenn aber - im Gegensatz zu den UDP-basierenden Beispielen - der Verlust von Nachrichten für Ihre Applikation kritisch ist, so bietet sich die Verwendung von MSMQ (Microsoft Message Queueing) an. MSMQ ist seit dem Option Pack für Windows NT 4.0 ein kostenloser optionaler Bestandteil aller Windows-Betriebssysteme, der jedoch in den meisten Fällen auf allen Sender- und Empfänger-Computern zusätzlich installiert werden muss. Die aktuellen Versionen (2.x und 3.0) von MSMQ haben keine Notwendigkeit für SQL Server oder Active Directory, sondern lassen sich auch in Workgroup-Konfigurationen problemlos verwenden. Seit Version 3.0 bietet MSMQ die Möglichkeit an, einzelne Nachrichten an eine Vielzahl von Empfängern zu senden. Anders als bei UDP müssen sich diese Empfänger nicht im gleichen Subnetz befinden. Es muss aber stattdessen beim Versand explizit jeder Empfänger angegeben werden. Die Warteschlangen innerhalb von MSMQ können Sie sich im Prinzip wie ein E-Mail-Postfach vorstellen. Im Gegensatz zu normalen E-Mails werden die Nachrichten jedoch von einem Programm empfangen und können auch in sehr hoher Geschwindigkeit übertragen werden. Die Übertragung von mehreren Tausend Nachrichten pro Sekunde ist in MSMQ-basierenden Anwendungen keine Seltenheit.Anders als bei Datenbanksystemen befindet sich der Nachrichtenspeicher nicht an einer Stelle zentralisiert, sondern wird von jedem Computer getrennt verwaltet. Im Normalfall lesen Applikationen daher immer die Nachrichten von ihrem lokalen Computer und senden Nachrichten an entfernte Maschinen. Wenn Sie also einen Server und 100 Clients mit ihrer Applikation bedienen möchten, so werden Sie zumindest mit 101 Warteschlagen arbeiten: eine auf dem Server, und jeweils eine auf jeder Clientmaschine. Um alle Clients zu benachrichtigen, sendet die Serveranwendung daher 100 Kopien der gleichen Nachricht an die 100 Empfängermaschinen. (Dies erledigt MSMQ aber automatisch für Sie im Hintergrund, ohne die Skalierbarkeit oder Stabilität Ihrer Applikation negativ zu beeinflussen.)
Die Anlage der Warteschlagen kann entweder bei der Programminstallation auf jedem Client, bei Programmstart (falls die Benutzerrechte dies erlauben) oder im Testfall auch manuell erfolgen. Für die automatische Erstellung können Sie die Funktion MessageQueue.Create() verwenden. Die manuelle Erzeugung von Warteschlagen erfolgt über ein MMC Snap-in, das Sie über Arbeitspatz | Eigenschaften | Verwalten starten. Dort erhalten Sie in der Kategorie Dienste und Anwendungen | Message Queueing Zugriff auf die MSMQ-Einstellungen. Bevor ich Ihnen ein Beispiel zu Versand und Empfang von Nachrichten zeige, möchte ich noch die Begriffe öffentliche Warteschlange (public queue) und private Warteschlage (private queue), die von MSMQ verwendet werden, klären. Die Unterscheidung betrifft nämlich nicht - wie manchmal fälschlicherweise angenommen - die Erreichbarkeit von Warteschlagen, sondern lediglich deren Registrierung in einem eventuell vorhandenen Active Directory. Sie können also sowohl Nachrichten an öffentliche als auch an private Warteschlagen von anderen Rechnern senden. Bei privaten Warteschlagen müssen Sie jedoch deren Namen kennen, während Sie öffentliche Warteschlagen im Active Directory suchen können.
Der Versand von Nachrichten per MSMQ gestaltet sich etwas anders als der von UDP-Paketen. Der größte Unterschied liegt darin, dass Sie bei MSMQ die Adresse jedes Empfängers explizit angeben müssen, während Sie bei UDP an ein gesamtes Subnetz senden können (zusätzlich zu der hier vorgestellten Variante bietet MSMQ noch die Möglichkeit, Nachrichten mittels IP Multicast zu verteilen. In diesem Fall würde auch nur ein einzelner Empfänger angegeben werden und die eigentliche Verteilung über die IP-Multicasting-Infrastruktur erfolgen.)
Die Adressierung erfolgt über die Angabe eines verketteten FormatNames, der die Adressen der einzelnen Queues, durch Komma getrennt, enthält. Ein Beispiel für einen derartigen Namen, der die private Queue Queue01 auf Server01 und die private Warteschlange Queue02 auf Server02 referenziert, lautet FormatName:direct=os:Server01\private$\Queue01,os:Server02\private$\Queue02.
Um beispielsweise eine Nachricht an die private Warteschlange Notifications der vier Maschinen Test01 bis Test04 zu versenden, können Sie Programmcode ähnlich dem verwenden, der in Listing 3 gezeigt wird. Sie müssen dazu Ihrem Projekt einen Verweis auf System.Messaging.DLL hinzufügen.
Listing 3
Versand einer MSMQ-Nachricht an mehrere Empfänger
using System;using System.Text;using System.Collections;using System.Messaging;class Sender{static void Main(string[] args){Console.Write("Enter string to broadcast:");String str = Console.ReadLine();ArrayList clients = new ArrayList();clients.Add("test01");clients.Add("test02");clients.Add("test03");clients.Add("test04");String formatName = BuildFormatName(clients);MessageQueue que = new MessageQueue(formatName);Message msg = new Message();msg.Formatter = new BinaryMessageFormatter();msg.Body = str;que.Send(msg);Console.ReadLine();}static string BuildFormatName(ArrayList clients){StringBuilder bld = new StringBuilder();bld.Append("FormatName:");foreach (String cli in clients){bld.Append("direct=os:");bld.Append(cli);bld.Append("\\private$\\NOTIFICATIONS");bld.Append(",");}bld.Remove(bld.Length-1,1);return bld.ToString();}}
Die in Listing 4 gezeigte Empfängerapplikation ist so geschrieben, dass sie per MessageQueue.Create() die jeweilige Warteschlange anlegt, sofern diese noch nicht vorhanden ist. Dieser Code ist nur zur Vollständigkeit gezeigt, da dessen Ausführung nur bei entsprechenden Benutzerberechtigungen möglich ist. Normalerweise werden Sie daher den Quellcode zur Erstellung von Warteschlangen eher in Ihr Setup-Projekt mit aufnehmen.
Listing 4
Empfangen von Nachrichten per MSMQ
using System;using System.Messaging;class Receiver{static void Main(string[] args){String queuename = @".\private$\NOTIFICATIONS";if (!MessageQueue.Exists(queuename)){MessageQueue.Create(queuename);}MessageQueue que = new MessageQueue(queuename);que.Formatter = new BinaryMessageFormatter();while (true){using (Message msg = que.Receive()){String str = (String) msg.Body;Console.WriteLine("Received: {0}", str);}}}}


