Sun Cluster Entwicklerhandbuch Datendienste für Solaris OS

Kapitel 12 CRNP

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.

Überblick über CRNP

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.

Abbildung 12–1 Funktionsweise des CRNP

Flussdiagramm, das die Funktionsweise des CRNP zeigt

Überblick über das CRNP-Protokoll

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.

Semantik des CRNP-Protokolls

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.

Abbildung 12–2 Kommunikationsfluss zwischen einem Client und einem Server

Flussdiagramm, das den Kommunikationsfluss zwischen Client und Server zeigt

Vom CRNP verwendete Meldungstypen

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:

  • Protokollversion

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

Die Formen ADD_CLIENT, ADD_EVENTS und REMOVE_EVENTS enthalten daneben eine uneingeschränkte Liste von Ereignistypen. Jeder Typ enthält folgende Informationen:

  • Ereignisklasse

  • Ereignisunterklasse (optional)

  • Liste der Namens- und Wertepaare (optional)

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:

  • Protokollversion

  • Ereignisklasse

  • Ereignisunterklasse

  • Hersteller

  • 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 (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:

  • Protokollversion

  • Fehlercode

  • Fehlermeldung

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.

Client-Registrierung beim Server

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.

Annahmen bezüglich der Serverkonfiguration durch Verwalter

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.

Client-Identifizierung durch den Server

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.

Senden von SC_CALLBACK_REG-Meldungen zwischen einem Client und dem Server

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.

Inhalt einer SC_CALLBACK_REG-Meldung

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.

Wenn ein Client:

ignoriert der Server diese Meldungen stillschweigend.

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

  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,

  5. Erhalten einer SC_REPLY-Meldung,

  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 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 REMOVE_CLIENT-Meldung,

  4. Warten auf eine SC_REPLY-Meldung,

  5. Erhalten einer SC_REPLY-Meldung,

  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 der möglichen Fehlermeldungen, die darin enthalten sein können.

Inhalt einer SC_REPLY-Meldung

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. 

Umgang des Clients mit Fehlerbedingungen

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:

Verfahren für Ereigniszustellungen vom Server an den Client

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:

  1. Abwarten einer vom Server hergestellten TCP-Verbindung,

  2. Akzeptieren der eingehenden Verbindung vom Server,

  3. Abwarten einer SC_EVENT-Meldung,

  4. Lesen der SC_EVENT-Meldung,

  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 dann erneut registrieren, indem er eine weitere SC_CALLBACK_REG-Meldung mit einer ADD_CLIENT-Meldung sendet. Erst dann kann er weitere Ereignisse erhalten.

Garantie der Ereigniszustellung

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.

Inhalt einer SC_EVENT-Meldung

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.

Authentisierung von Clients und Server durch das CRNP

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:

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

Erstellen einer Java-Anwendung, die CRNP verwendet

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.

Konfigurieren der Umgebung

Zunächst muss die Umgebung konfiguriert werden.

  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 Java 1.3.1 bzw. eine höhere Java-Version erforderlich.


  2. 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.

  3. 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.

Erste Schritte

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:

    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.

Analyse der Befehlszeilenargumente

    Anhand des Codes in Anhang G können Sie sehen, wie die Befehlszeilenargumente analysiert werden.

Definieren des Ereignisempfangs-Threads

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 –

Das Einrichten des XML wird weiter unten erläutert.


  1. 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;
    }

  2. 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();
    }

Registrieren und Deregistrieren von Rückmeldungen

Die Registrierung besteht aus folgenden Schritten:

  1. 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();
    }

  2. 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();
    }

Generieren des XML

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.

  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("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;
    }

  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;
    }

Erstellen der Registrierungs- und Deregistrierungsmeldungen

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.

  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);
    
            // 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);
    }

  2. 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);
    }

Konfigurieren des XML-Parsers

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);
    }

Analysieren der Registrierungsantwort

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.

  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
    {
            // 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);
    }

  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. 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;
    }

Analysieren der Rückmeldeereignisse

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.

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

  2. 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);
            }
    }

  3. 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();
            }

Ausführen der Anwendung

    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.