Ce chapitre présente le protocole CRNP (Cluster Reconfiguration Notification Protocol, protocole de notification des reconfigurations de clusters). Ce protocole permet aux applications évolutives et de basculement de “reconnaître les clusters.” Plus précisément, il propose un mécanisme qui permet aux applications de s'enregistrer et par la suite, d'être informées de manière 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 s'enregistrer pour être avertis des événements. Les événements sont générés lorsque l'appartenance à un cluster change et que l'état d'un groupe de ressources ou d'une ressource est modifié.
L'implémentation du type de ressources SUNW.Event fournit des services CRNP à haute disponibilité sur Sun Cluster. Cette implémentation est décrite plus en détail à la page de manuel SUNW.Event(5).
Ce chapitre contient les rubriques suivantes :
Processus de notification des événements au client par le serveur
Processus d'authentification des clients et du serveur par le protocole CRNP
Exemple de création d'une application Java utilisant le protocole CRNP
Le protocole CRNP définit les couches application, présentation et session de la pile de protocoles 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.
Le protocole CRNP propose des mécanismes et des démons qui génèrent des événements de reconfiguration de cluster, les acheminent via le 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, Resource Group Manager) de Sun Cluster génère des événements de reconfiguration de cluster. Ce démon utilise syseventd pour transmettre des événements à chaque noeud local. Le démon cl_apid communique avec les clients intéressés, via le protocole TCP/IP, en langage XML (Extensible Markup Language).
Le schéma suivant met en évidence 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.
Les clients débutent des communications en envoyant un message d'enregistrement (SC_CALLBACK_RG) au serveur. Ce message indique le type d'événements dont les clients souhaitent être avertis, 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 à 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 client en mémoire et en conserve la trace même après la réinitialisation du cluster.
Les clients annulent leur enregistrement en envoyant un message d'enregistrement (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 schéma suivant met en évidence le flux de communications entre un client et un serveur.
Le protocole CRNP utilise trois types de messages XML. Leur utilisation est décrite dans le tableau suivant. Ces trois types de messages sont également décrits plus en détail dans ce chapitre.
Type de message utilisé par le protocole CRNP |
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 :
ADD_CLIENT, ADD_EVENTS et REMOVE_EVENTS contiennent également une liste non bornée de types d'événements, comprenant chacun les informations suivantes :
La classe et la sous-classe d'événement définissent un “type d'événements” unique. La DTD (définition du type de document) qui permet de générer les classes de SC_CALLBACK_REG est SC_CALLBACK_REG. Elle est décrite plus en détail à l'Annexe F, Définitions de types de documents pour le protocole CRNP. |
SC_REPLY |
Ce message contient les informations suivantes :
La DTD qui permet de générer les classes de SC_REPLY est SC_REPLY. Elle est décrite plus en détail à l'Annexe F, Définitions de types de documents pour le protocole CRNP. |
SC_EVENT |
Ce message contient les informations suivantes :
Les valeurs d'un message SC_EVENT ne sont pas typées. La DTD qui permet de générer les classes de SC_EVENT est SC_EVENT. Elle est décrite plus en détail à l'Annexe F, Définitions de types de documents pour le protocole CRNP. |
Cette section indique comment un administrateur de cluster configure le serveur, comment les clients sont identifiés, comment les informations sont envoyées via les couches application et session, ainsi que les conditions d'erreur.
L'administrateur du cluster doit configurer le serveur à l'aide d'une adresse IP à haute disponibilité (une adresse IP qui n'est pas liée à une machine en particulier du cluster) et d'un numéro de port. L'administrateur du cluster 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. L'administrateur du cluster utilise un service d'attribution de noms, ce qui permet aux clients de trouver l'adresse réseau du serveur de manière dynamique, ou ajoute le nom du réseau à un fichier de configuration que le client doit lire. Le serveur fonctionne au sein du cluster comme un type de ressource de basculement.
Chaque client est identifié de manière unique par 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 considère que les messages SC_CALLBACK_REG suivants ayant la même adresse de rappel proviennent du même client, même si le port source de transmission des messages est différent.
Pour procéder à l'enregistrement, le client commence par ouvrir 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 l'écriture, le client doit envoyer son message d'enregistrement. Il doit s'agir d'un message SC_CALLBACK_REG correctement formaté, qui ne doit pas être 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 serveur. S'il ne formate pas le message correctement, le serveur n'enregistre pas le client et lui transmet un message d'erreur. Toutefois, si le client ferme la connexion (au niveau du socket) avant que le serveur n'ait envoyé sa réponse, ce dernier enregistre le client en suivant la procédure habituelle.
Les clients peuvent 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é, non fonctionnel ou non valide, il renvoie un message d'erreur au client.
Pour transmettre un message ADD_EVENTS, REMOVE_EVENTS ou REMOVE_CLIENT, un client doit d'abord envoyer un message ADD_CLIENT. De même, avant de transmettre un message REMOVE_CLIENT, il doit envoyer un message ADD_CLIENT.
Si un client envoie un message ADD_CLIENT alors qu'il est déjà enregistré, le serveur peut tolérer ce message. Il remplace l'enregistrement client antérieur par le nouvel enregistrement spécifié dans le second message ADD_CLIENT.
Mais en règle générale, un client s'enregistre une seule fois auprès du serveur par l'envoi d'un message ADD_CLIENT. Un client annule une seule fois son enregistrement en envoyant un message REMOVE_CLIENT au serveur. Le protocole CRNP offre une plus grande souplesse aux clients qui doivent modifier de façon dynamique leur liste de types d'événements.
Chaque message ADD_CLIENT, REMOVE_CLIENT, ADD_EVENTS et REMOVE_EVENTS contient une liste d'événements. Le tableau ci-après présente les types d'événements que le protocole CRNP accepte, avec les paires nom/valeurs requises.
Si un client effectue l'une des actions suivantes, le serveur ignore ces messages sans vous en avertir :
Il envoie un message REMOVE_EVENTS qui spécifie un ou plusieurs types d'événements pour lesquels il ne s'est pas enregistré précédemment.
Il s'enregistre deux fois pour le même type d'événement.
Classe et sous-classe |
Paire nom/valeurs |
Description |
---|---|---|
EC_Cluster ESC_cluster_membership |
Requise : aucune En option : aucune |
Enregistrement pour toutes les notifications d'événement de modification de l'appartenance au 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 En option : aucune |
Enregistrement pour toutes les notifications d'événements de modification d'état du groupe de ressources name |
EC_Cluster ESC_cluster_r_state |
Une seule requise comme suit : r_name Type de valeurs : chaîne de caractères En option : aucune |
Enregistrement pour toutes les notifications d'événement de modification d'état de la ressource nom |
EC_Cluster Aucun |
Requise : aucune En option : aucune |
Enregistrement pour toutes les notifications d'événement de Sun Cluster |
Une fois l'enregistrement traité, le serveur qui a reçu la demande d'enregistrement envoie le message SC_REPLY sur la connexion TCP ouverte par le client. Le serveur ferme la connexion. Le client doit laisser la connexion TCP ouverte jusqu'à ce que le serveur lui transmette le message SC_REPLY.
Par exemple, le client :
Ouvre une connexion TCP sur le serveur.
Attend que la connexion devienne « inscriptible ».
Envoie un message SC_CALLBACK_REG (contenant un message ADD_CLIENT).
Attend un message SC_REPLY du serveur.
Reçoit un message SC_REPLY du serveur.
Reçoit un message stipulant que le serveur a fermé la connexion (lecture de 0 octet du socket).
Ferme la connexion.
Un peu plus tard, le client :
Ouvre une connexion TCP sur le serveur.
Attend que la connexion devienne « inscriptible ».
Envoie un message SC_CALLBACK_REG (contenant un message REMOVE_CLIENT).
Attend un message SC_REPLY du serveur.
Reçoit un message SC_REPLY du serveur.
Reçoit un message stipulant que le serveur a fermé la connexion (lecture de 0 octet du socket).
Ferme la connexion.
Chaque fois que le serveur reçoit un message SC_CALLBACK_REG d'un client, il envoie, sur la même connexion ouverte, un message SC_REPLY 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 éventuels messages d'erreur que ce message peut contenir.
Un message SC_REPLY indique si une opération a réussi ou a é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 plus en détail. 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 temporaire. Le client doit faire une nouvelle tentative de connexion avec d'autres arguments. |
LOW_RESOURCE |
Les ressources du cluster sont faibles, le client peut seulement réessayer ultérieurement. L'administrateur du cluster peut également en augmenter les ressources. |
SYSTEM_ERROR |
Un incident grave s'est produit. Contactez l'administrateur 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 n'est pas valide, c'est-à-dire qu'elle n'est pas conforme aux spécifications XML. |
VERSION_TOO_HIGH |
La version du message est trop élevée pour que le message soit traité avec succès. |
VERSION_TOO_LOW |
La version du message est trop basse pour que le message soit traité avec succès. |
Dans des conditions normales, un client qui envoie un message SC_CALLBACK_REG reçoit une réponse précisant si l'enregistrement a réussi ou a échoué.
Toutefois, le serveur peut être confronté à une condition d'erreur pendant l'enregistrement, ce qui l'empêche de transmettre un message SC_REPLY au client. L'enregistrement a pu réussir avant l'erreur, échouer ou ne pas avoir été traité.
Étant donné que le serveur doit fonctionner en tant que serveur de basculement ou de haute disponibilité dans le cluster, cette condition d'erreur ne signifie pas que le service est interrompu. De fait, le serveur peut rapidement commencer à envoyer des événements au client nouvellement enregistré.
Pour pallier ces conditions, votre client doit effectuer les opérations suivantes :
Imposer un délai d'expiration au niveau de l'application pour les connexions d'enregistrement en attente d'un message SC_REPLY, délai après lequel il devra réessayer de s'enregistrer.
Commencer à écouter son adresse IP de rappel et son numéro de port en vue de recevoir des notifications d'événement avant d'être enregistré pour la notification de rappel d'événement. Il doit attendre un message de confirmation d'enregistrement et des notifications d'événements en parallèle. S'il commence à recevoir des notifications d'événements avant de recevoir un message de confirmation, il doit fermer la connexion d'enregistrement.
À mesure que les événements sont générés dans le cluster, le serveur CRNP les notifie à tous les clients qui ont demandé à être avertis de ce type d'événements. 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.
Aussitôt un client enregistré pour un type d'événement (à 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 peut déterminer 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 :
Attend que le serveur initie une connexion TCP.
Accepte la connexion entrante du serveur.
Attend un message SC_EVENT du serveur.
Lit un message SC_EVENT du serveur.
Reçoit un message stipulant que le serveur a fermé la connexion (lecture de 0 octet du socket).
Ferme la connexion.
Une fois enregistrés, tous les clients doivent écouter leur adresse de rappel (adresse IP et numéro de port) en permanence afin de recevoir les éventuelles connexions entrantes 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. Pour recevoir d'autres notifications d'événements, le client doit se ré-enregistrer en envoyant un autre message SC_CALLBACK_REG contenant un message ADD_CLIENT.
Au sein du cluster, l'avertissement des clients suit l'ordre de génération des événements. En d'autres termes, si un événement A est généré au sein du cluster avant un événement B, le client X reçoit l'événement A avant l'événement B. Cependant, l'ordre d'envoi des notifications d'événements à tous les clients n'est pas conservé. 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 », c'est-à-dire que le serveur peut envoyer la même notification d'événement à un client plus d'une fois. Cette autorisation est nécessaire lorsque le serveur s'arrête momentanément et qu'il est incapable de déterminer si le client a reçu les toutes dernières informations lorsqu'il se remet à fonctionner.
Le message SC_EVENT contient le message réel qui est généré au sein du cluster, retranscrit au format de message XML SC_EVENT. Le tableau ci-dessous présente les types d'événements que le protocole CRNP envoie, avec les paires nom/valeurs requises, l'éditeur et le fournisseur.
Les positions des éléments du tableau 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.
Les autres noms commençant par ev_ et les valeurs connexes peuvent être présents, mais ne sont pas destinés à être utilisés par les clients.
Classe et sous-classe |
Éditeur et fournisseur |
Paire nom/valeurs |
---|---|---|
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 Le tableau state_list contient uniquement les numéros au format ASCII. Chaque numéro représente le numéro actuel du nœud 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 nœud est un membre du cluster. Type de valeurs : tableau de chaînes de caractères |
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 Le tableau state_list contient les représentations (sous la forme de chaînes de caractères) de l'état du groupe de ressources. Les valeurs valides sont celles que vous pouvez extraire à l'aide des commandes scha_cmds(1HA). Type de valeurs : tableau de chaînes de caractères |
EC_Cluster ESC_cluster_r_state |
Éditeur : rgm Fournisseur : SUNW |
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 Le tableau state_list contient les représentations (en chaînes de caractères) de l'état de la ressource. Les valeurs valides sont celles que vous pouvez extraire à l'aide des commandes scha_cmds(1HA). Type de valeurs : tableau de chaînes de caractères |
Le serveur authentifie un client à l'aide d'une forme de wrappers TCP. L'adresse IP source du message d'enregistrement, qui sert également d'adresse IP de rappel à laquelle les notifications d'événements sont envoyées, doit figurer dans la liste des clients autorisés sur le serveur. En outre, cette adresse et ce message ne doivent pas figurer dans la liste des clients refusés. Si ces informations ne figurent pas dans la liste des clients autorisés, 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 , les messages SC_CALLBACK_REG suivants envoyés par ce client doivent utiliser la même adresse IP source que le premier message. Si le serveur CRNP reçoit un message SC_CALLBACK_REG non conforme à cette spécification, il effectue l'une des opérations suivantes :
Il ignore la requête et envoie un message d'erreur au client.
Il considère que la requête provient d'un nouveau client, selon le contenu du message SC_CALLBACK_REG.
Ce mécanisme de sécurité permet de prévenir les attaques de refus de service, où une personne tente d'annuler l'enregistrement d'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 d'enregistrement et au numéro de port qu'ils ont eux-mêmes utilisés.
Étant donné que les clients du service CRNP sont supposés se trouver à l'intérieur d'un pare-feu qui protège le cluster, le protocole CRNP ne comprend pas de mécanismes de sécurité supplémentaires
L'exemple suivant montre comment développer une application Java simple nommée CrnpClient et qui utilise le protocole CRNP. Cette application enregistre le client, en vue de la réception de rappels d'événements, auprès du serveur CRNP sur le cluster ; elle écoute les rappels, puis traite les événements en imprimant leur contenu. Pour terminer, elle supprime les rappels d'événements.
Gardez ces informations à l'esprit lorsque vous étudiez cet exemple :
Cet exemple d'application génère et analyse du code XML à l'aide de l'API Java pour le traitement XML (JAXP). Il ne vous explique pas comment utiliser JAXP. JAXP est décrit plus en détail à l'adresse http://java.sun.com/xml/jaxp/index.html.
Cet exemple ne présente que des parties de l'application, que vous trouverez dans son intégralité à l'Annexe G, Application CrnpClient.java . Pour illustrer des concepts en particulier de manière plus efficace, l'exemple de ce chapitre est légèrement différent de l'application complète qui est présentée à l'Annexe G, Application CrnpClient.java .
Pour plus de concision, les commentaires ont été supprimés de l'échantillon de code dans ce chapitre. L'application complète disponible à l'Annexe G, Application CrnpClient.java inclut les commentaires.
L'application proposée dans notre exemple gère la plupart des conditions d'erreur en quittant tout simplement l'application. Sachez que, dans la réalité, votre application doit offrir une gestion des erreurs plus robuste.
Téléchargez puis installez JAXP ainsi que la version appropriée du compilateur Java et de la machine virtuelle.
Vous trouverez des instructions à l'adresse http://java.sun.com/xml/jaxp/index.html.
Cet exemple requiert au minimum la version Java 1.3.1.
Placez-vous dans le répertoire qui contient le fichier source et tapez les lignes suivantes :
% 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 . source-filename.java |
où jaxp-root est le chemin d'accès, absolu ou relatif, au répertoire contenant les fichiers jar de JAXP et source-filename est le nom de votre fichier source Java.
Le fait de spécifier un classpath dans votre ligne de commande de compilation garantit que le programme de compilation trouvera les fichiers de classe JAXP.
Lors de l'exécution de l'application, spécifiez le chemin classpath de manière à ce que l'application puisse charger les bons fichiers de classe JAXP (le premier chemin d'accès spécifié dans classpath correspond au répertoire actuel) :
% java -cp .:jaxp-root/dom.jar:jaxp-rootjaxp-api. \ jar:jaxp-rootsax.jar:jaxp-rootxalan.jar:jaxp-root/xercesImpl \ .jar:jaxp-root/xsltc.jar source-filename arguments |
L'environnement est à présent configuré. Vous pouvez développer l'application.
Dans cette partie de l'exemple, vous allez créer une classe de base appelée CrnpClient , avec une méthode principale qui analyse les arguments de 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 déconnecte.
Le constructeur de la classe CrnpClient doit exécuter les tâches suivantes :
définir les objets de traitement XML ;
créer un thread contrôlant les rappels d'événement ;
se connecter au serveur CRNP et s'enregistrer pour être averti des rappels d'événement.
Créez 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 expliquées plus loin dans ce chapitre. Le code qui permet d'importer 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 ultérieurement.
Pour savoir comment analyser les arguments de ligne de commande, reportez-vous au code à l'Annexe G, Application CrnpClient.java .
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.
La configuration XML est abordée plus loin dans ce chapitre.
Dans votre code, définissez une sous-classe Thread appelée EventReceptionThread qui crée une classe ServerSocket et attend que les événements arrivent sur le socket.
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 plus loin dans ce chapitre. La classe EventReceptionThread crée une classe ServerSocket sur une adresse générique de protocole inter-réseau. EventReceptionThread conserve également l'objet CrnpClient en référence afin de pouvoir lui transmettre des événements à traiter.
class EventReceptionThread extends Thread { public EventReceptionThread(CrnpClient clientIn) throws IOException { client = clientIn; listeningSock = new ServerSocket(client.localPort, 50, InetAddress.getLocalHost()); } public void run() { try { DocumentBuilder db = client.dbf.newDocumentBuilder(); db.setErrorHandler(new DefaultHandler()); while(true) { Socket sock = listeningSock.accept(); // Construct event from the sock stream and process it sock.close(); } // UNREACHABLE } catch (Exception e) { System.out.println(e); System.exit(1); } } /* private member variables */ private ServerSocket listeningSock; private CrnpClient client; }
Créez un objet createEvtRecepThr.
private void createEvtRecepThr() throws Exception { evtThr = new EventReceptionThread(this); evtThr.start(); }
L'enregistrement implique les opérations suivantes :
ouverture d'un socket TCP de base sur le port et le protocole inter-réseau de connexion ;
création d'un message de connexion XML ;
envoi d'un message de connexion XML au socket ;
lecture d'un message de réponse XML sur le socket ;
fermeture du socket.
Créez le code Java mettant en oeuvre la logique précédente.
L'exemple de code 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 plus en détail 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(); }
Mettez en œuvre 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 plus loin dans ce chapitre.
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(); }
Maintenant que vous avez défini la structure de l'application et que vous avez rédigé l'intégralité du code de mise en réseau, vous allez rédiger le code qui permet de générer et d'analyser le langage XML. Commencez par rédiger le code qui permet de générer 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 http://java.sun.com/xml/jaxp/index.html décrit le code de cette méthode plus en détail.
La mise en oeuvre de la classe Event est présentée dans l'exemple de code suivant. La classe CallbackReg utilise une classe Event qui mémorise un événement et peut le convertir en Element XML.
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) { // Parser with specified options can't be built pce.printStackTrace(); System.exit(1); } // Create the root element Element root = (Element) document.createElement("SC_CALLBACK_REG"); // Add the attributes root.setAttribute("VERSION", "1.0"); root.setAttribute("PORT", port); root.setAttribute("regType", regType); // Add the events for (int i = 0; i < regEvents.size(); i++) { Event tempEvent = (Event) (regEvents.elementAt(i)); root.appendChild(tempEvent.createXmlElement(document)); } document.appendChild(root); // Convert the whole thing to a string DOMSource domSource = new DOMSource(document); StringWriter strWrite = new StringWriter(); StreamResult streamResult = new StreamResult(strWrite); TransformerFactory tf = TransformerFactory.newInstance(); try { Transformer transformer = tf.newTransformer(); transformer.transform(domSource, streamResult); } catch (TransformerException e) { System.out.println(e.toString()); return (""); } return (strWrite.toString()); } private String port; private String regType; private Vector regEvents; }
Mettez en oeuvre les classes Event et NVPair.
La classe CallbackReg utilise une classe Event, qui elle-même utilise 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; }
Maintenant que vous avez créé les classes d'assistant qui permettent de générer 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, décrite à la section Enregistrement et annulation de l'enregistrement aux rappels.
createRegistrationString crée un objet CallbackReg et définit son type et son port de connexion. createRegistrationString crée ensuite plusieurs é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.
La variable membre regs enregistre les arguments de 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 s'enregistrer. Le quatrième argument spécifie le type d'enregistrement, mais il est ignoré dans cet exemple. L'intégralité du code, que vous trouverez à l'Annexe G, Application CrnpClient.java explique comment utiliser ce quatrième argument.
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); // add the events for (int i = 4; i < regs.length; i++) { if (regs[i].equals("M")) { cbReg.addRegEvent(createMembershipEvent()); } else if (regs[i].equals("A")) { cbReg.addRegEvent(createAllEvent()); } else if (regs[i].substring(0,2).equals("RG")) { cbReg.addRegEvent(createRgEvent(regs[i].substring(3))); } else if (regs[i].substring(0,1).equals("R")) { cbReg.addRegEvent(createREvent(regs[i].substring(2))); } } String xmlStr = cbReg.convertToXml(); return (xmlStr); } private Event createAllEvent() { Event allEvent = new Event(); allEvent.setClass("EC_Cluster"); return (allEvent); } private Event createMembershipEvent() { Event membershipEvent = new Event(); membershipEvent.setClass("EC_Cluster"); membershipEvent.setSubclass("ESC_cluster_membership"); return (membershipEvent); } private Event createRgEvent(String rgname) { Event rgStateEvent = new Event(); rgStateEvent.setClass("EC_Cluster"); rgStateEvent.setSubclass("ESC_cluster_rg_state"); NVPair rgNvpair = new NVPair(); rgNvpair.setName("rg_name"); rgNvpair.setValue(rgname); rgStateEvent.addNvpair(rgNvpair); return (rgStateEvent); } private Event createREvent(String rname) { Event rStateEvent = new Event(); rStateEvent.setClass("EC_Cluster"); rStateEvent.setSubclass("ESC_cluster_r_state"); NVPair rNvpair = new NVPair(); rNvpair.setName("r_name"); rNvpair.setValue(rname); rStateEvent.addNvpair(rNvpair); return (rStateEvent); }
Créez la chaîne de caractères d'annulation de l'enregistrement.
Il est plus facile de créer une chaîne de caractères d'annulation d'enregistrement qu'une chaîne de caractères d'enregistrement, car vous n'avez pas à prendre en compte 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); }
Vous avez créé le code de génération XML et de gestion de réseaux de l'application. Le constructeur CrnpClient appelle une méthode setupXmlProcessing . Celle-ci crée un objet DocumentBuilderFactory et définit plusieurs propriétés d'analyse sur cet objet. Cette méthode est décrite plus en détail dans la documentation JAXP. Voir http://java.sun.com/xml/jaxp/index.html.
Créez le code Java mettant en oeuvre la logique précédente.
private void setupXmlProcessing() throws Exception { dbf = DocumentBuilderFactory.newInstance(); // We don't need to bother validating dbf.setValidating(false); dbf.setExpandEntityReferences(false); // We want to ignore comments and whitespace dbf.setIgnoringComments(true); dbf.setIgnoringElementContentWhitespace(true); // Coalesce CDATA sections into TEXT nodes. dbf.setCoalescing(true); }
Pour pouvoir analyser le message XML SC_REPLY que le serveur CRNP envoie en réponse à un message d'enregistrement ou d'annulation d'un enregistrement, 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 la méthode d'analyse de ce document. Cette méthode est décrite plus en détail dans la documentation JAXP disponible à l'adresse http://java.sun.com/xml/jaxp/index.html.
Créez le code Java mettant en oeuvre la logique précédente.
La méthode readRegistrationReply utilise la nouvelle classe RegReply.
private void readRegistrationReply(InputStream stream) throws Exception { // Create the document builder DocumentBuilder db = dbf.newDocumentBuilder(); db.setErrorHandler(new DefaultHandler()); //parse the input file Document doc = db.parse(stream); RegReply reply = new RegReply(doc); reply.print(System.out); }
Mettez en oeuvre la classe RegReply.
La méthode retrieveValues parcourt l'arborescence DOM dans le document XML et extrait le code et le message d'état. Pour en savoir plus, consultez la documentation JAXP à l'adresse 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; // Find the SC_REPLY element. nl = doc.getElementsByTagName("SC_REPLY"); if (nl.getLength() != 1) { System.out.println("Error in parsing: can't find " + "SC_REPLY node."); return; } n = nl.item(0); // Retrieve the value of the statusCode attribute statusCode = ((Element)n).getAttribute("STATUS_CODE"); // Find the SC_STATUS_MSG element nl = ((Element)n).getElementsByTagName("SC_STATUS_MSG"); if (nl.getLength() != 1) { System.out.println("Error in parsing: can't find " + "SC_STATUS_MSG node."); return; } // Get the TEXT section, if there is one. n = nl.item(0).getFirstChild(); if (n == null || n.getNodeType() != Node.TEXT_NODE) { // Not an error if there isn't one, so we just silently return. return; } // Retrieve the value statusMsg = n.getNodeValue(); } private String statusCode; private String statusMsg; }
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 section Génération du langage XML, afin qu'elle puisse créer un Event à partir d'un document XML et un Element XML. Pour effectuer cette modification, vous aurez besoin d'un constructeur supplémentaire (exécutant un document XML), une méthode retrieveValues, de l'ajout de deux variables membres (vendor et publisher), de méthodes de mécanisme d'accès à tous les champs et d'une méthode d'impression.
Créez le code Java mettant en oeuvre la logique précédente.
Ce code est identique à celui de la classe RegReply décrit à la section Analyse de la réponse d'enregistrement.
public Event(Document doc) { nvpairs = new Vector(); retrieveValues(doc); } public void print(PrintStream out) { out.println("\tCLASS=" + regClass); out.println("\tSUBCLASS=" + regSubclass); out.println("\tVENDOR=" + vendor); out.println("\tPUBLISHER=" + publisher); for (int i = 0; i < nvpairs.size(); i++) { NVPair tempNv = (NVPair) (nvpairs.elementAt(i)); out.print("\t\t"); tempNv.print(out); } } private void retrieveValues(Document doc) { Node n; NodeList nl; String nodeName; // Find the SC_EVENT element. nl = doc.getElementsByTagName("SC_EVENT"); if (nl.getLength() != 1) { System.out.println("Error in parsing: can't find " + "SC_EVENT node."); return; } n = nl.item(0); // // Retrieve the values of the CLASS, SUBCLASS, // VENDOR and PUBLISHER attributes. // regClass = ((Element)n).getAttribute("CLASS"); regSubclass = ((Element)n).getAttribute("SUBCLASS"); publisher = ((Element)n).getAttribute("PUBLISHER"); vendor = ((Element)n).getAttribute("VENDOR"); // Retrieve all the nv pairs for (Node child = n.getFirstChild(); child != null; child = child.getNextSibling()) { nvpairs.add(new NVPair((Element)child)); } } public String getRegClass() { return (regClass); } public String getSubclass() { return (regSubclass); } public String getVendor() { return (vendor); } public String getPublisher() { return (publisher); } public Vector getNvpairs() { return (nvpairs); } private String vendor, publisher;
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 (cf Étape 1) doivent être identiques à celles 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; // Find the NAME element nl = elem.getElementsByTagName("NAME"); if (nl.getLength() != 1) { System.out.println("Error in parsing: can't find " + "NAME node."); return; } // Get the TEXT section n = nl.item(0).getFirstChild(); if (n == null || n.getNodeType() != Node.TEXT_NODE) { System.out.println("Error in parsing: can't find " + "TEXT section."); return; } // Retrieve the value name = n.getNodeValue(); // Now get the value element nl = elem.getElementsByTagName("VALUE"); if (nl.getLength() != 1) { System.out.println("Error in parsing: can't find " + "VALUE node."); return; } // Get the TEXT section n = nl.item(0).getFirstChild(); if (n == null || n.getNodeType() != Node.TEXT_NODE) { System.out.println("Error in parsing: can't find " + "TEXT section."); return; } // Retrieve the value value = n.getNodeValue(); } public String getName() { return (name); } public String getValue() { return (value); } }
Mettez en oeuvre la boucle while dans la classe EventReceptionThread qui attend les rappels d'événements.
La classe EventReceptionThread est présentée à la section 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(); }
Connectez-vous en tant que superutilisateur ou prenez un rôle équivalent.
Exécutez votre application.
# java CrnpClient crnpHost crnpPort localPort ... |
L'intégralité du code de l'application CrnpClient est disponible à l'Annexe G, Application CrnpClient.java .