Sun Cluster Entwicklerhandbuch Datendienste für Solaris OS

Kapitel 12 CRNP (Cluster Reconfiguration Notification Protocol)

Dieses Kapitel enthält Informationen zum CRNP (Cluster Reconfiguration Notification Protocol). Das CRNP bietet Failover- und skalierbaren Anwendungen “Cluster-Unterstützung.” Das CRNP bietet einen Mechanismus, mit dem Anwendungen registriert werden können und anschließend eine asynchrone Benachrichtigung über die Sun Cluster-Rekonfigurationsereignisse erhalten. 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.


Hinweis –

Die SUNW.Event-Ressourcentypimplementierung bietet CRNP-Dienste mit hoher Verfügbarkeit in Sun Cluster. Die Implementierung dieses Ressourcentyps wird detailliert in der Online-Dokumentation unter SUNW.Event(5) beschrieben.


Dieses Kapitel behandelt die folgenden Themen:

CRNP-Konzepte

Das CRNP definiert die Anwendungs-, Präsentations- und Sitzungsschichten des OSI-Standardprotokollstapels mit sieben Schichten. Die Transportschicht muss TCP sein und die Netzwerkschicht IP. 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.

CRNP-Funktionsweise

Das CRNP bietet Mechanismen und Dämonen, die Cluster-Rekonfigurationsereignisse generieren, die Ereignisse über den Cluster weiterleiten 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. Dieser Dämon verwendet syseventd, um die Ereignisse an jedem lokalen Knoten zu übertragen. Der cl_apid-Dämon verwendet Extensible Markup Language (XML) via TCP/IP für die Kommunikation mit interessierten Clients.

Im folgenden Diagramm wird der Fluss der Ereignisse zwischen den CRNP-Komponenten dargestellt. 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.

Abbildung 12–1 Fluss der Ereignisse zwischen den CRNP-Komponenten

Flussdiagramm mit Darstellung der CRNP-Funktionsweise

CRNP-Semantik

Die Clients initiieren die Kommunikation, indem eine Registrierungsmeldung (SC_CALLBACK_RG) an den Server gesendet wird. Diese Registrierungsmeldung gibt die Ereignistypen an, für die Clients Benachrichtigungen erhalten möchten, sowie den Port, an den die Ereignisse zugestellt werden können. 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.

Im folgenden Diagramm wird der Kommunikationsfluss zwischen einem Client und einem Server dargestellt.

Abbildung 12–2 Kommunikationsfluss zwischen einem Client und einem Server

Flussdiagramm, das den Kommunikationsfluss zwischen Client und Server zeigt

CRNP-Meldungstypen

Das CRNP verwendet drei Typen von XML-basierten Meldungen. Die Verwendung dieser Meldungen wird in der folgenden Tabelle beschrieben. Weitere Einzelheiten zu diesen Meldungstypen werden weiter unten in diesem Kapitel beschrieben.

CRNP-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:

 

  • Protokollversion

  • Rückmelde-Port in ASCII-Format (nicht Binärformat)

ADD_CLIENT, ADD_EVENTS und REMOVE_EVENTS enthalten auch eine uneingeschränkte Liste mit Ereignistypen, von denen jeder die folgenden Informationen umfasst:

 

  • Ereignisklasse

  • Ereignisunterklasse (optional)

  • Liste der Namens- und Wertepaare (optional)

Die Ereignisklasse und die Ereignisunterklasse definieren zusammen einen einmaligen “Ereignistyp.” Die Dokumenttypdefinition (DTD, Document Type Definition), aus der die Klassen von SC_CALLBACK_REG generiert werden, lautet SC_CALLBACK_REG. Diese DTD wird detailliert in Anhang F, Dokumenttypdefinitionen für das CRNP beschrieben.

SC_REPLY

Diese Meldung enthält folgende Informationen: 

  • Protokollversion

  • Fehlercode

  • Fehlermeldung

Die DTD, aus der die Klassen von SC_REPLY generiert werden, lautet SC_REPLY. Diese DTD wird detailliert in Anhang F, Dokumenttypdefinitionen für das CRNP beschrieben.

SC_EVENT

Diese Meldung enthält folgende Informationen: 

  • Protokollversion

  • Ereignisklasse

  • Ereignisunterklasse

  • Anbieter

  • Herausgeber

  • Liste der Namens- und Wertepaare (0 oder mehr Namens- und Wertepaar-Datenstrukturen)

    • Name (Zeichenkette)

    • Wert (Zeichenkette oder Zeichenketten-Array)

Die Werte in einem SC_EVENT werden nicht eingegeben. Die DTD, aus der die Klassen von SC_EVENT generiert werden, lautet SC_EVENT. Diese DTD wird detailliert in Anhang F, Dokumenttypdefinitionen für das CRNP beschrieben.

Client-Registrierung beim Server

In diesem Abschnitt wird beschrieben, wie ein Cluster-Administrator den Server einrichtet, wie Clients identifiziert werden, wie Informationen über die Anwendungs- und Sitzungsschichten gesendet werden, sowie die Fehlermeldungen.

Annahmen zur Konfiguration des Servers durch den Administrator

Der Cluster-Administrator muss den Server mit einer hoch verfügbaren IP-Adresse (eine Adresse, die nicht an einen bestimmten Rechner im Cluster gebunden ist) und einer Portnummer konfigurieren. Der Cluster-Administrator muss diese Netzwerkadresse für potenzielle Clients veröffentlichen. Das CRNP definiert nicht, wie der Servername den Clients verfügbar gemacht wird. Der Cluster-Administrator verwendet entweder einen Namensdienst, mit dem Clients die Netzwerkadresse des Servers dynamisch suchen können, oder fügt den Netzwerknamen einer Konfigurationsdatei hinzu, die der Client lesen soll. Der Server wird innerhalb des Clusters als Failover-Ressourcentyp ausgeführt.

Client-Identifizierung durch den Server

Jeder Client wird durch seine Rückrufadresse eindeutig identifiziert, d.h. seine IP-Adresse und Port-Nummer. 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 nachfolgende SC_CALLBACK_REG-Meldungen mit derselben Rückrufadresse von demselben Client stammen, selbst wenn der Quell-Port, von dem die Meldungen gesendet werden, ein anderer ist.

Senden von SC_CALLBACK_REG-Meldungen zwischen einem Client und dem Server

Ein Client initiiert eine Registrierung, indem eine TCP-Verbindung mit der IP-Adresse und der Port-Nummer des Servers hergestellt wird. 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 von diesem Client keine ADD_CLIENT-Meldung gesendet wurde. Ein Client kann keine REMOVE_CLIENT-Meldung senden, bevor von diesem Client keine 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. Ein Client wird einmalig deregistriert, indem eine REMOVE_CLIENT-Meldung an den Server gesendet wird. Das CRNP bietet jedoch größere Flexibilität für diejenigen Clients, die ihre Ereignistypliste dynamisch ändern möchten.

Inhalt einer SC_CALLBACK_REG-Meldung

Jede ADD_CLIENT-, REMOVE_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.

Wenn ein Client eine der folgenden Aktionen durchführt, ignoriert der Server diese Meldungen automatisch:

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 

Server-Antworten an den Client

Nach Verarbeitung der Registrierung sendet der Server, dem die Registrierungsanforderung zugestellt wurde, die SC_REPLY-Meldung mit der TCP-Verbindung, die vom Client hergestellt wurde. Der Server schließt 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:

  1. Herstellen einer TCP-Verbindung mit dem Server,

  2. Warten, bis die Verbindung “schreibbereit” ist,

  3. Senden einer SC_CALLBACK_REG-Meldung mit einer ADD_CLIENT-Meldung,

  4. Warten auf eine SC_REPLY-Meldung vom Server

  5. Erhalten einer SC_REPLY-Meldung vom Server

  6. Erhalten einer Anzeige, dass der Server die Verbindung beendet hat (Lesen von 0 Bytes vom Socket),

  7. Beenden der Verbindung.

Zu einem späteren Zeitpunkt führt der Client die folgenden Aktionen aus:

  1. Herstellen einer TCP-Verbindung mit dem Server,

  2. Warten, bis die Verbindung “schreibbereit” ist,

  3. Senden einer SC_CALLBACK_REG -Meldung mit einer REMOVE_CLIENT-Meldung,

  4. Warten auf eine SC_REPLY-Meldung vom Server

  5. Erhalten einer SC_REPLY-Meldung vom Server

  6. Erhalten einer Anzeige, dass der Server die Verbindung beendet hat (Lesen von 0 Bytes vom Socket),

  7. 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 die möglichen Fehlermeldungen, die diese Meldung beinhalten kann.

Inhalt einer SC_REPLY-Meldung

Eine SC_REPLY-Meldung gibt an, ob eine Operation erfolgreich oder fehlerhaft war. Diese Meldung enthält die Version der CRNP-Meldung, einen Statuscode sowie eine Statusmeldung, die den Statuscode detailliert 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 aufgrund eines Übergangsfehlers zurückgewiesen. Der Client sollte die Registrierung erneut versuchen, mit unterschiedlichen Argumenten. 

LOW_RESOURCE

Die Cluster-Ressourcen sind gering und der Client kann es nur zu einem späteren Zeitpunkt erneut versuchen. Der Cluster-Administrator für den Cluster kann auch die Ressourcen im Cluster erhöhen.  

SYSTEM_ERROR

Ein schwerwiegendes Problem ist aufgetreten. Wenden Sie sich an den Cluster-Administrator.  

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, d.h. sie erfüllt die XML-Spezifikationen nicht.  

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.  

Umgang des Clients mit Fehlerbedingungen

Unter normalen Bedingungen erhält ein Client, der eine SC_CALLBACK_REG-Meldung sendet, eine Antwort, ob die Registrierung erfolgreich oder fehlerhaft war.

Der Server kann jedoch bei der Client-Registrierung eine Fehlerbedingung erfahren, die verbietet, dass der Server eine SC_REPLY-Meldung an den Client sendet. In diesem Fall kann die Registrierung entweder vor Auftreten der Fehlerbedingung erfolgreich verlaufen sein, oder sie konnte noch nicht verarbeitet werden.

Da der Server im Cluster als Failover funktionieren oder hoch verfügbar sein muss, bedeutet diese Fehlerbedingung keine Beendigung des Dienstes. Es kann sogar sein, dass der Server bald beginnt, Ereignisse an den neu registrierten Client zu senden.

Um diese Situation zu beheben, sollte Ihr Client die folgenden Aktionen durchführen:

Verfahren für Ereigniszustellungen vom Server an den Client

Wenn innerhalb des Clusters Ereignisse generiert werden, stellt sie der CRNP-Server jedem Client zu, der die Ereignisse dieser Typen anforderte. 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 den aktuellen Status des Systems bestimmen, aus dem die nachfolgenden Ereignisse stammen.

Wenn der Server eine TCP-Verbindung mit dem Client herstellt, sendet er genau eine SC_EVENT-Meldung über die Verbindung. Der Server führt eine Vollduplexbeendigung durch.

Der Client führt zum Beispiel folgende Aktionen aus:

  1. Abwarten einer vom Server hergestellten TCP-Verbindung,

  2. Akzeptieren der eingehenden Verbindung vom Server,

  3. Warten auf eine SC_EVENT-Meldung vom Server

  4. Lesen einer SC_EVENT-Meldung vom Server

  5. Erhalten einer Anzeige, dass der Server die Verbindung beendet hat (Lesen von 0 Bytes vom Socket),

  6. 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 auch erneut registrieren, indem eine andere SC_CALLBACK_REG-Meldung gesendet wird, die eine ADD_CLIENT-Meldung enthält, bevor der Client weitere Ereignisse erhalten kann.

Garantie der Ereigniszustellung

Es gibt eine Gesamtanforderung der Ereignisgenerierung innerhalb des Clusters, die in der Reihenfolge der Zustellung an jeden Client beibehalten wird. Mit anderen Worten: Wenn Ereignis A innerhalb des Clusters vor Ereignis B generiert wird, erhält Client X Ereignis A, bevor dieser Client Ereignis B erhält. Die gesamte Ereigniszustellung an alle Clients wird jedoch nicht beibehalten. 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, der Server kann dasselbe Ereignis mehr als einmal an einen Client senden. Diese Berechtigung ist erforderlich in Fällen, in denen der Server vorübergehend heruntergefahren wird, und wenn er wieder hochgefahren wird, nicht bestimmen kann, ob der Client die aktuellsten Informationen erhalten hat.

Inhalt einer SC_EVENT-Meldung

Die SC_EVENT-Meldung enthält die tatächliche Meldung, die innerhalb des Clusters generiert wird, so übersetzt, dass sie in das SC_EVENT-XML-Meldungsformat passt. Die folgende Tabelle beschreibt die vom CRNP zugestellten Ereignistypen, einschließlich der Namens- und Wertepaare, Herausgeber und Hersteller.


Hinweis –

Die Positionen der Array-Elemente für state_list sind mit denjenigen der node_list synchronisiert. Das heißt, der Zustand für den Knoten, der im node_list-Array zuerst aufgelistet ist, ist im state_list-Array auch zuerst aufgelistet.

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.


Klasse und Unterklasse 

Herausgeber und Hersteller 

Namens- und Wertepaare 

EC_Cluster

ESC_cluster_membership

Herausgeber: rgm 

Hersteller: SUNW 

Name: node_list

Werttyp: Zeichenketten-Array 

Name: state_list

Die state_list enthält nur Zahlen, die in ASCII dargestellt sind. 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.

Werttyp: Zeichenketten-Array 

EC_Cluster

ESC_cluster_rg_state

Herausgeber: rgm 

Hersteller: SUNW 

Name: rg_name

Werttyp: Zeichenkette 

Name: node_list

Werttyp: Zeichenketten-Array 

Name: state_list

Die state_list enthält Zeichenkettendarstellungen des Zustands der Ressourcengruppe. Gültige Werte sind diejenigen, die Sie mit den Befehlen scha_cmds(1HA) abrufen können.

Werttyp: Zeichenketten-Array 

EC_Cluster

ESC_cluster_r_state

Herausgeber: rgm 

Hersteller: SUNW 

Name: r_name

Werttyp: Zeichenkette 

Name: node_list

Werttyp: Zeichenketten-Array 

Name: state_list

Die state_list enthält Zeichenkettendarstellungen des Zustands der Ressource. Gültige Werte sind diejenigen, die Sie mit den Befehlen scha_cmds(1HA) abrufen können.

Werttyp: Zeichenketten-Array 

Authentisierung von Clients und Server durch das CRNP

Der Server authentifiziert einen Client durch Verwendung einer Form des TCP-Wrappers. Die IP-Quelladresse der Registrierungsmeldung, die auch als Rückruf-IP-Adresse verwendet wird, an die Ereignisse zugestellt werden, muss in der Liste zulässiger Clients auf dem Server enthalten sein. 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, müssen die nachfolgenden SC_CALLBACK_REG-Meldungen für diesen Client eine IP-Quelladresse enthalten, die der IP-Quelladresse in der ersten Meldung entspricht. Wenn der CRNP-Server eine SC_CALLBACK_REG-Meldung erhält, die diese Anforderungen nicht erfüllt, führt der Server eine der folgenden Aktionen durch:

Dieser Sicherheitsmechanismus trägt dazu bei, Dienstverweigerungsangriffe abzuwehren, bei denen versucht wird, einen berechtigten Client zu deregistrieren.

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 sich die Clients des CRNP-Dienstes wohl innerhalb einer Firewall befinden, die den Cluster schützt, bietet das CRNP keine zusätzlichen Sicherheitsmechanismen.

Beispiel zum Erstellen einer Java-Anwendung, die das CRNP verwendet

Das folgende Beispiel zeigt, wie eine einfache Java-Anwendung namens CrnpClient entwickelt wird, die das CRNP verwendet. Die Anwendung wird für Ereignisrückrufe mit dem CRNP-Server im Cluster registriert, hört die Ereignisrückrufe ab und verarbeitet die Ereignisse durch Drucken ihres Inhalts. Vor der Beendigung deregistriert die Anwendung ihre Anforderung von Ereignisrückmeldungen.

Beachten Sie folgende Punkte beim Prüfen dieses Beispiels:

ProcedureSo konfigurieren Sie Ihre Umgebung

Schritte
  1. 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.


    Hinweis –

    Für dieses Beispiel ist mindestens Java 1.3.1 erforderlich.


  2. Geben Sie in dem Verzeichnis, in dem sich Ihre Quelldatei befindet, 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 . Quelldateiname.java
    

    wobei jaxp-root den absoluten oder relativen Pfad des Verzeichnisses darstellt, in dem sich die JAXP-jar-Dateien befinden und Quelldateiname den Namen der Java-Quelldatei bezeichnet.

    Ein classpath in Ihrer Kompilierungsbefehlszeile stellt sicher, dass der Compiler die JAXP-Klassen finden kann.

  3. Geben Sie beim Ausführen der Anwendung den classpath so an, dass die Anwendung die richtigen JAXP-Klassendateien laden kann (Beachten Sie, dass es sich bei dem ersten Pfad im classpath um das aktuelle Verzeichnis handelt):


    % java -cp .:jaxp-root/dom.jar:jaxp-rootjaxp-api. \
    jar:jaxp-rootsax.jar:jaxp-rootxalan.jar:jaxp-root/xercesImpl \
    .jar:jaxp-root/xsltc.jar Quelldateiname Argumente
    

    Damit ist die Umgebung konfiguriert, und Sie können die Anwendung entwickeln.

ProcedureEntwickeln Ihrer Anwendung

In diesem Teil des Beispiels können Sie eine Basisklasse namens CrnpClient erstellen, zusammen mit einer Hauptmethode, mit der die Befehlszeilenargumente geparst werden und ein CrnpClient-Objekt erstellt wird. 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:

Schritt

    Erstellen Sie den Java-Code, der die vorstehende Logik implementiert.

    Das folgende Beispiel zeigt den Hauptcode für die CrnpClient-Klasse. Die Implementierung der vier Helper-Methoden, auf die im Konstruktor verwiesen wird, sowie die Shutdown-Methoden werden weiter unten in diesem Kapitel beschrieben. Beachten Sie, dass der Code, mit dem alle erforderlichen Pakete importiert werden, dargestellt wird.

    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("Hit return to terminate demo...");
                    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;
    }

    Die Mitgliedsvariablen werden weiter unten in diesem Kapitel detailliert erläutert.

ProcedureSo parsen Sie die Befehlszeilenargumente

Schritt

    Wie Sie die Befehlszeilenargumente parsen, erfahren Sie im Code in Anhang G, CrnpClient.java-Anwendung .

ProcedureSo definieren Sie den Ereignis-Empfangs-Thread

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.


Hinweis –

Die XML-Konfiguration wird weiter unten in diesem Kapitel erläutert.


Schritte
  1. Definieren Sie in Ihrem Code eine Thread-Unterklasse namens EventReceptionThread, die eine ServerSocket erstellt und darauf wartet, dass die Ereignisse am Socket ankommen.

    In diesem Teil des Beispielcodes werden Ereignisse weder gelesen noch verarbeitet. Lesen und Verarbeiten von Ereignissen, die weiter unten in diesem Kapitel erläutert werden. EventReceptionThread erstellt einen ServerSocket an einer Platzhalter-Netzwerkprotokolladresse. EventReceptionThread behält auch einen Verweis auf das CrnpClient -Objekt, sodass 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();
                                    // Construct event from the sock stream and process it
                                    sock.close();
                            }
                            // UNREACHABLE
    
                    } catch (Exception e) {
                            System.out.println(e);
                            System.exit(1);
                    }
            }
    
            /* private member variables */
            private ServerSocket listeningSock;
            private CrnpClient client;
    }
  2. Erstellen Sie ein createEvtRecepThr-Objekt.

    private void createEvtRecepThr() throws Exception
    {
            evtThr = new EventReceptionThread(this);
            evtThr.start();
    }

ProcedureSo registrieren und deregistrieren Sie Rückrufe

Für die Registrierungsaufgabe sind folgende Aktionen erforderlich:

Schritte
  1. Erstellen Sie den Java-Code, der die vorstehende Logik implementiert.

    Folgender Beispielcode zeigt die Implementierung der registerCallbacks-Methode der CrnpClient-Klasse (die vom CrnpClient-Konstruktur aufgerufen wird). Die Aufrufe von createRegistrationString() und readRegistrationReply () werden weiter unten in diesem Kapitel detailliert beschrieben.

    regIp und regPort sind Objektmitglieder, die vom Konstruktur eingerichtet 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();
    }
  2. Implementieren Sie die unregister-Methode.

    Diese Methode wird von der shutdown-Methode von CrnpClient aufgerufen. Die Implementierung von createUnregistrationString wird weiter unten in diesem Kapitel detailliert 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();
    }

ProcedureSo generieren Sie die XML

Nachdem Sie nun die Struktur der Anwendung eingerichtet und den gesamten Netzwerkcode geschrieben haben, müssen Sie den Code schreiben, der die XML generiert und parst. Beginnen Sie damit, den Code, mit dem die SC_CALLBACK_REG-XML-Registrierungsmeldung generiert wird, zu schreiben.

Eine SC_CALLBACK_REG-Meldung besteht aus einem Registrierungstyp (ADD_CLIENT, REMOVE_CLIENT, ADD_EVENTS oder REMOVE_EVENTS), einem Rückruf-Port und einer Liste mit wichtigen Ereignissen. 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 selbst in 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. In der JAXP-Dokumentation unter http://java.sun.com/xml/jaxp/index.html wird der Code dieser Methode detailliert beschrieben.

Die Implementierung der Event-Klasse wird im folgenden Beispielcode dargestellt. Beachten Sie, dass die CallbackReg-Klasse eine Event-Klasse verwendet, das ein Ereignis speichert und das dieses Ereignis in ein XML- Element konvertieren kann.

Schritte
  1. 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("Error, invalid 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 with specified options can't be built
                            pce.printStackTrace();
                            System.exit(1);
                    }
    
                    // Create the root element
                    Element root = (Element) document.createElement("SC_CALLBACK_REG");
    
                    // Add the attributes
                    root.setAttribute("VERSION", "1.0");
                    root.setAttribute("PORT", port);
                    root.setAttribute("regType", regType);
    
                    // Add the events
                    for (int i = 0; i < regEvents.size(); i++) {
                            Event tempEvent = (Event)
                                (regEvents.elementAt(i));
                            root.appendChild(tempEvent.createXmlElement(document));
                    }
                    document.appendChild(root);
    
                    // Convert the whole thing to a string
                    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;
    }
  2. 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;
    }

ProcedureSo erstellen Sie die Registrierungs- und Deregistrierungsmeldungen

Nachdem Sie die Helper-Klassen, die die XML-Meldungen generieren, erstellt haben, können Sie die Implementierung der createRegistrationString-Methode schreiben. Diese Methode wird von der registerCallbacks-Methode aufgerufen, die unter So registrieren und deregistrieren Sie Rückrufe beschrieben wird.

createRegistrationString erstellt ein CallbackReg-Objekt und richtet dessen Registrierungstyp und Port ein. Anschließend konstruiert createRegistrationString unter Verwendung der createAllEvent-, createMembershipEvent-, createRgEvent- und createREvent-Helper-Methoden verschiedene Ereignisse. 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 ein Benutzer der Anwendung liefert. 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, CrnpClient.java-Anwendung zeigt, wie dieses vierte Argument verwendet wird.

Schritte
  1. 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);
    
            // add the events
            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);
    }
  2. Erstellen Sie die Deregistrierungs-Zeichenkette.

    Das Erstellen der Deregistrierungszeichenkette ist einfacher als das Erstellen der Registrierungszeichenkette, da Sie keine Ereignisse zu berücksichtigen brauchen.

    private String createUnregistrationString() throws Exception
    {
            CallbackReg cbReg = new CallbackReg();
            cbReg.setPort("" + localPort);
            cbReg.setRegType(CallbackReg.REMOVE_CLIENT);
            String xmlStr = cbReg.convertToXml();
            return (xmlStr);
    }

ProcedureSo konfigurieren Sie den XML-Parser

Bisher wurden der Netzwerk- und der XML-Generierungscode für die Anwendung erstellt. Der CrnpClient-Konstruktur ruft eine setupXmlProcessing -Methode auf. Mit dieser Methode wird ein DocumentBuilderFactory-Objekt erstellt und es werden verschiedene Parsing-Eigenschaften für dieses Objekt eingerichtet. In der JAXP-Dokumentation wird diese Methode detailliert beschrieben. Weitere Informationen finden Sie unter http://java.sun.com/xml/jaxp/index.html.

Schritt

    Erstellen Sie den Java-Code, der die vorstehende Logik implementiert.

    private void setupXmlProcessing() throws Exception
    {
            dbf = DocumentBuilderFactory.newInstance();
    
            // We don't need to bother validating
            dbf.setValidating(false);
            dbf.setExpandEntityReferences(false);
    
            // We want to ignore comments and whitespace
            dbf.setIgnoringComments(true);
            dbf.setIgnoringElementContentWhitespace(true);
    
            // Coalesce CDATA sections into TEXT nodes.
            dbf.setCoalescing(true);
    }

ProcedureSo parsen Sie die Registrierungsantwort

Um die SC_REPLY-XML-Meldung zu parsen, die vom CRNP-Server als Antwort auf eine Registrierungs- oder Deregistrierungsmeldung gesendet wird, 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-Stream vom Server zu parsen, müssen Sie ein neues XML-Dokument erstellen und die Parse-Methode dieses Dokuments verwenden. In der JAXP-Dokumentation unter http://java.sun.com/xml/jaxp/index.html wird diese Methode detailliert beschrieben.

Schritte
  1. 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
    {
            // Create the document builder
            DocumentBuilder db = dbf.newDocumentBuilder();
            db.setErrorHandler(new DefaultHandler());
    
            //parse the input file
            Document doc = db.parse(stream);
    
            RegReply reply = new RegReply(doc);
            reply.print(System.out);
    }
  2. 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. Die JAXP-Dokumentation unter http://java.sun.com/xml/jaxp/index.html enthält weitere Informationen.

    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;
    
                    // Find the SC_REPLY element.
                    nl = doc.getElementsByTagName("SC_REPLY");
                    if (nl.getLength() != 1) {
                            System.out.println("Error in parsing: can't find "
                                + "SC_REPLY node.");
                            return;
                    }
    
                    n = nl.item(0);
    
                    // Retrieve the value of the statusCode attribute
                    statusCode = ((Element)n).getAttribute("STATUS_CODE");
    
                    // Find the SC_STATUS_MSG element
                    nl = ((Element)n).getElementsByTagName("SC_STATUS_MSG");
                    if (nl.getLength() != 1) {
                            System.out.println("Error in parsing: can't find "
                                + "SC_STATUS_MSG node.");
                            return;
                    }
                    // Get the TEXT section, if there is one.
                    n = nl.item(0).getFirstChild();
                    if (n == null || n.getNodeType() != Node.TEXT_NODE) {
                    // Not an error if there isn't one, so we just silently return.
                            return;
                    }
    
                    // Retrieve the value
                    statusMsg = n.getNodeValue();
            }
    
            private String statusCode;
            private String statusMsg;
    }

ProcedureSo parsen Sie die Rückrufereignisse

Der letzte Schritt besteht in der Analyse und Verarbeitung der Rückmeldeereignisse selbst. Ändern Sie dazu die Event-Klasse, die Sie unter So generieren Sie die XML erstellt haben, so, dass diese Klasse ein Event aus einem XML-Dokument konstruieren und ein XML- Element erstellen kann. Für diese Änderung sind ein zusätzlicher Konstruktor (der ein XML-Dokument verwendet), eine retrieveValues-Methode, das Hinzufügen von zwei Mitgliedsvariablen (vendor und publisher), Zugriffsmethoden für alle Felder und schließlich eine Druckmethode erforderlich.

Schritte
  1. Erstellen Sie den Java-Code, der die vorstehende Logik implementiert.

    Note that this code is similar to the code for the RegReply class that is described in So parsen Sie die Registrierungsantwort.

    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;
    
                    // Find the SC_EVENT element.
                    nl = doc.getElementsByTagName("SC_EVENT");
                    if (nl.getLength() != 1) {
                       System.out.println("Error in parsing: can't find "
                           + "SC_EVENT node.");
                       return;
                    }
    
                    n = nl.item(0);
    
                    //
                    // Retrieve the values of the CLASS, SUBCLASS,
                    // VENDOR and PUBLISHER attributes.
                    //
                    regClass = ((Element)n).getAttribute("CLASS");
                    regSubclass = ((Element)n).getAttribute("SUBCLASS");
                    publisher = ((Element)n).getAttribute("PUBLISHER");
                    vendor = ((Element)n).getAttribute("VENDOR");
    
                    // Retrieve all the nv pairs
                    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;
  2. Implementieren Sie die zusätzlichen Konstruktoren und Methoden für die NVPair-Klasse, die das XML-Parsen unterstützt.

    Die Änderungen an der Event-Klasse, die in Schritt 1 abgebildet sind, setzen ähnliche Änderungen an der NVPair-Klasse voraus.

    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;
    
                    // Find the NAME element
                    nl = elem.getElementsByTagName("NAME");
                    if (nl.getLength() != 1) {
                       System.out.println("Error in parsing: can't find "
                           + "NAME node.");
                       return;
                    }
                    // Get the TEXT section
                    n = nl.item(0).getFirstChild();
                    if (n == null || n.getNodeType() != Node.TEXT_NODE) {
                       System.out.println("Error in parsing: can't find "
                           + "TEXT section.");
                       return;
                    }
    
                    // Retrieve the value
                    name = n.getNodeValue();
    
                    // Now get the value element
                    nl = elem.getElementsByTagName("VALUE");
                    if (nl.getLength() != 1) {
                       System.out.println("Error in parsing: can't find "
                           + "VALUE node.");
                       return;
                    }
                    // Get the TEXT section
                    n = nl.item(0).getFirstChild();
                    if (n == null || n.getNodeType() != Node.TEXT_NODE) {
                    System.out.println("Error in parsing: can't find "
                                + "TEXT section.");
                            return;
                    }
    
                    // Retrieve the value
                    value = n.getNodeValue();
                    }
    
            public String getName()
            {
                    return (name);
            }
    
            public String getValue()
            {
                    return (value);
            }
    }
  3. Implementieren Sie die while-Schleife in EventReceptionThread, die auf Ereignisrückrufe wartet.

    EventReceptionThread wird in So definieren Sie den Ereignis-Empfangs-Thread beschrieben.

    while(true) {
                    Socket sock = listeningSock.accept();
                    Document doc = db.parse(sock.getInputStream());
                    Event event = new Event(doc);
                    client.processEvent(event);
                    sock.close();
            }

ProcedureSo führen Sie die Anwendung aus

Schritte
  1. Nehmen Sie Superuser-Status oder eine entsprechende administrative Rolle an.

  2. Führen Sie die Anwendung aus.


    # java CrnpClient CRNPHost CMPPort LokalerPort ...
    

    Der vollständige Code für die CrnpClient-Anwendung befindet sich in Anhang G, CrnpClient.java-Anwendung .