Dieses Kapitel enthält Informationen zum CRNP (Cluster Reconfiguration Notification Protocol). Das CRNP ermöglicht die “Cluster-Unterstützung” von Failover- und Scalable-Anwendungen. Insbesondere stellt das CRNP einen Mechanismus bereit, mit dessen Hilfe sich die Anwendungen für Sun Cluster-Rekonfigurationsereignisse registrieren und anschließend asynchrone Benachrichtigungen darüber erhalten können. Datendienste, die innerhalb des Clusters, und Anwendungen, die außerhalb des Clusters ausgeführt werden, können sich für die Ereignisbenachrichtigung registrieren. Ereignisse werden generiert, wenn sich die Mitgliedschaft in einem Cluster ändert und wenn sich der Zustand einer Ressourcengruppe oder einer Ressource ändert.
CRNP stellt Mechanismen und Dämonen bereit, die Cluster-Rekonfigurationsereignisse generieren, diese durch den Cluster routen und sie an interessierte Clients senden.
Der cl_apid-Dämon arbeitet interaktiv mit den Clients zusammen. Sun Cluster-Ressourcengruppen-Manager (RGM) generiert Cluster-Rekonfigurationsereignisse. Diese Dämone verwenden syseventd( 1M), um Ereignisse an jeden lokalen Knoten zu übertragen. Der cl_apid-Dämon verwendet XML (Extensible Markup Language) über TCP/IP, um mit den interessierten Clients zu kommunizieren.
Das folgende Diagramm stellt einen Überblick über den Ereignisfluss zwischen den CRNP-Komponenten dar. In diesem Diagramm wird ein Client auf Cluster-Knoten 2 ausgeführt, und der andere Client läuft auf einem Computer, der nicht zum Cluster gehört.
CRNP definiert die Anwendungs-, Darstellungs und Sitzungsschichten des standardmäßigen OSI-Protokollstapels mit sieben Ebenen (OSI, Open System Interconnect). Die Transportschicht muss TCP und die Netzwerkebene muss IP sein. Das CRNP ist von den Datenverbindungs- und realen Schichten unabhängig. Alle Meldungen der Anwendungsschicht, die im CRNP ausgetauscht werden, basieren auf XML 1.0.
Die Clients leiten die Kommunikation ein, indem sie eine Registrierungsmeldung (SC_CALLBACK_RG) an den Server senden. Diese Registrierungsmeldung gibt die Ereignistypen an, über welche die Clients benachrichtigt werden möchten, sowie den Port, an den die Ereignisse gesendet werden. Das Quell-IP der Registrierungsverbindung und der angegebene Port bilden zusammen die Rückmeldeadresse.
Immer wenn im Cluster ein Ereignis eintritt, das für den Client von Interesse ist, kontaktiert der Server den Client über die Rückmeldeadresse (IP und Port) und stellt dem Client das Ereignis (SC_EVENT) zu. Der Server ist hoch verfügbar und wird im Cluster selbst ausgeführt. Der Server speichert Clientregistrierungen in einem Speicher, der auch bei einem Neustart des Clusters nicht gelöscht wird.
Clients können sich deregistrieren, indem sie eine Registrierungsmeldung (SC_CALLBACK_RG mit einer REMOVE_CLIENT-Meldung) an den Server senden. Nachdem der Client eine SC_REPLY-Meldung vom Server erhalten hat, beendet er die Verbindung.
Das folgende Diagramm zeigt den Kommunikationsfluss zwischen einem Client und einem Server.
Das CRNP verwendet drei XML-basierte Meldungstypen, die in der folgenden Tabelle beschrieben werden. Weitere Einzelheiten zu diesen Meldungstypen werden weiter unten in diesem Kapitel beschrieben. Auch auf die Syntax wird später in diesem Kapitel noch genauer eingegangen.
Meldungstyp |
Beschreibung |
---|---|
SC_CALLBACK_REG |
Diese Meldung kann in vier Formen vorkommen: ADD_CLIENT, REMOVE_CLIENT, ADD_EVENTS und REMOVE_EVENTS. Jede dieser Formen enthält folgende Informationen:
Die Formen ADD_CLIENT, ADD_EVENTS und REMOVE_EVENTS enthalten daneben eine uneingeschränkte Liste von Ereignistypen. Jeder Typ enthält folgende Informationen:
Die Ereignisklasse und die Ereignisunterklasse definieren zusammen einen einmaligen “Ereignistyp.” Die DTD (Document Type Definition), von der aus die SC_CALLBACK_REG-Klassen generiert werden, ist SC_CALLBACK_REG. Diese DTD wird in Anhang F genauer beschrieben. |
SC_EVENT |
Diese Meldung enthält folgende Informationen:
Die Werte in einem SC_EVENT werden nicht eingegeben. Die DTD (Document Type Definition), von der aus die SC_EVENT-Klassen generiert werden, ist SC_EVENT. Diese DTD wird in Anhang F genauer beschrieben. |
SC_REPLY |
Diese Meldung enthält folgende Informationen:
Die DTD (Document Type Definition), von der aus die SC_REPLY-Klassen generiert werden, ist SC_REPLY. Diese DTD wird in Anhang F genauer beschrieben. |
Dieser Abschnitt beschreibt, wie ein Verwalter den Server einrichtet, wie Clients identifiziert werden, wie Informationen über die Anwendungsschicht und die Sitzungsebene gesendet werden, sowie die Fehlerbedingungen.
Der Systemadministrator muss den Server mit einer hoch verfügbaren IP-Adresse, also einer IP-Adresse, die nicht an einen bestimmten Rechner im Cluster gebunden ist, sowie mit einer Port-Nummer konfigurieren. Er muss diese Netzwerkadresse an mögliche Clients veröffentlichen. Das CRNP definiert nicht, wie der Servername den Clients verfügbar gemacht wird. Die Verwalter können entweder einen Namensdienst einsetzen, mit dessen Hilfe die Clients die Netzwerkadresse des Servers dynamisch finden können, oder sie fügen den Netzwerknamen einer vom Client gelesenen Konfigurationsdatei hinzu. Der Server wird im Cluster als Failover-Ressourcentyp ausgeführt.
Jeder Client wird durch seine Rückmeldeadresse, also die IP-Adresse und Port-Nummer, eindeutig identifiziert. Der Port wird in den SC_CALLBACK_REG-Meldungen angegeben, und die IP-Adresse wird aus der TCP-Registrierungsverbindung abgerufen. Das CRNP geht davon aus, dass alle folgenden SC_CALLBACK_REG-Meldungen mit der gleichen Rückmeldeadresse von demselben Client kommen, auch wenn der Quell-Port, von dem aus die Meldungen gesendet werden, unterschiedlich ist.
Ein Client leitet die Registrierung ein, indem er eine TCP-Verbindung mit der IP-Adresse und Port-Nummer des Servers herstellt. Wenn die TCP-Verbindung hergestellt und schreibbereit ist, muss der Client die Registrierungsmeldung senden. Die Registrierungsmeldung muss eine korrekt formatierte SC_CALLBACK_REG-Meldung sein, die weder vor noch nach der Meldung zusätzliche Bytes enthält.
Nach Schreiben aller Bytes an den Strom muss der Client die Verbindung aufrechterhalten, um die Antwort des Servers erhalten zu können. Wenn der Client die Meldung nicht korrekt formatiert, wird er vom Server nicht registriert, und dieser sendet eine Fehlerantwort an den Client. Wenn der Client die Socketverbindung beendet, bevor der Server eine Antwort gesendet hat, wird er dennoch vom Server ordnungsgemäß registriert.
Ein Client kann jederzeit Kontakt mit dem Server aufnehmen. Bei jeder Kontaktaufnahme mit dem Server muss der Client eine SC_CALLBACK_REG-Meldung senden. Wenn der Server eine Meldung erhält, die fehlerhaft, beschädigt oder ungültig ist, sendet er eine Fehlerantwort an den Client.
Ein Client kann keine ADD_EVENTS-, REMOVE_EVENTS- oder REMOVE_CLIENT-Meldung senden, bevor seine ADD_CLIENT-Meldung gesendet wurde. Er kann auch keine REMOVE_CLIENT-Meldung senden, bevor seine ADD_CLIENT-Meldung gesendet wurde.
Wenn ein Client eine ADD_CLIENT-Meldung sendet, obwohl er bereits registriert ist, kann der Server diese Meldung tolerieren. In diesem Fall ersetzt der Server die alte Client-Registrierung stillschweigend durch die neue Client-Registrierung, die in der zweiten ADD_CLIENT-Meldung angegeben ist.
In den meisten Fällen registriert sich ein Client einmal beim Server. Dies geschieht beim Starten des Clients mittels Senden einer ADD_CLIENT-Meldung. Ebenso deregistriert sich ein Client nur einmal, indem er eine REMOVE_CLIENT-Meldung an den Server sendet. Das CRNP bietet jedoch größere Flexibilität für diejenigen Clients, die ihre Ereignistypliste dynamisch ändern möchten.
Jede ADD_CLIENT-, ADD_EVENTS- und REMOVE_EVENTS-Meldung enthält eine Ereignisliste. Die folgende Tabelle beschreibt die Ereignistypen, die das CRNP akzeptiert, einschließlich der erforderlichen Namens- und Wertepaare.
eine REMOVE_EVENTS-Meldung sendet, die einen oder mehrere Ereignistypen angibt, für die der Client nicht zuvor registriert wurde, oder
sich zweimal für den gleichen Ereignistyp registriert,
Klasse und Unterklasse |
Namens- und Wertepaare |
Beschreibung |
---|---|---|
EC_Cluster ESC_cluster_membership |
Erforderlich: Keine Optional: Keine |
Wird für alle Änderungsereignisse bezüglich der Cluster-Mitgliedschaft registriert (Knotenversagen oder -beitritt) |
EC_Cluster ESC_cluster_rg_state |
Eines erforderlich, wie folgt: rg_name Werttyp: Zeichenkette Optional: Keine |
Wird für alle Zustandsänderungsereignisse für Ressourcengruppe Name registriert |
EC_Cluster ESC_cluster_r_state |
Eines erforderlich, wie folgt: r_name Werttyp: Zeichenkette Optional: Keine |
Wird für alle Zustandsänderungsereignisse für Ressource Name registriert |
EC_Cluster Keine |
Erforderlich: Keine Optional: Keine |
Wird für alle Sun Cluster-Ereignisse registriert |
Nach Verarbeiten der Registrierung sendet der Server die SC_REPLY-Meldung. Der Server sendet diese Meldung über die offene TCP-Verbindung des Clients, über die er die Registrierungsanforderung erhalten hatte. Daraufhin beendet der Server die Verbindung. Der Client muss die TCP-Verbindung so lange offen halten, bis er die SC_REPLY-Meldung vom Server erhalten hat.
Der Client führt zum Beispiel folgende Aktionen aus:
Herstellen einer TCP-Verbindung mit dem Server,
Warten, bis die Verbindung “schreibbereit” ist,
Senden einer SC_CALLBACK_REG-Meldung mit einer ADD_CLIENT-Meldung,
Warten auf eine SC_REPLY-Meldung,
Erhalten einer SC_REPLY-Meldung,
Erhalten einer Anzeige, dass der Server die Verbindung beendet hat (Lesen von 0 Bytes vom Socket),
Beenden der Verbindung.
Herstellen einer TCP-Verbindung mit dem Server,
Warten, bis die Verbindung “schreibbereit” ist,
Senden einer SC_CALLBACK_REG -Meldung mit einer REMOVE_CLIENT-Meldung,
Warten auf eine SC_REPLY-Meldung,
Erhalten einer SC_REPLY-Meldung,
Erhalten einer Anzeige, dass der Server die Verbindung beendet hat (Lesen von 0 Bytes vom Socket),
Beenden der Verbindung.
Jedes Mal, wenn der Server eine SC_CALLBACK_REG-Meldung von einem Client erhält, sendet er eine SC_REPLY-Meldung über dieselbe offene Verbindung. Diese Meldung gibt an, ob der Vorgang erfolgreich war oder fehlgeschlagen ist. SC_REPLY-XML-DTD enthält die XML-Dokumenttypdefinition einer SC_REPLY-Meldung sowie der möglichen Fehlermeldungen, die darin enthalten sein können.
Eine SC_REPLY-Meldung gibt an, ob ein Vorgang erfolgreich war oder fehlgeschlagen ist. Diese Meldung enthält die Version der CRNP-Protokollmeldung, einen Statuscode und eine Statusmeldung, die den Statuscode detaillierter beschreibt. In der folgenden Tabelle werden die möglichen Werte für den Statuscode aufgelistet.
Statuscode |
Beschreibung |
---|---|
OK |
Die Meldung wurde erfolgreich verarbeitet. |
RETRY |
Die Client-Registrierung wurde vom Server aufgrund eines temporären Fehlers zurückgewiesen. Der Client sollte einen weiteren Registrierungsversuch mit anderen Parametern unternehmen. |
LOW_RESOURCE |
Die Cluster-Ressourcen sind ausgelastet, und der Client muss für einen erneuten Registrierungsversuch einen späteren Zeitpunkt abwarten. Eine andere Möglichkeit wäre, dass der Systemverwalter die entsprechenden Cluster-Ressourcen erhöht. |
SYSTEM_ERROR |
Ein schwerwiegendes Problem ist aufgetreten. Nehmen Sie Kontakt mit dem Systemverwalter für den Cluster auf. |
FAIL |
Die Autorisierung ist fehlgeschlagen, oder ein sonstiges Problem hat das Scheitern der Registrierung verursacht. |
MALFORMED |
Die XML-Anforderung war fehlerhaft und konnte nicht analysiert werden. |
INVALID |
Die XML-Anforderung war ungültig (entspricht nicht der XML-Spezifikation). |
VERSION_TOO_HIGH |
Die Meldungsversion war für eine erfolgreiche Verarbeitung zu hoch. |
VERSION_TOO_LOW |
Die Meldungsversion war für eine erfolgreiche Meldungsverarbeitung zu niedrig. |
Unter normalen Bedingungen erhält ein Client, der eine SC_CALLBACK_REG-Meldung sendet, eine Antwort, die eine erfolgreiche oder fehlgeschlagene Registrierung angibt.
Bei der Client-Registrierung kann jedoch auf der Serverseite eine Fehlerbedingung auftreten, die den Server davon abhält, eine SC_REPLY-Meldung an den Client zu senden. In diesem Fall kann die Registrierung entweder vor Auftreten der Fehlerbedingung erfolgreich verlaufen sein, oder sie konnte noch nicht verarbeitet werden.
Da der Server auf dem Cluster als Failover- oder hoch verfügbarer Server auf dem Cluster eingesetzt werden muss, bedeutet diese Fehlerbedingung nicht, dass der Dienst beendet wird. Es kann sogar sein, dass der Server bald beginnt, Ereignisse an den neu registrierten Client zu senden.
Um diese Fehlerbedingungen zu beheben, muss der Client folgendermaßen verfahren:
Er muss auf Anwendungsebene eine Zeitüberschreitung für die Registrierungsverbindung einsetzen, die auf eine SC_REPLY-Meldung wartet. Nach Ablauf der Zeitüberschreitung muss der Client einen erneuten Registrierungsversuch unternehmen.
Er muss beginnen, an der Rückmelde-IP-Adresse und Port-Nummer Ereigniszustellungen abzuhören, bevor er sich für die Ereignisrückmeldungen registriert. Der Client muss parallel eine Registrierungs-Bestätigungsmeldung und Ereigniszustellungen abwarten. Wenn der Client Ereignisse erhält, bevor die Bestätigungsmeldung bei ihm eingegangen ist, sollte er die Registrierungsverbindung stillschweigend beenden.
Sobald im Cluster Ereignisse generiert werden, stellt der CRNP-Server diese allen Clients zu, die Ereignisse des entsprechenden Typs angefordert haben. Die Zustellung besteht im Senden einer SC_EVENT-Meldung an die Rückmeldeadresse des Clients. Jedes Ereignis wird über eine neue TCP-Verbindung zugestellt.
Unmittelbar nach der Client-Registrierung für einen Ereignistyp sendet der Server über eine SC_CALLBACK_REG-Meldung mit einer ADD_CLIENT-Meldung bzw. einer ADD_EVENT-Meldung dem Client das neueste Ereignis des entsprechenden Typs. Der Client kann so den aktuellen Zustand des Systems feststellen, von dem die nachfolgenden Ereignisse kommen.
Wenn der Server eine TCP-Verbindung mit dem Client herstellt, sendet er genau eine SC_EVENT-Meldung über die Verbindung. Daraufhin gibt der Server eine Vollduplex-Beendigung aus.
Der Client führt zum Beispiel folgende Aktionen aus:
Abwarten einer vom Server hergestellten TCP-Verbindung,
Akzeptieren der eingehenden Verbindung vom Server,
Abwarten einer SC_EVENT-Meldung,
Lesen der SC_EVENT-Meldung,
Erhalten einer Anzeige, dass der Server die Verbindung beendet hat (Lesen von 0 Bytes vom Socket),
Beenden der Verbindung.
Wenn sich alle Clients registriert haben, müssen sie jederzeit ihre Rückmeldeadressen (IP-Adresse und Port-Nummer) abhören, um eine eingehende Verbindung für die Ereigniszustellung abzuwarten.
Wenn der Server keinen Kontakt mit dem Client aufnehmen kann, um ein Ereignis zuzustellen, versucht der Server eine vom Benutzer definierte Anzahl von Malen innerhalb eines definierten Zeitintervalls erneut, das Ereignis zuzustellen. Wenn alle Versuche fehlschlagen, wird der Client aus der Client-Liste des Servers entfernt. Der Client muss sich dann erneut registrieren, indem er eine weitere SC_CALLBACK_REG-Meldung mit einer ADD_CLIENT-Meldung sendet. Erst dann kann er weitere Ereignisse erhalten.
Innerhalb des Clusters besteht eine Gesamtreihenfolge für die Ereignisgenerierung, die in der Reihenfolge der Zustellung an jeden Client eingehalten wird. Wenn also Ereignis A im Cluster vor Ereignis B generiert wird, erhält der Client X das Ereignis A vor dem Ereignis B. Diese Gesamtreihenfolge der Ereigniszustellung an alle Clients wird jedoch nicht eingehalten. Das heißt, dass Client Y beide Ereignisse A und B erhalten kann, bevor Client X das Ereignis A erhält. Dadurch wird sichergestellt, dass langsame Clients nicht die Zustellung an alle Clients aufhalten.
Alle Ereignisse, die der Server zustellt (mit Ausnahme des ersten Ereignisses für eine Unterklasse und von Ereignissen, die auf Serverfehler folgen), sind eine Reaktion auf die tatsächlichen Ereignisse, die der Cluster generiert, es sei denn, beim Server tritt ein Fehler auf, durch den er im Cluster generierte Ereignisse nicht erfasst. In diesem Fall generiert der Server ein Ereignis für jeden Ereignistyp, das den aktuellen Zustand des Systems für diesen Typ darstellt. Jedes Ereignis wird an Clients gesendet, die Interesse an diesem Ereignistyp registriert haben.
Die Ereigniszustellung folgt der “Mindestens einmal”-Semantik. Das heißt, dass der Server das Ereignis mehr als einmal an einen Client senden kann. Dies muss in Fällen zugelassen sein, in denen der Server vorübergehend heruntergefahren wird und nach erneutem Herauffahren nicht feststellen kann, ob der Client die neuesten Informationen erhalten hat.
Die SC_EVENT-Meldung enthält die eigentliche Meldung, die im Cluster generiert wird, übertragen in das SC_EVENT-XML-Meldungsformat. Die folgende Tabelle beschreibt die vom CRNP zugestellten Ereignistypen, einschließlich der Namens- und Wertepaare, Herausgeber und Hersteller.
Klasse und Unterklasse |
Herausgeber und Hersteller |
Namens- und Wertepaare |
Anmerkungen |
---|---|---|---|
EC_Cluster ESC_cluster_membership |
Herausgeber: rgm Hersteller: SUNW |
Name: node_list Werttyp: Zeichenketten-Array Name: state_list Werttyp: Zeichenketten-Array |
Die Positionen der Array-Elemente für state_list sind mit denjenigen der node_list synchronisiert. Das heißt, dass der Zustand für den im node_list-Array zuerst aufgelisteten Knoten der erste Zustand im state_list-Array ist. Die state_list enthält nur in ASCII dargestellte Ziffern. Jede Ziffer stellt die aktuelle Zusammensetzungsnummer für diesen Knoten im Cluster dar. Wenn die Nummer die gleiche wie die in einer vorhergehenden Meldung erhaltene Nummer ist, hat sich die Beziehung des Knotens zum Cluster nicht geändert (gelöscht, beigetreten bzw. erneut beigetreten). Wenn die Zusammensetzungsnummer –1 ist, so ist der Knoten kein Cluster-Mitglied. Wenn die Zusammensetzungsnummer keine negative Zahl ist, handelt es sich beim Knoten um ein Cluster-Mitglied. Zusätzliche Namen, die mit ev_ beginnen, und deren zugeordnete Werte können vorhanden sein, sind aber nicht für die Verwendung durch den Client vorgesehen. |
EC_Cluster ESC_cluster_rg_state |
Herausgeber: rgm Hersteller: SUNW |
Name: rg_name Werttyp: Zeichenkette Name: node_list Werttyp: Zeichenketten-Array Name: state_list Werttyp: Zeichenketten-Array |
Die Positionen der Array-Elemente für state_list sind mit denjenigen der node_list synchronisiert. Das heißt, dass der Zustand für den im node_list-Array zuerst aufgelisteten Knoten der erste Zustand im state_list-Array ist. Die state_list enthält Zeichenkettendarstellungen des Zustands der Ressourcengruppe. Gültige Werte sind Werte, die mit den Befehlen scha_cmds(1HA) abgerufen werden können. Zusätzliche Namen, die mit ev_ beginnen, und deren zugeordnete Werte können vorhanden sein, sind aber nicht für die Verwendung durch den Client vorgesehen. |
EC_Cluster ESC_cluster_r_state |
Herausgeber: rgm Hersteller: SUNW |
Drei erforderlich, wie folgt: Name: r_name Werttyp: Zeichenkette Name: node_list Werttyp: Zeichenketten-Array Name: state_list Werttyp: Zeichenketten-Array |
Die Positionen der Array-Elemente für state_list sind mit denjenigen der node_list synchronisiert. Das heißt, dass der Zustand für den im node_list-Array zuerst aufgelisteten Knoten der erste Zustand im state_list-Array ist. Die state_list enthält Zeichenkettendarstellungen des Zustands der Ressource. Gültige Werte sind Werte, die mit den Befehlen scha_cmds(1HA) abgerufen werden können. Zusätzliche Namen, die mit ev_ beginnen, und deren zugeordnete Werte können vorhanden sein, sind aber nicht für die Verwendung durch den Client vorgesehen. |
Der Server authentifiziert einen Client mit einer Form von TCP-Wrappern. Die Quell-IP-Adresse der Registrierungsmeldung, die auch als Rückmelde-IP-Adresse dient, an die Ereignisse zugestellt werden, muss sich in der Liste der zulässigen Clients für den Server befinden. Die Quell-IP-Adresse und Registrierungsmeldung darf sich nicht in der Liste der abgewiesenen Clients befinden. Wenn sich die Quell-IP-Adresse und Registrierung nicht in der Liste befinden, weist der Server die Anforderung zurück und gibt eine Fehlerantwort an den Client aus.
Wenn der Server eine SC_CALLBACK_REG ADD_CLIENT-Meldung erhält, muss die Quell-IP-Adresse der nachfolgenden SC_CALLBACK_REG-Meldungen für diesen Client derjenigen der ersten Meldung entsprechen. Wenn der CRNP-Server eine SC_CALLBACK_REG erhält, die dieser Anforderung nicht entspricht, hat der Server zwei Möglichkeiten:
Er ignoriert die Anforderung und sendet eine Fehlerantwort an den Client; oder
Er nimmt an, dass die Anforderung von einem neuen Client stammt (abhängig vom Inhalt der SC_CALLBACK_REG-Meldung).
Clients sollten den Server auf ähnliche Weise authentisieren. Die Clients brauchen nur die Ereigniszustellungen von einem Server zu akzeptieren, dessen Quell-IP-Adresse und Port-Nummer der vom Client für die Registrierung verwendeten IP-Adresse und Port-Nummer entsprechen.
Da davon ausgegangen wird, dass sich die Clients des CRNP-Dienstes hinter einem Firewall befinden, der den Cluster schützt, stellt das CRNP keine weiteren Sicherheitsmechanismen bereit.
Das folgende Beispiel zeigt, wie eine einfache Java-Anwendung mit dem Namen CrnpClient, die das CRNP verwendet, entwickelt werden kann. Die Anwendung wird für Ereignisrückmeldungen bei dem CRNP-Server auf dem Cluster registriert, hört Ereignisrückmeldungen ab und verarbeitet die Ereignisse, indem sie deren Inhalt druckt. Vor der Beendigung deregistriert die Anwendung ihre Anforderung von Ereignisrückmeldungen.
Beachten Sie folgende Punkte beim Untersuchen des vorliegenden Beispiels.
Die Beispielanwendung führt XML-Generierung und -Analyse mit JAXP (Java API for XML Processing) aus. Dieses Beispiel zeigt nicht, wie JAXP verwendet wird. JAXP wird unter http://java.sun.com/xml/jaxp/index.html genauer beschrieben.
Dieses Beispiel stellt Teile einer vollständigen Anwendung vor. Die ganze Anwendung finden Sie in Anhang G. Um bestimmte Konzepte deutlicher darzustellen, weicht das in diesem Kapitel beschriebene Beispiel leicht von der in Anhang G vorgestellten vollständigen Anwendung ab.
Der Kürze halber werden die Kommentare im Beispielcode in diesem Kapitel ausgelassen. Die vollständige Anwendung in Anhang G enthält Kommentare.
Die in diesem Beispiel gezeigte Anwendung reagiert auf die meisten Fehlerbedingungen, indem die Anwendung einfach beendet wird. Eine reale Anwendung muss Fehlern gegenüber robuster reagieren können.
Zunächst muss die Umgebung konfiguriert werden.
Laden Sie JAXP und die richtige Version des Java-Compilers und der Virtual Machine herunter, und installieren Sie sie.
Anweisungen hierzu finden Sie unter http://java.sun.com/xml/jaxp/index.html.
Für dieses Beispiel ist Java 1.3.1 bzw. eine höhere Java-Version erforderlich.
Vergewissern Sie sich, dass Sie einen classpath in der Kompilierungsbefehlszeile angegeben haben, damit der Compiler die JAXP-Klassen finden kann. Geben Sie vom Verzeichnis der Quelldatei aus Folgendes ein:
% javac -classpath JAXP_ROOT/dom.jar:JAXP_ROOTjaxp-api. \ jar:JAXP_ROOTsax.jar:JAXP_ROOTxalan.jar:JAXP_ROOT/xercesImpl \ .jar:JAXP_ROOT/xsltc.jar -sourcepath . QUELL_DATEINAME.java |
wobei JAXP_ROOT der absolute bzw. relative Pfad zu dem Verzeichnis ist, in dem sich die JAXP-JAR-Dateien befinden und QUELL_DATEINAME der Name der Java-Quelldatei.
Beim Ausführen der Anwendung geben Sie den classpath an, damit die Anwendung die richtigen JAXP-Klassendateien laden kann. Beachten Sie, dass der erste Pfad in classpath das aktuelle Verzeichnis ist:
java -cp .:JAXP_ROOT/dom.jar:JAXP_ROOTjaxp-api. \ jar:JAXP_ROOTsax.jar:JAXP_ROOTxalan.jar:JAXP_ROOT/xercesImpl \ .jar:JAXP_ROOT/xsltc.jar QUELL_DATEINAME ARGUMENTE |
Damit ist die Umgebung konfiguriert, und Sie können die Anwendung entwickeln.
In diesem Teil des Beispiels erstellen Sie eine Basisklasse mit dem Namen CrnpClient, deren Hauptmethode die Befehlszeilenargumente analysiert und ein CrnpClient-Objekt erstellt. Dieses Objekt übergibt die Befehlszeilenargumente an die Klasse, wartet, bis der Benutzer die Anwendung beendet, ruft shutdown für CrnpClient auf und wird dann beendet.
Der Konstruktor der CrnpClient-Klasse muss folgende Aufgaben ausführen:
Einrichten der XML-Verarbeitungsobjekte.
Erstellen eines Threads, der Ereignisrückmeldungen abhört.
Kontaktaufnahme mit dem CRNP-Server und Registrierung für Ereignisrückmeldungen.
Erstellen Sie den Java-Code, der die vorstehende Logik implementiert.
Das folgende Beispiel zeigt den Hauptcode für die CrnpClient-Klasse. Die Implementierungen der vier Hilfsmethoden, die im Konstruktor referenziert werden, sowie die Methoden zum Herunterfahren werden später erläutert. Beachten Sie, dass der Code gezeigt wird, mit dem alle benötigten Pakete importiert werden.
import javax.xml.parsers.*; import javax.xml.transform.*; import javax.xml.transform.dom.*; import javax.xml.transform.stream.*; import org.xml.sax.*; import org.xml.sax.helpers.*; import org.w3c.dom.*; import java.net.*; import java.io.*; import java.util.*; class CrnpClient { public static void main(String []args) { InetAddress regIp = null; int regPort = 0, localPort = 0; try { regIp = InetAddress.getByName(args[0]); regPort = (new Integer(args[1])).intValue(); localPort = (new Integer(args[2])).intValue(); } catch (UnknownHostException e) { System.out.println(e); System.exit(1); } CrnpClient client = new CrnpClient(regIp, regPort, localPort, args); System.out.println("Drücken Sie die Eingabetaste, um die Demo zu beenden..."); try { System.in.read(); } catch (IOException e) { System.out.println(e.toString()); } client.shutdown(); System.exit(0); } public CrnpClient(InetAddress regIpIn, int regPortIn, int localPortIn, String []clArgs) { try { regIp = regIpIn; regPort = regPortIn; localPort = localPortIn; regs = clArgs; setupXmlProcessing(); createEvtRecepThr(); registerCallbacks(); } catch (Exception e) { System.out.println(e.toString()); System.exit(1); } } public void shutdown() { try { unregister(); } catch (Exception e) { System.out.println(e); System.exit(1); } } private InetAddress regIp; private int regPort; private EventReceptionThread evtThr; private String regs[]; public int localPort; public DocumentBuilderFactory dbf; } |
Mitgliedsvariablen werden weiter unten detaillierter behandelt.
Anhand des Codes in Anhang G können Sie sehen, wie die Befehlszeilenargumente analysiert werden.
Im Code müssen Sie sicherstellen, dass der Ereignisempfang über einen eigenen Thread ausgeführt wird, so dass die Anwendung anderweitig weiterarbeiten kann, wenn der Thread blockiert ist und auf Ereignisrückmeldungen wartet.
Das Einrichten des XML wird weiter unten erläutert.
Definieren Sie im Code eine Thread-Unterklasse mit dem Namen EventReceptionThread, die ein ServerSocket erstellt und die an dieses Socket ankommenden Ereignisse abwartet.
In diesem Teil des Beispielcodes werden Ereignisse weder gelesen noch verarbeitet. Das Lesen und Verarbeiten von Ereignissen wird später behandelt. EventReceptionThread erstellt ein ServerSocket über eine Internetworking-Protokolladresse mit Platzhalter. EventReceptionThread ist auch mit dem CrnpClient-Objekt referenziert, so dass EventReceptionThread Ereignisse zur Verarbeitung an das CrnpClient-Objekt senden kann.
class EventReceptionThread extends Thread { public EventReceptionThread(CrnpClient clientIn) throws IOException { client = clientIn; listeningSock = new ServerSocket(client.localPort, 50, InetAddress.getLocalHost()); } public void run() { try { DocumentBuilder db = client.dbf.newDocumentBuilder(); db.setErrorHandler(new DefaultHandler()); while(true) { Socket sock = listeningSock.accept(); // Ereignis aus sock-Strom erstellen und verarbeiten sock.close(); } // UNERREICHBAR } catch (Exception e) { System.out.println(e); System.exit(1); } } /* private Mitgliedsvariablen */ private ServerSocket listeningSock; private CrnpClient client; } |
Nachdem Sie nun gesehen haben, wie die EventReceptionThread-Klasse funktioniert, erstellen Sie ein createEvtRecepThr-Objekt:
private void createEvtRecepThr() throws Exception { evtThr = new EventReceptionThread(this); evtThr.start(); } |
Die Registrierung besteht aus folgenden Schritten:
Öffnen eines Basis-TCP-Sockets für das Internetworking-Protokoll und den Port für die Registrierung.
Erstellen der XML-Registrierungsmeldung.
Senden der XML-Registrierungsmeldung an das Socket.
Lesen der XML-Antwortmeldung vom Socket.
Schließen des Sockets.
Erstellen Sie den Java-Code, der die vorstehende Logik implementiert.
Das folgende Beispiel zeigt die Implementierung der registerCallbacks-Methode der CrnpClient-Klasse, die vom CrnpClient-Konstruktor aufgerufen wird. Die Aufrufe an createRegistrationString() und readRegistrationReply() werden später eingehender beschrieben.
regIp und regPort sind Objektmitglieder, die vom Konstruktor konfiguriert werden.
private void registerCallbacks() throws Exception { Socket sock = new Socket(regIp, regPort); String xmlStr = createRegistrationString(); PrintStream ps = new PrintStream(sock.getOutputStream()); ps.print(xmlStr); readRegistrationReply(sock.getInputStream(); sock.close(); } |
Implementieren Sie die unregister-Methode. Diese Methode wird von der shutdown-Methode von CrnpClient aufgerufen. Die Implementierung von createUnregistrationString wird später eingehender beschrieben.
private void unregister() throws Exception { Socket sock = new Socket(regIp, regPort); String xmlStr = createUnregistrationString(); PrintStream ps = new PrintStream(sock.getOutputStream()); ps.print(xmlStr); readRegistrationReply(sock.getInputStream()); sock.close(); } |
Nach dem Konfigurieren der Anwendungsstruktur und Schreiben des gesamten Netzwerkcodes können Sie nun den Code schreiben, der XML generiert und analysiert. Schreiben Sie zunächst den Code, der die SC_CALLBACK_REG-XML-Registrierungsmeldung generiert.
Eine SC_CALLBACK_REG-Meldung besteht aus einem Registrierungstyp (ADD_CLIENT, REMOVE_CLIENT, ADD_EVENTS oder REMOVE_EVENTS), einem Rückmelde-Port und einer Liste der interessierenden Ereignisse. Jedes Ereignis besteht aus einer Klasse und einer Unterklasse, gefolgt von einer Liste der Namens- und Wertepaare.
In diesem Teil des Beispiels schreiben Sie eine CallbackReg-Klasse, die den Registrierungstyp, den Rückmelde-Port und die Liste der Registrierungsereignisse speichert. Diese Klasse kann sich auch an eine SC_CALLBACK_REG-XML-Meldung serialisieren.
Eine interessante Methode dieser Klasse ist die convertToXml-Methode, die eine SC_CALLBACK_REG-XML-Meldungszeichenkette aus den Klassenmitgliedern erstellt. Eine detailliertere Beschreibung des Codes dieser Methode finden Sie in der JAXP-Dokumentation unter http://java.sun.com/xml/jaxp/index.html.
Im Folgenden wird die Implementierung der Event-Klasse gezeigt. Beachten Sie, dass die CallbackReg-Klasse eine Event-Klasse verwendet, die ein Ereignis speichert und dieses Ereignis in ein XML-Element konvertieren kann.
Erstellen Sie den Java-Code, der die vorstehende Logik implementiert.
class CallbackReg { public static final int ADD_CLIENT = 0; public static final int ADD_EVENTS = 1; public static final int REMOVE_EVENTS = 2; public static final int REMOVE_CLIENT = 3; public CallbackReg() { port = null; regType = null; regEvents = new Vector(); } public void setPort(String portIn) { port = portIn; } public void setRegType(int regTypeIn) { switch (regTypeIn) { case ADD_CLIENT: regType = "ADD_CLIENT"; break; case ADD_EVENTS: regType = "ADD_EVENTS"; break; case REMOVE_CLIENT: regType = "REMOVE_CLIENT"; break; case REMOVE_EVENTS: regType = "REMOVE_EVENTS"; break; default: System.out.println("Fehler, ungültiger regType " + regTypeIn); regType = "ADD_CLIENT"; break; } } public void addRegEvent(Event regEvent) { regEvents.add(regEvent); } public String convertToXml() { Document document = null; DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); try { DocumentBuilder builder = factory.newDocumentBuilder(); document = builder.newDocument(); } catch (ParserConfigurationException pce) { // Parser mit angegebenen Optionen kann nicht erstellt werden pce.printStackTrace(); System.exit(1); } // Root-Element erstellen Element root = (Element) document.createElement( "SC_CALLBACK_REG"); // Attribute hinzufügen root.setAttribute("VERSION", "1.0"); root.setAttribute("PORT", port); root.setAttribute("regType", regType); // Ereignisse hinzufügen for (int i = 0; i < regEvents.size(); i++) { Event tempEvent = (Event) (regEvents.elementAt(i)); root.appendChild(tempEvent.createXmlElement( document)); } document.appendChild(root); // Das Ganze in eine Zeichenkette konvertieren DOMSource domSource = new DOMSource(document); StringWriter strWrite = new StringWriter(); StreamResult streamResult = new StreamResult(strWrite); TransformerFactory tf = TransformerFactory.newInstance(); try { Transformer transformer = tf.newTransformer(); transformer.transform(domSource, streamResult); } catch (TransformerException e) { System.out.println(e.toString()); return (""); } return (strWrite.toString()); } private String port; private String regType; private Vector regEvents; } |
Implementieren Sie die Event- und NVPair-Klassen.
Beachten Sie, dass die CallbackReg-Klasse eine Event-Klasse verwendet, die wiederum eine NVPair-Klasse verwendet.
class Event { public Event() { regClass = regSubclass = null; nvpairs = new Vector(); } public void setClass(String classIn) { regClass = classIn; } public void setSubclass(String subclassIn) { regSubclass = subclassIn; } public void addNvpair(NVPair nvpair) { nvpairs.add(nvpair); } public Element createXmlElement(Document doc) { Element event = (Element) doc.createElement("SC_EVENT_REG"); event.setAttribute("CLASS", regClass); if (regSubclass != null) { event.setAttribute("SUBCLASS", regSubclass); } for (int i = 0; i < nvpairs.size(); i++) { NVPair tempNv = (NVPair) (nvpairs.elementAt(i)); event.appendChild(tempNv.createXmlElement( doc)); } return (event); } private String regClass, regSubclass; private Vector nvpairs; } class NVPair { public NVPair() { name = value = null; } public void setName(String nameIn) { name = nameIn; } public void setValue(String valueIn) { value = valueIn; } public Element createXmlElement(Document doc) { Element nvpair = (Element) doc.createElement("NVPAIR"); Element eName = doc.createElement("NAME"); Node nameData = doc.createCDATASection(name); eName.appendChild(nameData); nvpair.appendChild(eName); Element eValue = doc.createElement("VALUE"); Node valueData = doc.createCDATASection(value); eValue.appendChild(valueData); nvpair.appendChild(eValue); return (nvpair); } private String name, value; } |
Nach dem Erstellen der Helper-Klassen für die Generierung von XML-Meldungen können Sie die Implementierung der createRegistrationString-Methode schreiben. Diese Methode wird von der registerCallbacks-Methode aufgerufen (siehe Beschreibung unter Registrieren und Deregistrieren von Rückmeldungen).
createRegistrationString erstellt ein CallbackReg-Objekt und richtet dessen Registrierungstyp und Port ein. Danach erstellt createRegistrationString verschiedene Ereignisse unter Verwendung der Helper-Methoden createAllEvent, createMembershipEvent, createRgEvent und createREvent. Jedes Ereignis wird diesem Objekt nach Erstellung des CallbackReg-Objekts hinzugefügt. Zuletzt ruft createRegistrationString die convertToXml-Methode des CallbackReg-Objekts auf, um die XML-Meldung im String-Format abzurufen.
Beachten Sie, dass die regs-Mitgliedsvariable die Befehlszeilenargumente speichert, die der Benutzer der Anwendung angibt. Das fünfte und alle folgenden Argumente geben die Ereignisse an, für die eine Anwendung registriert werden soll. Das vierte Argument gibt den Registrierungstyp an; es wird in diesem Beispiel jedoch übergangen. Der vollständige Code in Anhang G zeigt die Verwendung dieses vierten Arguments.
Erstellen Sie den Java-Code, der die vorstehende Logik implementiert.
private String createRegistrationString() throws Exception { CallbackReg cbReg = new CallbackReg(); cbReg.setPort("" + localPort); cbReg.setRegType(CallbackReg.ADD_CLIENT); // Ereignisse hinzufügen for (int i = 4; i < regs.length; i++) { if (regs[i].equals("M")) { cbReg.addRegEvent( createMembershipEvent()); } else if (regs[i].equals("A")) { cbReg.addRegEvent( createAllEvent()); } else if (regs[i].substring(0,2).equals("RG")) { cbReg.addRegEvent(createRgEvent( regs[i].substring(3))); } else if (regs[i].substring(0,1).equals("R")) { cbReg.addRegEvent(createREvent( regs[i].substring(2))); } } String xmlStr = cbReg.convertToXml(); return (xmlStr); } private Event createAllEvent() { Event allEvent = new Event(); allEvent.setClass("EC_Cluster"); return (allEvent); } private Event createMembershipEvent() { Event membershipEvent = new Event(); membershipEvent.setClass("EC_Cluster"); membershipEvent.setSubclass("ESC_cluster_membership"); return (membershipEvent); } private Event createRgEvent(String rgname) { Event rgStateEvent = new Event(); rgStateEvent.setClass("EC_Cluster"); rgStateEvent.setSubclass("ESC_cluster_rg_state"); NVPair rgNvpair = new NVPair(); rgNvpair.setName("rg_name"); rgNvpair.setValue(rgname); rgStateEvent.addNvpair(rgNvpair); return (rgStateEvent); } private Event createREvent(String rname) { Event rStateEvent = new Event(); rStateEvent.setClass("EC_Cluster"); rStateEvent.setSubclass("ESC_cluster_r_state"); NVPair rNvpair = new NVPair(); rNvpair.setName("r_name"); rNvpair.setValue(rname); rStateEvent.addNvpair(rNvpair); return (rStateEvent); } |
Erstellen Sie die Deregistrierungs-Zeichenkette.
Das Erstellen einer Deregistrierungs-Zeichenkette ist einfacher als das Erstellen der Registrierungszeichenkette, da keine Ereignisse berücksichtigt werden müssen:
private String createUnregistrationString() throws Exception { CallbackReg cbReg = new CallbackReg(); cbReg.setPort("" + localPort); cbReg.setRegType(CallbackReg.REMOVE_CLIENT); String xmlStr = cbReg.convertToXml(); return (xmlStr); } |
Bisher wurden der Netzwerk- und der XML-Generierungscode für die Anwendung erstellt. Der letzte Schritt besteht in der Analyse und Verarbeitung der Registrierungsantwort und der Ereignisrückmeldungen. Der CrnpClient-Konstruktor ruft eine setupXmlProcessing-Methode auf. Diese Methode erstellt ein DocumentBuilderFactory-Objekt und stellt verschiedene Analyseeigenschaften für dieses Objekt ein. Eine detailliertere Beschreibung dieser Methode finden Sie in der JAXP-Dokumentation unter http://java.sun.com/xml/jaxp/index.html.
Erstellen Sie den Java-Code, der die vorstehende Logik implementiert.
private void setupXmlProcessing() throws Exception { dbf = DocumentBuilderFactory.newInstance(); // Eine Validierung ist nicht erforderlich. dbf.setValidating(false); dbf.setExpandEntityReferences(false); // Kommentare und Leerzeichen sollen ignoriert werden dbf.setIgnoringComments(true); dbf.setIgnoringElementContentWhitespace(true); // CDATA-Abschnitte mit TEXT-Knoten verbinden. dbf.setCoalescing(true); } |
Für die Analyse der SC_REPLY-XML-Meldung, die der CRNP-Server als Antwort auf eine Registrierungs- bzw. Deregistrierungsmeldung sendet, benötigen Sie eine RegReply-Helper-Klasse. Diese Klasse kann aufbauend auf einem XML-Dokument erstellt werden. Die Klasse ermöglicht den Zugang zum Statuscode und zur Statusmeldung. Um den XML-Strom vom Server zu analysieren, müssen Sie ein neues XML-Dokument erstellen und die Analysemethode dieses Dokuments verwenden. Eine detailliertere Beschreibung dieser Methode finden Sie in der JAXP-Dokumentation unter http://java.sun.com/xml/jaxp/index.html.
Erstellen Sie den Java-Code, der die vorstehende Logik implementiert.
Beachten Sie, dass die readRegistrationReply-Methode die neue RegReply-Klasse verwendet.
private void readRegistrationReply(InputStream stream) throws Exception { // Dokument-Builder erstellen DocumentBuilder db = dbf.newDocumentBuilder(); db.setErrorHandler(new DefaultHandler()); // Eingabedatei analysieren Document doc = db.parse(stream); RegReply reply = new RegReply(doc); reply.print(System.out); } |
Implementieren Sie die RegReply-Klasse.
Beachten Sie, dass die retrieveValues-Methode der DOM-Struktur im XML-Dokument folgt und den Statuscode und die Statusmeldung abruft. Weitere Einzelheiten finden Sie in der JAXP-Dokumentation unter http://java.sun.com/xml/jaxp/index.html.
class RegReply { public RegReply(Document doc) { retrieveValues(doc); } public String getStatusCode() { return (statusCode); } public String getStatusMsg() { return (statusMsg); } public void print(PrintStream out) { out.println(statusCode + ": " + (statusMsg != null ? statusMsg : "")); } private void retrieveValues(Document doc) { Node n; NodeList nl; String nodeName; // SC_REPLY-Element suchen. nl = doc.getElementsByTagName("SC_REPLY"); if (nl.getLength() != 1) { System.out.println("Analysefehler:" + "SC_REPLY-Knoten kann nicht gefunden werden."); return; } n = nl.item(0); // Wert des statusCode-Attributs abrufen statusCode = ((Element)n).getAttribute("STATUS_CODE"); // SC_STATUS_MSG-Element suchen nl = ((Element)n).getElementsByTagName("SC_STATUS_MSG"); if (nl.getLength() != 1) { System.out.println("Analysefehler: " + "SC_STATUS_MSG-Knoten kann nicht gefunden werden."); return; } // TEXT-Abschnitt abrufen, falls vorhanden. n = nl.item(0).getFirstChild(); if (n == null || n.getNodeType() != Node.TEXT_NODE) { // Kein Fehler, falls nicht vorhanden; einfach stillschweigend zurückgehen. return; } // Wert abrufen statusMsg = n.getNodeValue(); } private String statusCode; private String statusMsg; } |
Der letzte Schritt besteht in der Analyse und Verarbeitung der Rückmeldeereignisse selbst. Um diese Aufgabe zu unterstützen, ändern Sie die Event-Klasse, die in Generieren des XML erstellt wurde, damit diese Klasse in der Lage ist, ein Event basierend auf einem XML-Dokument zu erstellen und ein XML-Element zu erstellen. Diese Änderung erfordert einen zusätzlichen Konstruktor (XML-Dokument), eine retrieveValues-Methode, das Hinzufügen zweier weiterer Mitgliedsvariablen (vendor und publisher), Zugangsmethoden für alle Felder und schließlich eine Druckmethode.
Erstellen Sie den Java-Code, der die vorstehende Logik implementiert.
Beachten Sie, dass dieser Code dem Code für die RegReply-Klasse ähnelt, die unter Analysieren der Registrierungsantwort beschrieben wurde.
public Event(Document doc) { nvpairs = new Vector(); retrieveValues(doc); } public void print(PrintStream out) { out.println("\tCLASS=" + regClass); out.println("\tSUBCLASS=" + regSubclass); out.println("\tVENDOR=" + vendor); out.println("\tPUBLISHER=" + publisher); for (int i = 0; i < nvpairs.size(); i++) { NVPair tempNv = (NVPair) (nvpairs.elementAt(i)); out.print("\t\t"); tempNv.print(out); } } private void retrieveValues(Document doc) { Node n; NodeList nl; String nodeName; // SC_EVENT-Element suchen. nl = doc.getElementsByTagName("SC_EVENT"); if (nl.getLength() != 1) { System.out.println("Analysefehler: " + "SC_EVENT-Knoten kann nicht gefunden werden."); return; } n = nl.item(0); // // Werte der Attribute CLASS, SUBCLASS, // VENDOR und PUBLISHER abrufen. // regClass = ((Element)n).getAttribute("CLASS"); regSubclass = ((Element)n).getAttribute("SUBCLASS"); publisher = ((Element)n).getAttribute("PUBLISHER"); vendor = ((Element)n).getAttribute("VENDOR"); // Alle NW-Paare abrufen for (Node child = n.getFirstChild(); child != null; child = child.getNextSibling()) { nvpairs.add(new NVPair((Element)child)); } } public String getRegClass() { return (regClass); } public String getSubclass() { return (regSubclass); } public String getVendor() { return (vendor); } public String getPublisher() { return (publisher); } public Vector getNvpairs() { return (nvpairs); } private String vendor, publisher; |
Implementieren Sie die zusätzlichen Konstruktoren und Methoden für die NVPair-Klasse, welche die XML-Analyse unterstützen.
Die Änderungen an der Event-Klasse, die in Schritt 1 gezeigt werden, machen vergleichbare Änderungen an der NVPair-Klasse erforderlich.
public NVPair(Element elem) { retrieveValues(elem); } public void print(PrintStream out) { out.println("NAME=" + name + " VALUE=" + value); } private void retrieveValues(Element elem) { Node n; NodeList nl; String nodeName; // NAME-Element suchen nl = elem.getElementsByTagName("NAME"); if (nl.getLength() != 1) { System.out.println("Analysefehler: " + "NAME-Knoten kann nicht gefunden werden."); return; } // TEXT-Abschnitt abrufen n = nl.item(0).getFirstChild(); if (n == null || n.getNodeType() != Node.TEXT_NODE) { System.out.println("Analysefehler: " + "TEXT-Abschnitt konnte nicht gefunden werden."); return; } // Wert abrufen name = n.getNodeValue(); // Jetzt das Wertelement abrufen nl = elem.getElementsByTagName("VALUE"); if (nl.getLength() != 1) { System.out.println("Analysefehler: " + "VALUE-Knoten konnte nicht gefunden werden."); return; } // TEXT-Abschnitt abrufen n = nl.item(0).getFirstChild(); if (n == null || n.getNodeType() != Node.TEXT_NODE) { System.out.println("Analysefehler " + "TEXT-Abschnitt konnte nicht gefunden werden."); return; } // Wert abrufen value = n.getNodeValue(); } public String getName() { return (name); } public String getValue() { return (value); } } |
Implementieren Sie die while-Schleife in EventReceptionThread, die Rückmeldeereignisse abwartet (EventReceptionThread wird unter Definieren des Ereignisempfangs-Threads beschrieben).
while(true) { Socket sock = listeningSock.accept(); Document doc = db.parse(sock.getInputStream()); Event event = new Event(doc); client.processEvent(event); sock.close(); } |
Führen Sie die Anwendung aus.
# java CrnpClient crnpHost crnpPort localPort ... |
Der vollständige Code für die CrnpClient-Anwendung ist in Anhang G enthalten.