Guide des développeurs pour les services de données Sun Cluster 3.1 10/03

Chapitre 12 Protocole CRNP

Ce chapitre présente le protocole CRNP (Cluster Reconfiguration Notification Protocol). Le protocole CRNP permet aux applications de basculement et évolutives d'être prises en charge sur le cluster. Il offre plus particulièrement un mécanisme permettant aux applications de se connecter et d'être notifiées ultérieurement de façon asynchrone des événements de reconfiguration de Sun Cluster. Les services de données fonctionnant au sein du cluster et les applications exécutées à l'extérieur du cluster peuvent se connecter pour être notifiés des événements. Les événements sont générés lorsque l'adhésion au sein d'un cluster change et que l'état d'un groupe de ressources ou d'une ressource est modifié.

Présentation générale du protocole CRNP

Le protocole CRNP offre des mécanismes et des démons qui génèrent des événements de reconfiguration du cluster, les acheminent au sein du cluster et les transmettent aux clients intéressés.

Le démon cl_apid interagit avec les clients. Le gestionnaire de groupe de ressources (RGM) de Sun Cluster génère des événements de reconfiguration du cluster. Ces démons utilisent syseventd( 1M) pour transmettre les événements à chaque noeud local. Le démon cl_apid communique avec les clients intéressés via le protocole TCP/IP au moyen du langage XML (Extensible Markup Language).

Le diagramme suivant présente de façon générale le flux d'événements entre les composants CRNP : un client est exécuté sur le noeud 2 du cluster tandis que l'autre fonctionne sur un ordinateur n'appartenant pas au cluster.

Figure 12–1 Fonctionnement du protocole CRNP

Diagramme illustrant le fonctionnement du protocole CRNP

Présentation générale du protocole CRNP

Le protocole CRNP définit les couches application, présentation et session de la pile de protocoles de communication OSI (Open System Interconnect/interconnexion de systèmes ouverts) standard constituée de sept couches. La couche transport doit utiliser le protocole TCP et la couche réseau le protocole IP. Le protocole CRNP est indépendant des couches liaison de données et physique. Tous les messages de la couche application échangés au moyen du protocole CRNP utilisent le langage XML 1.0.

Sémantique du protocole CRNP

Les clients initient des communications en envoyant un message de connexion (SC_CALLBACK_RG) au serveur. Ce message spécifie le type d'événements dont les clients souhaitent être notifiés, ainsi que le port auquel ces événements peuvent être transmis. L'IP source de la connexion d'enregistrement et le port spécifié constituent l'adresse de rappel.

Chaque fois qu'un événement susceptible d'intéresser un client est généré au sein du cluster, le serveur contacte le client sur son adresse de rappel (IP et port) et lui transmet l'événement (SC_EVENT). Le serveur est hautement disponible car il fonctionne sur le cluster lui-même. Il enregistre les connexions clients en mémoire et en conserve la trace même après la réinitialisation du cluster.

Les clients se déconnectent en envoyant un message de connexion (SC_CALLBACK_RG contenant un message REMOVE_CLIENT) au serveur. Lorsque ce dernier leur renvoie le message SC_REPLY, les clients ferment leur connexion.

Le diagramme suivant illustre le flux de communications entre un client et un serveur :

Figure 12–2 Flux de communications entre un client et un serveur

Diagramme illustrant le flux de communications entre un client et un serveur

Types de messages utilisés par le protocole CRNP

Le protocole CRNP utilise trois types de messages basés sur le langage XML. Présentés brièvement dans le tableau suivant, ces trois types de messages sont également décrits plus en détail dans ce chapitre, de même que leur utilisation.

Type de message 

Description 

SC_CALLBACK_REG

Ce message se présente sous quatre formes : ADD_CLIENT, REMOVE_CLIENT, ADD_EVENTS et REMOVE_EVENTS . Elles contiennent toutes les informations suivantes :

  • version du protocole ;

  • port de rappel au format ASCII (et non au format binaire).

Les formes ADD_CLIENT, ADD_EVENTS et REMOVE_EVENTS contiennent également une liste non bornée des types d'événements contenant les informations suivantes :

  • classe d'événement ;

  • sous-classe d'événement (facultative) ;

  • liste des paires nom/valeurs (facultative).

La classe et la sous-classe d'événement définissent un « type d'événements » unique. La DTD (définition de type de document) permettant de générer les classes de SC_CALLBACK_REG est SC_CALLBACK_REG. Cette DTD est présentée plus en détail dans l'Annexe F.

SC_EVENT

Ce message contient les informations suivantes :

  • version du protocole ;

  • classe d'événement ;

  • sous-classe d'événement ;

  • fournisseur ;

  • éditeur ;

  • liste des paires nom/valeurs (0 ou plusieurs structures de données de paires nom/valeurs) :

    • nom (chaîne de caractères) ;

    • valeur (chaîne de caractères ou tableau de chaînes de caractères).

Les valeurs d'un message SC_EVENT ne sont pas typées. La DTD (définition de type de document) permettant de générer les classes de SC_EVENT est SC_EVENT. Cette DTD est présentée plus en détail dans l'Annexe F.

SC_REPLY

Ce message contient les informations suivantes :

  • version du protocole ;

  • code d'erreur ;

  • message d'erreur ;

La DTD (définition de type de document) permettant de générer les classes de SC_REPLY est SC_REPLY. Cette DTD est présentée plus en détail dans l'Annexe F.

Processus de connexion d'un client au serveur

Cette rubrique décrit comment un administrateur configure le serveur, comment les clients sont enregistrés et les informations transmises aux couches application et session, ainsi que les conditions d'erreur.

Hypothèses sur la configuration du serveur par les administrateurs

L'administrateur système doit configurer le serveur à l'aide d'une adresse IP à haut niveau de disponibilité (c'est-à-dire une adresse IP qui n'est pas liée à une machine particulière du cluster) ni à un numéro de port. L'administrateur doit transmettre cette adresse réseau aux clients potentiels. Le protocole CRNP ne définit pas comment ce nom de serveur est rendu accessible aux clients. Les administrateurs utilisent un service d'attribution de noms qui permet aux clients de rechercher l'adresse réseau du serveur de façon dynamique ou ajoutent le nom du réseau au fichier de configuration que le client doit lire. Le serveur fonctionne au sein du cluster comme un type de ressources de basculement.

Processus d'identification d'un client par le serveur

Chaque client est identifié de façon unique au moyen de son adresse de rappel, c'est-à-dire son adresse IP et son numéro de port. Le port est spécifié dans les messages SC_CALLBACK_REG et l'adresse IP obtenue à partir de la connexion d'enregistrement TCP. Le protocole CRNP suppose que des messages SC_CALLBACK_REG ultérieurs, possédant la même adresse de rappel, proviendront du même client, même si le port source de transmission des messages sera différent.

Processus de transmission des messages SC_CALLBACK_REG entre un client et le serveur

Un client initie une connexion en ouvrant une connexion TCP sur l'adresse IP et le numéro de port du serveur. Une fois la connexion TCP établie et prête pour la lecture, le client doit envoyer son message de connexion. Il doit s'agir d'un message SC_CALLBACK_REG correctement formaté qui n'est ni suivi ni précédé d'octets supplémentaires.

Une fois tous les octets émis, le client doit rester connecté pour recevoir une réponse du client. Si ce dernier ne formate pas le message correctement, le serveur n'enregistre pas le client et lui transmet un message d'erreur. Si le client ferme la connexion de prise avant que le serveur n'envoie sa réponse, ce dernier enregistre le client comme d'habitude.

Un client peut contacter le serveur à tout moment. Chaque fois qu'un client contacte le serveur, il doit envoyer un message SC_CALLBACK_REG. Si le serveur reçoit un message malformé, en dérangement ou invalide, il renvoie un message d'erreur au client.

Un client ne peut pas transmettre un message ADD_EVENTS, REMOVE_EVENTS ou REMOVE_CLIENT avant d'avoir envoyé un message ADD_CLIENT. Un client ne peut pas transmettre un message REMOVE_CLIENT avant d'avoir envoyé un message ADD_CLIENT .

Si un client envoie un message ADD_CLIENT alors qu'il est déjà connecté, le serveur peut supporter ce message. Dans cette situation, le serveur remplace la connexion client antérieure par la nouvelle qui est spécifiée dans le second message ADD_CLIENT.

Mais en règle générale, un client se connecte une seule fois au serveur par l'envoi d'un message ADD_CLIENT. De même, il se déconnecte une seule fois en envoyant au serveur un message REMOVE_CLIENT. Le protocole CRNP offre une plus grande souplesse aux clients qui doivent modifier de façon dynamique leur liste de types d'événements.

Contenu d'un message SC_CALLBACK_REG

Chaque message ADD_CLIENT, ADD_EVENTS et REMOVE_EVENTS contient une liste d'événements. Le tableau indiqué ci-dessous présente les types d'événements que le protocole CRNP accepte, y compris les paires nom/valeurs requises.

Si un client

le serveur ignore ces messages sans vous en avertir.

Classe et sous-classe 

Paire nom/valeurs 

Description 

EC_Cluster

ESC_cluster_membership

Requise : aucune 

Facultative : aucune 

Connexion pour toutes les notifications d'événement sur la modification des membres du cluster (suppression ou ajout d'un noeud) 

EC_Cluster

ESC_cluster_rg_state

Une seule requise comme suit : 

rg_name

Type de valeurs : chaîne de caractères 

Facultative : aucune 

Connexion pour toutes les notifications d'événement sur la modification des états relatifs au nom des groupes de ressources

EC_Cluster

ESC_cluster_r_state

Une seule requise comme suit : 

r_name

Type de valeurs : chaîne de caractères 

Facultative : aucune 

Connexion pour toutes les notifications d'événement sur la modification des états relatifs au nom des ressources

EC_Cluster

Sans effet 

Requise : aucune 

Facultative : aucune 

Connexion pour toutes les notifications d'événement de Sun Cluster 

Processus de réponse du client au serveur

La connexion effectuée, le serveur envoie le message SC_REPLY sur la connexion TCP ouverte que le client a utilisée pour lui transmettre sa requête de connexion. Le client ferme ensuite la connexion. Le client doit conserver la connexion TCP ouverte jusqu'à ce que le serveur lui transmette le message SC_REPLY.

Par exemple, le client :

  1. Ouvre une connexion TCP sur le serveur.

  2. Attend que la connexion devienne «modifiable».

  3. Envoie un message SC_CALLBACK_REG (contenant un message ADD_CLIENT).

  4. Attend de recevoir un message SC_REPLY.

  5. Reçoit un message SC_REPLY.

  6. Reçoit un message stipulant que le serveur a fermé la connexion (lecture de 0 octet du socket).

  7. Ferme la connexion.

Puis, ultérieurement il :
  1. Ouvre une connexion TCP sur le serveur.

  2. Attend que la connexion devienne «inscriptible ».

  3. Envoie un message SC_CALLBACK_REG (contenant un message REMOVE_CLIENT).

  4. Attend de recevoir un message SC_REPLY.

  5. Reçoit un message SC_REPLY.

  6. Reçoit un message stipulant que le serveur a fermé la connexion (lecture de 0 octet du socket).

  7. Ferme la connexion.

Chaque fois que le serveur reçoit un message SC_CALLBACK_REG d'un client, le serveur transmet un message SC_REPLY sur la même connexion ouverte indiquant si l'opération a réussi ou échoué. DTD XML SC_REPLY contient la DTD XML d'un message SC_REPLY, ainsi que les messages d'erreur éventuels que ce message peut contenir.

Contenu d'un message SC_REPLY

Un message SC_REPLY indique si une opération a réussi ou échoué. Il contient la version du message de protocole CRNP, un code d'état et un message d'état décrivant le code d'état de façon détaillée. Le tableau suivant présente les valeurs possibles du code d'état :

Code d'état 

Description 

OK

Le message a été traité avec succès. 

RETRY

La connexion du client a été rejetée par le serveur à cause d'une erreur transitoire (le client doit tenter de se reconnecter en utilisant d'autres paramètres). 

LOW_RESOURCE

Les ressources du cluster sont faibles et le client peut uniquement réessayer ultérieurement (l'administrateur système du cluster peut également augmenter les ressources sur le cluster). 

SYSTEM_ERROR

Un incident grave s'est produit. Contactez l'administrateur système du cluster. 

FAIL

Le refus d'une autorisation ou tout autre incident a provoqué l'échec de la connexion. 

MALFORMED

La requête XML est malformée et n'a pas pu être analysée. 

INVALID

La requête XML est invalide (elle n'est pas conforme aux spécifications XML). 

VERSION_TOO_HIGH

La version du message possède une résolution trop haute pour permettre de traiter le message avec succès. 

VERSION_TOO_LOW

La version du message possède une résolution trop basse pour permettre de traiter le message avec succès. 

Processus de gestion des conditions d'erreur par un client

Dans des conditions normales, un client envoyant un message SC_CALLBACK_REG reçoit une réponse précisant si la connexion a réussi ou échoué.

Pourtant, le serveur peut être confronté à une condition d'erreur l'empêchant de retransmettre le message SC_REPLY au client. Le cas échéant, la connexion a pu réussir avant que la condition d'erreur ne se produise, échouer ou tout simplement ne pas avoir encore été traitée.

Comme le serveur doit être exécuté sur le cluster en tant que serveur de basculement ou hautement disponible, cette condition d'erreur ne signifie pas que le service est interrompu. De fait, le serveur est rapidement en mesure d'envoyer des événements au client nouvellement connecté.

Pour pallier ces conditions, le client doit :

Processus de notification d'événement au client par le serveur

Comme les événements sont générés dans le cluster, le serveur CRNP les notifie à tous les clients qui se sont connectés pour recevoir les notifications de ce type. La notification se concrétise par l'envoi d'un message SC_EVENT à l'adresse de rappel des clients. Chaque notification d'événement utilise une nouvelle connexion TCP.

Juste après qu'un client se soit connecté pour être notifié d'un type d'événements à l'aide d'un message SC_CALLBACK_REG contenant un message ADD_CLIENT ou ADD_EVENT, le serveur lui transmet le dernier événement de ce type. Le client est ainsi informé de l'état actuel du système dont seront issus les événements ultérieurs.

Lorsque le serveur ouvre une connexion TCP sur le client, il envoie exactement un message SC_EVENT sur la connexion. Il procède ensuite à une fermeture bidirectionnelle.

Par exemple, le client :

  1. Attend que le serveur initie une connexion TCP.

  2. Accepte la connexion entrante du serveur.

  3. Attend de recevoir un message SC_EVENT.

  4. Lit un message SC_EVENT.

  5. Reçoit un message stipulant que le serveur a fermé la connexion (lecture de 0 octet du socket).

  6. Ferme la connexion.

Une fois tous les clients connectés, ils doivent écouter leur adresse de rappel (adresse IP et numéro de port) en permanence en vue d'une connexion entrante de notification d'événement.

Si le serveur ne parvient pas à contacter le client pour lui notifier un événement, il réessaie suivant l'intervalle et le nombre de fois que vous avez spécifié. Si toutes les tentatives échouent, le client est supprimé de la liste des clients du serveur. Il doit alors se reconnecter en envoyant un autre message SC_CALLBACK_REG contenant un message ADD_CLIENT avant de pouvoir recevoir d'autres notifications d'événement.

Mécanisme garantissant la notification des événements

Au sein du cluster, un ordre de génération des événements est préservé afin de notifier chaque client. En d'autres termes, si l'événement A est généré sur le cluster avant l'événement B, le client X reçoit l'événement A avant l'événement B. Par contre, l'ordre de notification des événements à tous les clients n'est pas préservé. Cela signifie que le client Y peut recevoir les événements A et B avant que le client X ne soit notifié de l'événement A. De cette façon, les clients dont la connexion est lente ne retardent pas la notification à tous les clients.

Tous les événements notifiés par le serveur (à l'exception du premier événement d'une sous-classe et des événements qui suivent des erreurs serveur) se produisent en réponse aux événements réels que génère le cluster, hormis lorsque le serveur est confronté à une erreur lui faisant ignorer les événements générés par le cluster. Le cas échéant, il génère un événement pour chaque type d'événements représentant l'état actuel du système. Chaque événement est transmis aux clients qui ont notifié leur souhait de recevoir ce type d'événements.

Chaque événement respecte la sémantique « au moins une fois », ce qui signifie que le serveur peut envoyer le même événement au client plus d'une fois. Cette faculté est indispensable lorsque le serveur redevient opérationnel après une interruption provisoire et qu'il ne peut déterminer si le client a reçu les dernières informations transmises.

Contenu d'un message SC_EVENT

Le message SC_EVENT contient le message exact qui a été généré au sein du cluster, retranscrit au format de message XML SC_EVENT. Le tableau indiqué ci-dessous présente les types d'événements que le protocole CRNP envoie, y compris les paires nom/valeurs requises, l'éditeur et le fournisseur.

Classe et sous-classe 

Éditeur et fournisseur 

Paires nom/valeurs 

Notes 

EC_Cluster

ESC_cluster_membership

Éditeur : rgm 

Fournisseur : SUNW 

Nom : node_list

Type de valeurs : tableau de chaînes de caractères 

Nom : state_list

Type de valeurs : tableau de chaînes de caractères 

Les positions des éléments du tableau de state_list sont synchronisées avec celles de node_list. Cela signifie que l'état du noeud répertorié en première position dans le tableau node_list se trouve en première position dans le tableau state_list.

state_list contient uniquement les numéros ASCII. Chaque numéro représente le numéro actuel du noeud dans le cluster. Si ce numéro correspond à celui reçu dans un message précédent, cela signifie que la relation qui lie le noeud au cluster n'a pas changé (disparu/connecté/déconnecté). Si ce numéro est -1, le noeud n'est pas un membre du cluster. Si ce numéro n'est pas un nombre négatif, le noeud est un membre du cluster.

Les autres noms commençant par ev_ et les valeurs connexes peuvent être présents mais ils ne sont pas destinés à être utilisés par les clients.

EC_Cluster

ESC_cluster_rg_state

Éditeur : rgm 

Fournisseur : SUNW 

Nom : rg_name

Type de valeurs : chaîne de caractères 

Nom : node_list

Type de valeurs : tableau de chaînes de caractères 

Nom : state_list

Type de valeurs : tableau de chaînes de caractères 

Les positions des éléments du tableau de state_list sont synchronisées avec celles de node_list. Cela signifie que l'état du noeud répertorié en première position dans le tableau node_list se trouve en première position dans le tableau state_list.

state_list contient les représentations de chaînes de caractères de l'état du groupe de ressources. Les valeurs valides correspondent à celles que vous pouvez rechercher et extraire à l'aide des commandes scha_cmds( 1HA).

Les autres noms commençant par ev_ et les valeurs connexes peuvent être présents mais ils ne sont pas destinés à être utilisés par les clients.

EC_Cluster

ESC_cluster_r_state

Éditeur : rgm 

Fournisseur : SUNW 

Trois requises comme suit :  

Nom : r_name

Type de valeurs : chaîne de caractères 

Nom : node_list

Type de valeurs : tableau de chaînes de caractères 

Nom : state_list

Type de valeurs : tableau de chaînes de caractères 

Les positions des éléments du tableau de state_list sont synchronisées avec celles de node_list. Cela signifie que l'état du noeud répertorié en première position dans le tableau node_list se trouve en première position dans le tableau state_list.

state_list contient les représentations de chaînes de caractères de l'état de la ressource. Les valeurs valides correspondent à celles que vous pouvez rechercher et extraire à l'aide des commandes scha_cmds( 1HA).

Les autres noms commençant par ev_ et les valeurs connexes peuvent être présents mais ils ne sont pas destinés à être utilisés par les clients.

Processus d'authentification des clients et du serveur par le protocole CRNP

Le serveur authentifie un client en utilisant une forme de wrappers TCP. L'adresse IP source du message de connexion (qui sert également d'adresse IP de rappel à laquelle les événements sont envoyés) doit figurer dans la liste des clients autorisés sur le serveur. En outre, cette adresse et ce message ne peuvent pas figurer dans la liste des clients refusés. Si ces informations ne figurent pas dans la liste, le serveur refuse la requête et renvoie un message d'erreur au client.

Lorsque le serveur reçoit un message SC_CALLBACK_REG ADD_CLIENT d'un client, les messages SC_CALLBACK_REG ultérieurs de ce client doivent contenir une adresse IP source identique à celle figurant dans le premier message. Si le serveur CRNP reçoit un message SC_CALLBACK_REG qui n'est pas conforme à cette exigence, il :

Ce mécanisme de sécurité permet de prévenir les attaques de refus de service tandis qu'une personne tente de déconnecter un client légitime.

Les clients doivent également authentifier le serveur de façon similaire. Les clients ne doivent accepter que les notifications d'événement d'un serveur dont l'adresse IP source et le numéro de port sont identiques à l'adresse IP de connexion et au numéro de port que le client a utilisé.

Comme il est prévu que les clients du service CRNP soient protégés par le pare-feu du cluster, le protocole CRNP n'intègre pas d'autres mécanismes de sécurité.

Création d'une application Java utilisant le protocole CRNP

L'exemple suivant illustre le processus de développement d'une application Java, CrnpClient, utilisant le protocole CRNP. Cette application enregistre les rappels d'événements avec le serveur CRNP sur le cluster, les écoute, puis traite les événements en imprimant leur contenu. Pour terminer, elle supprime les rappels d'événements.

Gardez ces informations à l'esprit pendant que vous étudiez cet exemple.

Configuration de l'environnement

Vous devez commencer par configurer votre environnement.

  1. Téléchargez puis installez JAXP ainsi que la version appropriée du compilateur Java et de la machine virtuelle.

    Vous trouvez de plus amples instructions à l'adresse suivante : http://java.sun.com/xml/jaxp/index.html.


    Remarque :

    cet exemple requiert Java 1.3.1 ou une version ultérieure.


  2. N'oubliez pas de spécifier un classpath sur la ligne de commande de compilation, afin que le compilateur puisse trouver les classes JAXP. Depuis le répertoire dans lequel figure le fichier source, entrez :


    % javac -classpath RACINE_JAXP/dom.jar:RACINE_JAXPjaxp-api. \
    jar:RACINE_JAXPsax.jar:RACINE_JAXPxalan.jar:RACINE_JAXP/xercesImpl \
    .jar:RACINE_JAXP/xsltc.jar -sourcepath . NOM_FICHIER_SOURCE.java
    

    RACINE_JAXP correspond au chemin d'accès, relatif ou absolu, au répertoire dans lequel se trouvent les fichiers jar de JAXP et NOM_FICHIER_SOURCE au nom du fichier Java source.

  3. Lors de l'exécution de l'application, spécifiez le classpath pour que l'application puisse charger les fichiers de classe JAXP appropriés (veuillez noter que le premier chemin d'accès spécifié dans le classpath correspond au répertoire courant) :


    java -cp .:RACINE_JAXP/dom.jar:RACINE_JAXPjaxp-api. \
    jar:RACINE_JAXPsax.jar:RACINE_JAXPxalan.jar:RACINE_JAXP/xercesImpl \
    .jar:RACINE_JAXP/xsltc.jar NOM_FICHIER_SOURCE ARGUMENTS
    

    L'environnement est à présent configuré. Vous pouvez développer l'application.

Premiers pas

Dans cette première partie de notre exemple, vous allez créer une classe de base appelée CrnpClient à l'aide d'une méthode principale qui analyse les arguments de la ligne de commande et crée un objet CrnpClient. Cet objet transfère les arguments de la ligne de commande à la classe, attend que l'utilisateur arrête l'application, exécute la commande shutdown sur la classe CrnpClient , puis se ferme.

Le constructeur de la classe CrnpClient doit exécuter les tâches suivantes :

    Créer le code Java mettant en oeuvre la logique précédente.

    L'exemple suivant présente le code du squelette de la classe CrnpClient. Les mises en oeuvre des quatre méthodes d'assistant référencées dans les méthodes d'arrêt et du constructeur sont décrites ultérieurement. Veuillez noter que le code important tous les packages requis est indiqué.


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

    Les variables membres sont traitées plus en détail par la suite.

Analyse des arguments de la ligne de commande

    Pour découvrir comment analyser les arguments de la ligne de commande, reportez-vous au code de l'Annexe G.

Définition d'un thread de réception d'événements

Vous devez vous assurer, au niveau du code, que les événements sont reçus sur un thread distinct, afin que votre application puisse continuer d'exécuter d'autres tâches tandis que le thread d'événements attend la notification de rappel d'événements.


Remarque :

la configuration XML est traitée ultérieurement.


  1. Dans votre code, définissez une sous-classe Thread appelée EventReceptionThread créant une classe ServerSocket et attendant que le socket reçoive des événements.

    Dans cette partie du code, les événements ne sont ni lus ni traités. La lecture et le traitement des événements sont abordés ultérieurement. La classe EventReceptionThread crée une classe ServerSocket sur l'adresse générique du protocole interréseau. EventReceptionThread conserve également l'objet CrnpClient en référence, afin de pouvoir lui transmettre des événements.


    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();
                                    // Créer l'événement à partir du flux du sock et le traiter
                                    sock.close();
                            }
                            // IMPOSSIBLE À ATTEINDRE
    
                    } catch (Exception e) {
                            System.out.println(e);
                            System.exit(1);
                    }
            }
    
            /* private member variables */
            private ServerSocket listeningSock;
            private CrnpClient client;
    }

  2. Maintenant que vous savez comment fonctionne la classe EventReceptionThread, créez un objet createEvtRecepThr :


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

Connexion et déconnexion des rappels

La connexion recoupe les tâches suivantes :

  1. Créez le code Java mettant en oeuvre la logique précédente.

    L'exemple suivant présente la mise en oeuvre de la méthode registerCallbacks de la classe CrnpClient (qui est appelée par le constructeur CrnpClient). Les appels des fonctions createRegistrationString() et readRegistrationReply() sont décrits ultérieurement.

    regIp et regPort sont des objets membres définis par le constructeur.


    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. Mettez en oeuvre la méthode unregister. Cette méthode est appelée par la méthode shutdown de la classe CrnpClient. La mise en oeuvre de createUnregistrationString est présentée plus en détail ultérieurement.


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

Génération du langage XML

Maintenant que vous avez défini la structure de l'application et rédigé l'intégralité du code de gestion de réseaux, vous rédigez le code qui génère et analyse XML. Commencez par rédiger le code générant le message de connexion XML SC_CALLBACK_REG.

Un message SC_CALLBACK_REG est constitué d'un type de connexion (ADD_CLIENT, REMOVE_CLIENT, ADD_EVENTS ou REMOVE_EVENTS), d'un port de rappel et d'une liste d'événements intéressants. Chaque événement comprend une classe et une sous-classe, suivies d'une liste de paires nom/valeurs.

Dans cette partie de l'exemple, vous rédigez une classe CallbackReg qui enregistre le type de connexion, le port de rappel et la liste des événements de connexion. Cette classe peut également se sérialiser en message XML SC_CALLBACK_REG.

Cette classe comprend la méthode convertToXml. Cette méthode intéressante crée une chaîne de message XML SC_CALLBACK_REG à partir des membres de la classe. La documentation JAXP disponible à l'adresse suivante http://java.sun.com/xml/jaxp/index.html présente plus en détail le code de cette méthode.

La mise en oeuvre de la classe Event est présentée ci-après. Veuillez noter que la classe CallbackReg utilise une classe Event qui mémorise un événement et peut le convertir en Element XML.

  1. Créez le code Java mettant en oeuvre la logique précédente.


    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) {
                            // L'analyseur avec options spécifiques ne peut être créé
                            pce.printStackTrace();
                            System.exit(1);
                    }
    
                    // Créer l'élément racine
                    Element root = (Element) document.createElement(
                        "SC_CALLBACK_REG");
    
                    // Ajouter les attributs
                    root.setAttribute("VERSION", "1.0");
                    root.setAttribute("PORT", port);
                    root.setAttribute("regType", regType);
    
                    // Ajouter les événements
                    for (int i = 0; i < regEvents.size(); i++) {
                            Event tempEvent = (Event)
                                (regEvents.elementAt(i));
                            root.appendChild(tempEvent.createXmlElement(
                                document));
                    }
                    document.appendChild(root);
    
                    // Tout convertir en chaîne
                    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. Mettez en oeuvre les classes Event et NVPair.

    Veuillez noter que la classe CallbackReg utilise une classe Event qui utilise elle-même une classe NVPair.


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

Création des messages de connexion et de déconnexion

Maintenant que vous avez créé les classes d'assistant générant les messages XML, vous pouvez écrire la mise en oeuvre de la méthode createRegistrationString. Cette méthode est appelée par la méthode registerCallbacks présentée dans la rubrique Connexion et déconnexion des rappels.

createRegistrationString crée un objet CallbackReg et définit son type et son port de connexion. createRegistrationString crée divers événements à l'aide des méthodes d'assistant createAllEvent, createMembershipEvent, createRgEvent et createREvent. Chaque événement est ajouté à l'objet CallbackReg une fois cet objet créé. Enfin, createRegistrationString appelle la méthode convertToXml sur l'objet CallbackReg pour rechercher et extraire les messages XML au format String.

Veuillez noter que la variable membre regs enregistre les arguments de la ligne de commande qu'un utilisateur fournit à l'application. Le cinquième argument et les arguments ultérieurs spécifient les événements pour lesquels l'application doit se connecter. Le quatrième argument spécifie le type de connexion mais il est ignoré dans cet exemple. Le code complet présenté dans l'Annexe G montre comment utiliser ce quatrième argument.

  1. Créez le code Java mettant en oeuvre la logique précédente.


    private String createRegistrationString() throws Exception
    {
            CallbackReg cbReg = new CallbackReg();
            cbReg.setPort("" + localPort);
    
            cbReg.setRegType(CallbackReg.ADD_CLIENT);
    
            // ajouter les événements
            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. Créez la chaîne de caractères de déconnexion.

    La création de la chaîne de caractères de déconnexion est plus facile à créer que la chaîne de caractères de connexion car vous n'avez pas à prendre en charge les événements :


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

Configuration de l'analyseur XML

Maintenant que vous avez créé le code de génération XML et de gestion de réseaux de l'application, l'étape finale consiste à analyser et traiter la réponse de connexion et les rappels d'événement. Le constructeur CrnpClient appelle une méthode setupXmlProcessing. Cette méthode crée un objet DocumentBuilderFactory et définit plusieurs propriétés d'analyse sur cet objet. La documentation JAXP disponible à l'adresse suivante http://java.sun.com/xml/jaxp/index.html présente plus en détail cette méthode.

    Créez le code Java mettant en oeuvre la logique précédente.


    private void setupXmlProcessing() throws Exception
    {
            dbf = DocumentBuilderFactory.newInstance();
    
            // Nous n'avons pas besoin de valider
            dbf.setValidating(false);
            dbf.setExpandEntityReferences(false);
    
            // Nous souhaitons ignorer les commentaires et les espaces
            dbf.setIgnoringComments(true);
            dbf.setIgnoringElementContentWhitespace(true);
    
            // Combiner les sections CDATA en noeuds TEXT.
            dbf.setCoalescing(true);
    }

Analyse de la réponse de connexion

Pour analyser le message XML SC_REPLY que le serveur CRNP transmet en réponse à un message de connexion ou de déconnexion, vous avez besoin d'une classe d'assistant RegReply. Vous pouvez créer cette classe à partir d'un document XML. Elle propose les mécanismes d'accès du code et du message d'état. Pour analyser le flux XML du serveur, vous devez créer un nouveau document XML et utiliser cette méthode d'analyse (la documentation JAXP disponible à l'adresse suivante http://java.sun.com/xml/jaxp/index.html présente plus en détail cette méthode).

  1. Créez le code Java mettant en oeuvre la logique précédente.

    Veuillez noter que la méthode readRegistrationReply utilise la nouvelle classe RegReply.


    private void readRegistrationReply(InputStream stream) throws Exception
    {
            // Créer le constructeur de document
            DocumentBuilder db = dbf.newDocumentBuilder();
            db.setErrorHandler(new DefaultHandler());
    
            //analyser le fichier d'entrée
            Document doc = db.parse(stream);
    
            RegReply reply = new RegReply(doc);
            reply.print(System.out);
    }

  2. Mettez en oeuvre la classe RegReply.

    Veuillez noter que la méthode retrieveValues parcourt l'arborescence DOM dans le document XML et extrait le code et le message d'état. La documentation JAXP disponible à l'adresse suivante http://java.sun.com/xml/jaxp/index.html contient de plus amples informations.


    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;
    
                    // Trouver l'élément SC_REPLY.
                    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);
    
                    // Récupérer la valeur de l'attribut statusCode
                    statusCode = ((Element)n).getAttribute("STATUS_CODE");
    
                    // Trouver l'élément SC_STATUS_MSG
                    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;
                    }
                    // Obtenir la section TEXT, le cas échéant.
                    n = nl.item(0).getFirstChild();
                    if (n == null || n.getNodeType() != Node.TEXT_NODE) {
                            // Si elle n'existe pas, ce n'est pas une erreur, retourner
                            sans avertissement.
                            return;
                    }
    
                    // Récupérer la valeur
                    statusMsg = n.getNodeValue();
            }
    
            private String statusCode;
            private String statusMsg;
    }

Analyse des événements de rappel

L'étape finale consiste à analyser et traiter les événements de rappels réels. Pour faciliter cette tâche, modifiez la classe Event que vous avez créée à la rubrique Génération du langage XML, afin qu'elle puisse créer un Event à partir d'un document XML et un Element XML. Cette modification requiert un constructeur supplémentaire (exécutant un document XML), une méthode retrieveValues, l'ajout de deux variables membres (vendor et publisher), des méthodes de mécanisme d'accès à tous les champs et une méthode d'impression.

  1. Créez le code Java mettant en oeuvre la logique précédente.

    Veuillez noter que ce code est identique à celui de la classe RegReply décrite dans la rubrique Analyse de la réponse de connexion.


            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;
    
                    // Trouver l'élément SC_EVENT.
                    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);
    
                    //
                    // Récupérer la valeurs des attributs de CLASS, SUBCLASS,
                    // VENDOR et PUBLISHER.
                    //
                    regClass = ((Element)n).getAttribute("CLASS");
                    regSubclass = ((Element)n).getAttribute("SUBCLASS");
                    publisher = ((Element)n).getAttribute("PUBLISHER");
                    vendor = ((Element)n).getAttribute("VENDOR");
    
                    // Récupérer toutes les paires nv
                    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. Mettez en oeuvre les autres constructeurs et méthodes de la classe NVPair qui prennent en charge l'analyse XML.

    Les modifications apportées à la classe Event (voir Étape 1) doivent être identiques aux modifications apportées à la classe NVPair.


            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;
    
                    // Trouver l'élément NAME
                    nl = elem.getElementsByTagName("NAME");
                    if (nl.getLength() != 1) {
                            System.out.println("Error in parsing: can't find "
                                + "NAME node.");
                            return;
                    }
                    // Obtenir la section TEXT
                    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;
                    }
    
                    // Récupérer la valeur
                    name = n.getNodeValue();
    
                    // Obtenir maintenant l'élément de valeur
                    nl = elem.getElementsByTagName("VALUE");
                    if (nl.getLength() != 1) {
                            System.out.println("Error in parsing: can't find "
                                + "VALUE node.");
                            return;
                    }
                    // Obtenir la section TEXT
                    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;
                    }
    
                    // Récupérer la valeur
                    value = n.getNodeValue();
                    }
    
            public String getName()
            {
                    return (name);
            }
    
            public String getValue()
            {
                    return (value);
            }
    }

  3. Mettez en oeuvre une boucle while dans la classe EventReceptionThread attendant les rappels d'événements (EventReceptionThread est présenté dans la rubrique Définition d'un thread de réception d'événements).


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

Exécution de l'application

    Exécutez votre application.


    # java CrnpClient hôte_crnp port_crnp port_local ...
    

    Le code complet de l'application CrnpClient figure à l'Annexe G.