Sun Cluster: Guía del desarrollador de los servicios de datos del sistema operativo Solaris

Capítulo 12 Protocolo para la notificación de la reconfiguración de los clústers

Este capítulo proporciona información sobre el Protocolo de notificación de reconfiguración del clúster (CRNP, Cluster Reconfiguration Notification Protocol) que permite que las aplicaciones a prueba de fallos y escalables tengan un modo de funcionamiento “en cluster”. En concreto, CRNP proporciona un mecanismo que permite registrar las aplicaciones y recibir las consiguientes notificaciones asíncronas de los eventos de reconfiguración de Sun Cluster. Los servicios de datos que se ejecutan en el clúster y las aplicaciones que se ejecutan fuera de él se pueden registrar para recibir notificación de los eventos. Éstos se generan cuando cambian los miembros de un clúster y cuando cambia el estado de un grupo de recursos o de un recurso.


Nota –

La implementación del tipo de recurso SUNW.Event proporciona servicios CRNP con una alta disponibilidad en Sun Cluster. La implementación de este tipo de recurso se describe de forma más detallada en la página de comando man SUNW.Event(5).


En este capítulo se tratan los temas siguientes:

Conceptos sobre CRNP

CRNP define las capas de aplicación, presentación y sesión de la pila de protocolos de interconexión de sistemas abiertos (OSI, Open System Interconnect) estándar, compuesto por siete capas. La capa de transporte debe ser TCP y la de red debe ser IP. CRNP es independiente de las capas física y de enlace de datos. Todos los mensajes de capa de aplicación intercambiados en CRNP se basan en XML 1.0.

Funcionamiento de CRNP

CRNP proporciona mecanismos y daemons que generan eventos de reconfiguración del clúster, enrutan dichos eventos a través del clúster y los envían a los clientes interesados.

El daemon cl_apid interactúa con los clientes. El Gestor de grupos de recursos (RGM) de Sun Cluster genera eventos de reconfiguración del clúster. Este daemon utiliza syseventd para transmitir los eventos en cada nodo local. El daemon cl_apid utiliza un lenguaje de marcado extensible (XML, Extensible Markup Language) a través de TCP/IP para establecer comunicación con los clientes interesados.

El siguiente diagrama muestra el flujo de eventos entre los componentes de CRNP. En este diagrama, un cliente se está ejecutando en el nodo del clúster 2 y el otro en un ordenador que no forma parte del clúster.

Figura 12–1 Flujo de eventos entre componentes de CRNP

Diagrama de flujo que muestra el funcionamiento de CRNP

Semántica de CRNP

Los clientes establecen comunicación mediante el envío de un mensaje de registro (SC_CALLBACK_RG) al servidor. Este mensaje especifica los tipos de eventos sobre los cuales los clientes desean recibir notificaciones y el puerto al que se pueden enviar los eventos. La IP origen de la conexión de registro y el puerto especificado forman conjuntamente la dirección de retrollamada.

Siempre que se genera un evento que pueda interesar a un cliente en el clúster, el servidor se pone en contacto con el cliente en su dirección de rellamada (IP y puerto) y le envía el evento (SC_EVENT). El servidor tiene una alta disponibilidad y se ejecuta en el propio clúster. El servidor almacena los registros del cliente en un almacenamiento que se conserva incluso después de rearrancar el clúster.

Los clientes anulan sus registros mediante el envío de un mensaje de registro (SC_CALLBACK_RG que contiene un mensaje REMOVE_CLIENT) al servidor. Cuando el cliente recibe un mensaje SC_REPLY del servidor, cierra la conexión.

El siguiente diagrama muestra el flujo de comunicación entre un cliente y un servidor.

Figura 12–2 Flujo de comunicación entre un cliente y un servidor

Diagrama de flujo que muestra el flujo de comunicaciones entre el cliente y el servidor

Tipos de mensajes de CRNP

CRNP utiliza tres tipos de mensajes basados en XML. El uso de estos mensajes se describe en la siguiente tabla. Estos tipos de mensaje y su sintaxis se explican con más detalle

Tipo de mensaje de CRNP 

Descripción 

SC_CALLBACK_REG

Este mensaje adopta cuatro formas: ADD_CLIENT, REMOVE_CLIENT , ADD_EVENTS y REMOVE_EVENTS. Cada una de ellas contiene la información siguiente:

 

  • Versión del protocolo

  • Puerto de rellamada en formato ASCII (no formato binario)

ADD_CLIENT, ADD_EVENTS y REMOVE_EVENTS también contienen una lista ilimitada de tipos de eventos; cada uno de ellos incluye la siguiente información:

 

  • Clase de evento

  • Subclase de evento (opcional)

  • Lista de los pares de nombre y valor (opcional)

La clase y la subclase de evento definen conjuntamente un “tipo de evento” exclusivo. La definicion de tipo de documento (DTD) a partir de la que se generan las clases de SC_CALLBACK_REG es SC_CALLBACK_REG. Esta DTD se describe de forma más detallada en Apéndice F, Definiciones de tipos de documentos de CRNP.

SC_REPLY

Este mensaje contiene la información siguiente: 

  • Versión del protocolo

  • Código de error

  • Mensaje de error

La DTD a partir de la que se generan las clases deSC_REPLY es SC_REPLY. Esta DTD se describe de forma más detallada en Apéndice F, Definiciones de tipos de documentos de CRNP.

SC_EVENT

Este mensaje contiene la información siguiente: 

  • Versión del protocolo

  • Clase de evento

  • Subclase de evento

  • Proveedor

  • Editor

  • Lista de pares de nombres y valores (estructuras de datos de pares de 0 o más nombres y valores)

    • Nombre (cadena)

    • Valor (cadena o matriz de cadenas)

Los valores en SC_EVENT no tienen un tipo. La DTD a partir de la que se generan las clases deSC_EVENT es SC_EVENT. Esta DTD se describe de forma más detallada en Apéndice F, Definiciones de tipos de documentos de CRNP.

Cómo se registra un cliente en un servidor

Esta sección describe cómo puede un administrador del clúster configurar el servidor, cómo se identifican los clientes, de qué forma se envía la información a través de las capas de aplicación y sesión, y las condiciones de error.

Nociones sobre la configuración del servidor por parte de los administradores

El administrador del clúster debe configurar el servidor con una dirección IP (que no esté vinculada a una máquina específica en un clúster) y un número de puerto de alta disponibilidad. Además, debe dar a conocer esta dirección de red a los posibles clientes. CRNP no define cómo se pone este nombre de servidor a disposición de los clientes. El administrador del clúster puede utilizar un servicio de nombres, que permita que los clientes busquen de forma dinámica la dirección del servidor de red, o agregar el nombre de red a un archivo de configuración para que puedan leerlo los clientes. El servidor se ejecuta en un clúster con el tipo de recurso de recuperación ante fallos.

Cómo identifica el servidor a un cliente

Cada cliente se identifica de forma exclusiva mediante su dirección de rellamada, su dirección IP y el número de puerto. El puerto se especifica en los mensajes SC_CALLBACK_REG y la dirección IP se obtiene de la conexión de registro de TCP. CRNP presupone que los subsiguientes mensajes SC_CALLBACK_REG con la misma dirección de rellamada provienen del mismo cliente, aunque el puerto de origen desde el que se enviaron sea diferente.

Cómo se pasan los mensajes SC_CALLBACK_REG entre el cliente y el servidor

El cliente inicia el registro mediante la apertura de una conexión TCP con la dirección IP y el número de puerto del servidor. Una vez establecida la conexión TCP y lista para escribir, el cliente debe enviar su mensaje de registro. Éste debe ser un mensaje SC_CALLBACK_REG de formato correcto que no contenga bytes adicionales antes ni después del mensaje.

Una vez escritos todos los bytes en el canal, el cliente debe mantener la conexión abierta para recibir la respuesta del servidor. Si el cliente no le da el formato correcto al mensaje, el servidor no registrará el cliente y le enviará una respuesta de error. No obstante, si el cliente cierra la conexión de socket antes de que el servidor envíe una respuesta, éste registra el cliente como normal.

Un cliente puede ponerse en contacto con el servidor en cualquier momento. Cada vez que un cliente se pone en contacto con el servidor, el cliente debe enviar un mensaje SC_CALLBACK_REG. Si el servidor recibe un mensaje con formato erróneo, fuera de servicio o no válido, envía una respuesta de error al cliente.

Un cliente no puede enviar mensajes ADD_EVENTS, REMOVE_EVENTS , or REMOVE_CLIENT antes de enviar un mensaje ADD_CLIENT. Un cliente no puede enviar un mensaje REMOVE_CLIENT antes de enviar un mensaje ADD_CLIENT.

Si un cliente envía un mensaje ADD_CLIENT y el cliente ya está registrado, es posible que el servidor tolere el mensaje. En este caso, el servidor sustituye silenciosamente el registro anterior del cliente por el nuevo, especificado en el segundo mensaje ADD_CLIENT.

En la mayoría de los casos, un cliente se registra sólo una vez en el servidor, al comenzar, con un mensaje ADD_CLIENT. Para anular el registo de un cliente, se envía un mensaje REMOVE_CLIENT al servidor. Sin embargo, CRNP proporciona una mayor flexibilidad a aquellos clientes que tengan que modificar dinámicamente la lista de tipos de eventos.

Contenido de un mensaje SC_CALLBACK_REG

Cada mensaje ADD_CLIENT, REMOVE_CLIENT, ADD_EVENTS y REMOVE_EVENTS contiene una lista de eventos. La tabla siguiente describe los tipos de eventos que acepta CRNP, incluidos los valores requeridos de nombre y valor.

Si un cliente realiza una de las siguientes acciones, el servidor ignorará de forma silenciosa los siguientes mensajes:

Clase y subclase 

Pares nombre-valor 

Descripción 

EC_Cluster

ESC_cluster_membership

Siempre: ninguno 

Opcional: ninguno 

Registra todos los eventos de cambio en los miembros del clúster (terminación de nodo o unión) 

EC_Cluster

ESC_cluster_rg_state

Uno necesario, como sigue: 

rg_name

Tipo de valor: cadena 

Opcional: ninguno 

Registra todos los eventos de cambio de estado del grupo de recursos name

EC_Cluster

ESC_cluster_r_state

Uno necesario, como sigue: 

r_name

Tipo de valor: cadena 

Opcional: ninguno 

Registra todos los eventos de cambio de estado del recurso nombre

EC_Cluster

Ninguna 

Siempre: ninguno 

Opcional: ninguno 

Registra todos los eventos de Sun Cluster 

Cómo responde el servidor a un cliente

Una vez procesado el registro, el servidor que ha recibido la solicitud de registro envía un mensaje SC_REPLY mediante la conexión TCP que el cliente ha abierto. El servidor cierra la conexión. El cliente debe mantener abierta la conexión TCP hasta que reciba el mensaje SC_REPLY del servidor.

Por ejemplo, el cliente realiza las siguientes acciones:

  1. Abre una conexión TCP al servidor

  2. Espera que la conexión se pueda “escribir”

  3. Envía un mensaje SC_CALLBACK_REG (que contiene un mensaje ADD_CLIENT)

  4. Espera la recepción de un mensaje SC_REPLY desde el servidor

  5. Recibe un mensaje SC_REPLY desde el servidor

  6. Recibe un indicador de que el servidor ha cerrado la conexión (lee 0 bytes del socket)

  7. Cierra la conexión

Más tarde, el cliente lleva a cabo las siguientes acciones:

  1. Abre una conexión TCP al servidor

  2. Espera que la conexión se pueda “escribir”

  3. Envía un mensaje SC_CALLBACK_REG (que contiene un mensaje REMOVE_CLIENT)

  4. Espera la recepción de un mensaje SC_REPLY desde el servidor

  5. Recibe un mensaje SC_REPLY desde el servidor

  6. Recibe un indicador de que el servidor ha cerrado la conexión (lee 0 bytes del socket)

  7. Cierra la conexión

Cada vez que el servidor recibe un mensaje SC_CALLBACK_REG de un cliente, le envía un mensaje SC_REPLY en la misma conexión abierta. Este mensaje especifica si la operación se ha realizado de modo satisfactorio o no. DTD de XML de SC_REPLY contiene la definición de tipo de documento XML de un mensaje SC_REPLY y los posibles mensajes de error incluidos en el mismo.

Contenido de un mensaje SC_REPLY

Un mensaje SC_REPLY indica si una operación se ha realizado con éxito o si, por el contrario, ha presentado errores. Este mensaje contiene la versión del mensaje de CRNP, un código de estado y un mensaje de estado que describe de forma más detallada el código de estado. La tabla siguiente describe los valores posibles del código de estado.

Código de estado 

Descripción 

OK

El mensaje se ha procesado satisfactoriamente. 

RETRY

El servidor ha rechazado el registro del cliente debido a un error transitorio. El cliente debería registrarse de nuevo con argumentos diferentes. 

LOW_RESOURCE

Hay un nivel bajo de recursos del clúster, por lo que el cliente debe intentarlo de nuevo más adelante. El administrador del clúster puede aumentar también los recursos del mismo.  

SYSTEM_ERROR

Se ha producido un error grave. Póngase en contacto con el administrador del clúster.  

FAIL

La autorización ha fallado o se ha producido otro problema que ha provocado el fallo de registro. 

MALFORMED

La solicitud XML tenía un formato erróneo y no se ha podido analizar. 

INVALID

La solicitud XML no era válida, es decir, no cumplía con la especificación XML.  

VERSION_TOO_HIGH

La versión del mensaje era demasiado elevada para procesar satisfactoriamente el mensaje.  

VERSION_TOO_LOW

La versión del mensaje era demasiado baja para procesarlo satisfactoriamente.  

Cómo debe resolver el cliente las condiciones de error

En condiciones normales, el cliente que envía un mensaje SC_CALLBACK_REG recibe una respuesta en la que se indica si el registro se ha realizado con éxito o no.

Sin embargo, el servidor puede experimentar una condición de red durante el registro de un cliente que le impida enviar un mensaje SC_REPLY a éste. En este caso, el registro se podría haber realizado satisfactoriamente antes de que se produjera la condición de error, podría haber fallado o podría no haber sido procesado.

Como el servidor debe funcionar como tipo de recurso de recuperación antes fallos o como servidor del clúster, con alta disponibilidad, esta condición de error no implica la finalización del servicio. De hecho, es posible que el servidor pueda empezar muy pronto a enviar eventos al cliente recién registrado.

Para solucionar estas condiciones, el cliente debe realizar las siguientes acciones:

Cómo envía eventos el servidor al cliente

A medida que los eventos se generan en el clúster, el servidor de CRNP los envía a cada cliente que ha solicitado estos tipos de eventos. El envío consiste en dirigir un mensaje SC_EVENT a la dirección de rellamada del cliente. El envío de cada evento se produce en una conexión TCP nueva.

Inmediatamente después de que un cliente se registre para un tipo de evento, a través de un mensaje SC_CALLBACK_REG que contenga un mensaje ADD_CLIENT o ADD_EVENT, el servidor enviará el evento más reciente de ese tipo al cliente. El cliente puede determinar el estado actual del sistema desde el que provienen los eventos siguientes.

Cuando el servidor inicie una conexión TCP con el cliente, enviará exactamente un mensaje SC_EVENT por la conexión. El servidor emite un proceso de cierre de dúplex completo.

Por ejemplo, el cliente realiza las siguientes acciones:

  1. Espera que el servidor inicie una conexión TCP

  2. Acepta la conexión entrante del servidor

  3. Espera la recepción de un mensaje SC_EVENT desde el servidor

  4. Lee un mensaje SC_EVENT del servidor

  5. Recibe un indicador de que el servidor ha cerrado la conexión (lee 0 bytes del socket)

  6. Cierra la conexión

Cuando todos los clientes estén registrados, deberán recibir en todas sus direcciones de rellamada (dirección IP y número de puerto) en todo momento, por si hubiera una conexión entrante para el envío de un evento.

Si el servidor no logra contactar con el cliente para enviarle un evento, vuelve a intentar enviarlo un cierto número de veces, en el intervalo que se defina. Si los intentos fallan, el cliente es eliminado de la lista de clientes del servidor. El cliente debe también volver a registrarse mediante el envío de un mensaje SC_CALLBACK_REG que contenga a su vez un mensaje ADD_CLIENT antes de poder recibir más eventos.

Cómo se garantiza el envío de eventos

La generación de eventos se realiza de forma completamente ordenada en el clúster; este orden se mantiene a la hora de realizar el envío a cada cliente. En otras palabras, si el evento A se genera en el clúster antes que el evento B, el cliente recibe el evento A antes de recibir el evento B. Sin embargo, no se mantiene el orden completo de envío de eventos a todos los clientes. Es decir, el cliente Y podría recibir los eventos A y B antes de que el cliente X recibiera el evento A. Así, los clientes más lentos no retrasan el envío a los demás clientes.

Todos los eventos que envía el servidor (salvo el primero de una subclase y los eventos que siguen a los errores de servidor) se producen como respuesta a eventos reales generados por el clúster, salvo que el servidor sufra un error que haga que pierda eventos generados por el clúster. En ese caso, el servidor genera un evento para cada tipo de evento que representa el estado actual del sistema para ese tipo. Cada evento se envía a los clientes que registraron su interés en ese tipo de evento.

El envío de eventos sigue la semántica de “una vez como mínimo”. Es decir, el servidor puede enviar el mismo evento a un cliente en más de una ocasión. Esta concesión es necesaria en aquellos casos en los que el servidor permanece inactivo temporalmente y, al reanudar su actividad, no puede determinar si el cliente ha recibido o no la información más reciente.

Contenido de un mensaje SC_EVENT

El mensaje SC_EVENT contiene el mensaje real generado en el clúster y convertido para adaptarlo al formato de mensaje de XML SC_EVENT. La tabla siguiente describe los tipos de evento que envía CRNP, incluidos los pares nombre-valor, editor y fabricante.


Nota –

Las posiciones de los elementos de la matriz de state_list están sincronizadas con las de node_list. Es decir, el estado del nodo que aparece en primer lugar en la matriz de node_list aparece también en la posición inicial en la matriz de state_list.

Otros nombres que empiezan por ev_ y sus valores asociados pueden estar también presentes, pero no son para el uso del cliente.


Clase y subclase 

Editor y fabricante 

Pares nombre-valor 

EC_Cluster

ESC_cluster_membership

Editor: rgm 

Proveedor: SUNW 

Nombre: node_list

Tipo de valor: matriz de cadenas 

Nombre: state_list

state_list contiene únicamente números representados con el formato ASCII. cada uno de los cuales representa el número que encarna actualmente ese nodo en el clúster. Si el número es el mismo que el recibido en un mensaje anterior, el nodo no ha modificado su relación con el clúster (se ha ido, se ha unido o se ha vuelto a unir). Si el número de representación es –1, el nodo no es miembro del clúster. Si el número de representación es un número positivo, el nodo es miembro del clúster.

Tipo de valor: matriz de cadenas 

EC_Cluster

ESC_cluster_rg_state

Editor: rgm 

Proveedor: SUNW 

Nombre: rg_name

Tipo de valor: cadena 

Nombre: node_list

Tipo de valor: matriz de cadenas 

Nombre: state_list

state_list contiene representaciones de cadenas del estado del grupo de recursos. Entre los valores válidos, se encuentran aquéllos que pueden recuperarse con los comandos scha_cmds(1HA).

Tipo de valor: matriz de cadenas 

EC_Cluster

ESC_cluster_r_state

Editor: rgm 

Proveedor: SUNW 

Nombre: r_name

Tipo de valor: cadena 

Nombre: node_list

Tipo de valor: matriz de cadenas 

Nombre: state_list

state_list contiene representaciones de cadenas del estado del recurso. Entre los valores válidos, se encuentran aquéllos que pueden recuperarse con los comandos scha_cmds(1HA).

Tipo de valor: matriz de cadenas 

Cómo autentica CRNP los clientes y el servidor

El servidor autentica los clientes mediante el uso de empaquetadores TCP. La dirección IP de origen del mensaje de registro, que se utiliza también como dirección IP de rellamada con la que se envían los eventos, debe incluirse en la lista de clientes permitidos del servidor. La dirección IP de origen y el mensaje de registro no pueden estar en la lista de clientes rechazados. Si la dirección IP de origen y el registro no están en la lista, el servidor rechaza la solicitud y emite una respuesta de error para el cliente.

Cuando el servidor recibe un mensaje SC_CALLBACK_REG ADD_CLIENT , los siguientes mensajes SC_CALLBACK_REG de ese cliente deben contener una dirección IP de origen igual a la de la primer mensaje. Si el servidor de CRNP recibe un mensaje SC_CALLBACK_REG que no cumple este requisito, el servidor realiza una de las siguientes acciones:

Este mecanismo de seguridad ayuda a evitar ataques de denegación de servicio, en los que alguien intenta anular el registro de un cliente legítimo.

Los clientes deberían autenticar del mismo modo al servidor y Los clientes sólo deben aceptar los envíos de un servidor cuya dirección IP de origen y número de puerto coincidan con la dirección IP y número de puerto de registro empleados por el cliente.

Como se supone que los clientes del servicio CRNP deben ubicarse en un servidor de seguridad (firewall) que proteja el clúster, CRNP no incluye ningún mecanismo de seguridad adicoonal.

Ejemplo de creación de una aplicación de Java que utiliza CRNP

El siguiente ejemplo muestra cómo desarrollar una aplicación de Java denominada CrnpClient que utiliza CRNP. La aplicación registra rellamadas de eventos con el servidor de CRNP del clúster, escucha rellamadas de eventos y procesa dichos eventos mediante la impresión de su contenido. Antes de finalizar, la aplicación anula el registro de su solicitud de rellamadas de eventos.

Tenga en cuenta los siguientes puntos al examinar este ejemplo:

ProcedureCómo configurar el entorno

Pasos
  1. Descargue e instale JAXP y la versión correcta del compilador de Java y la máquina virtual Java.

    Puede encontrar instrucciones en http://java.sun.com/xml/jaxp/index.html.


    Nota –

    Este ejemplo requiere al menos Java 1.3.1.


  2. En el directorio en el que está ubicado el archivo de origen, escriba lo siguiente:


    % 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
    

    donde jaxp-root es la ruta absoluta o relativa al directorio en el que residen los archivos jar de JAXP y source-filename es el nombre del archivo de Java de origen.

    Si se incluye classpath en la línea de comandos de compilación, se asegurará de que el compilador pueda encontrar las clases de JAXP.

  3. Al ejecutar la aplicación, especifique la ruta de clase en classpath para que la aplicación pueda ejecutar los archivos de clases de JAXP correctos (tenga en cuenta que la primera ruta de classpath señala al directorio actual):


    % 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
    

    Ahora que se ha configurado el entorno se puede desarrollar la aplicación.

ProcedureCómo comenzar a desarrollar la aplicación

En esta parte del ejemplo, debe crear una clase básica denominada CrnpClient con un método principal que analice los argumentos de la línea de comandos y construya un objeto CrnpClient. Este objeto pasa los argumentos de la línea de comandos a la clase, espera a que el usuario termine la aplicación, llama a shutdown en la clase CrnpClient y sale.

El constructor de la clase CrnpClient debe ejecutar las siguientes tareas:

Paso

    Crear el código Java que aplica la lógica anterior.

    El ejemplo siguiente muestra el código de la estructura básica de la clase CrnpClient. Las implementaciones de los cuatros métodos del auxiliar a los que se hace referencia en los métodos de cierre y el constructor se muestran más adelante en este capítulo Tenga en cuenta que se muestra el código que importa todos los paquetes necesarios.

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

    Las variables del miembro se describen más adelante en este capítulo.

ProcedureCómo analizar los argumentos de la línea de comandos

Paso

    Para analizar los argumentos de la línea de comandos, consulte el código incluido en Apéndice G, Aplicación CrnpClient.java .

ProcedureCómo definir el subproceso de recepción de eventos

En el código, hay que comprobar que la recepción de eventos se realice en un subproceso aparte de forma que la aplicación pueda seguir realizando el otro trabajo, mientras el subproceso de evento se bloquea y espera rellamadas de eventos.


Nota –

La configuración de XML se describe más adelante en este capítulo.


Pasos
  1. En el código, defina la subclase Thread denominada EventReceptionThread que crea un socket, ServerSocket , y espera la recepción de los eventos en el socket.

    En esta parte del código de ejemplo, los eventos no se leen ni se procesan. La lectura y el procesamiento de eventos se describen más adelante en este capítulo. EventReceptionThread crea un socket, ServerSocket, en una dirección de protocolo de inteconexión con comodines. EventReceptionThread también mantiene una referencia al objeto CrnpClient para que EventReceptionThread pueda enviar eventos al objeto CrnpClient para su procesamiento.

    class EventReceptionThread extends Thread
    {
            public EventReceptionThread(CrnpClient clientIn) throws IOException
            {
                    client = clientIn;
                    listeningSock = new ServerSocket(client.localPort, 50,
                        InetAddress.getLocalHost());
            }
    
            public void run()
            {
                    try {
                            DocumentBuilder db = client.dbf.newDocumentBuilder();
                            db.setErrorHandler(new DefaultHandler());
    
                            while(true) {
                                    Socket sock = listeningSock.accept();
                                    // Construct event from the sock stream and process it
                                    sock.close();
                            }
                            // UNREACHABLE
    
                    } catch (Exception e) {
                            System.out.println(e);
                            System.exit(1);
                    }
            }
    
            /* private member variables */
            private ServerSocket listeningSock;
            private CrnpClient client;
    }
  2. Construya un objeto createEvtRecepThr.

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

ProcedureCómo registrar rellamadas o anular el registro

El proceso de registro conlleva las siguientes acciones:

Pasos
  1. Crear el código Java que aplica la lógica anterior.

    El siguiente código de ejemplo muestra la implementación del método registerCallbacks de la clase CrnpClient (llamada por el constructor CrnpClient). Las llamadas a createRegistrationString() y readRegistrationReply () se describen de forma más detallada posteriormente en este capítulo.

    regIp y regPort son objetos miembros configurados por el constructor.

    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. Aplique el método unregister.

    El método shutdown de CrnpClient llama a este método. La implementación de createUnregistrationString se describe de forma más detallada posteriormente en este capítulo.

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

ProcedureCómo generar XML

Ahora que se ha configurado la estructura de la aplicación y se ha escrito todo el código de red, debe escribirse el código que genera y analiza XML. En primer lugar, escriba el código que genera el mensaje de registro XML SC_CALLBACK_REG.

Un mensaje SC_CALLBACK_REG está formado por un tipo de registro (ADD_CLIENT, REMOVE_CLIENT, ADD_EVENTS , or REMOVE_EVENTS), un puerto de rellamada y una lista de eventos de interés. Cada evento consta de una clase y una subclase, seguidos de una lista de pares de nombres y valores.

En esta parte del ejemplo, se escribe una clase CallbackReg que guarda el tipo de registro, el puerto de rellamada y la lista de eventos de registro. Esta clase también puede serializarse para un mensaje de XML SC_CALLBACK_REG.

Un método interesante de esta clase es el convertToXml, que crea una cadena de mensaje de XML SC_CALLBACK_REG a partir de los miembros de la clase. La documentación de JAXP ubicada en http://java.sun.com/xml/jaxp/index.html describe de forma más detallada el código de este método.

La implementación de la clase Event se muestra en el siguiente código de ejemplo. Tenga en cuenta que la clase CallbackReg utiliza una clase Event, que permite almacenar un evento y convertirlo en un elemento, Element, XML.

Pasos
  1. Crear el código Java que aplica la lógica anterior.

    class CallbackReg
    {
            public static final int ADD_CLIENT = 0;
            public static final int ADD_EVENTS = 1;
            public static final int REMOVE_EVENTS = 2;
            public static final int REMOVE_CLIENT = 3;
    
            public CallbackReg()
            {
                    port = null;
                    regType = null;
                    regEvents = new Vector();
            }
    
            public void setPort(String portIn)
            {
                    port = portIn;
            }
    
            public void setRegType(int regTypeIn)
            {
                    switch (regTypeIn) {
                    case ADD_CLIENT:
                            regType = "ADD_CLIENT";
                            break;
                    case ADD_EVENTS:
                            regType = "ADD_EVENTS";
                            break;
                    case REMOVE_CLIENT:
                            regType = "REMOVE_CLIENT";
                            break;
                    case REMOVE_EVENTS:
                            regType = "REMOVE_EVENTS";
                            break;
                    default:
                            System.out.println("Error, invalid regType " +
                                regTypeIn);
                            regType = "ADD_CLIENT";
                            break;
                    }
            }
    
            public void addRegEvent(Event regEvent)
            {
                    regEvents.add(regEvent);
            } 
    
            public String convertToXml()
            {
                    Document document = null;
                    DocumentBuilderFactory factory =
                        DocumentBuilderFactory.newInstance();
                    try {
                            DocumentBuilder builder = factory.newDocumentBuilder();
                            document = builder.newDocument(); 
                    } catch (ParserConfigurationException pce) {
                            // Parser with specified options can't be built
                            pce.printStackTrace();
                            System.exit(1);
                    }
    
                    // Create the root element
                    Element root = (Element) document.createElement("SC_CALLBACK_REG");
    
                    // Add the attributes
                    root.setAttribute("VERSION", "1.0");
                    root.setAttribute("PORT", port);
                    root.setAttribute("regType", regType);
    
                    // Add the events
                    for (int i = 0; i < regEvents.size(); i++) {
                            Event tempEvent = (Event)
                                (regEvents.elementAt(i));
                            root.appendChild(tempEvent.createXmlElement(document));
                    }
                    document.appendChild(root);
    
                    // Convert the whole thing to a string
                    DOMSource domSource = new DOMSource(document);
                    StringWriter strWrite = new StringWriter();
                    StreamResult streamResult = new StreamResult(strWrite);
                    TransformerFactory tf = TransformerFactory.newInstance();
                    try {
                            Transformer transformer = tf.newTransformer();
                            transformer.transform(domSource, streamResult);
                    } catch (TransformerException e) {
                            System.out.println(e.toString());
                            return ("");
                    }
                    return (strWrite.toString());
            }
    
            private String port;
            private String regType;
            private Vector regEvents;
    }
  2. Implemente las clases Event y NVPair.

    Tenga en cuenta que la clase CallbackReg utiliza Event, que a su vez utiliza una clase 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;
    }

ProcedureCómo crear los mensajes de registro y de anulación del registro

Una vez creadas las clases del auxiliar que generan mensajes de XML, puede escribir la implementación del método createRegistrationString. registerCallbacks llama a este método; este proceso se describe en Cómo registrar rellamadas o anular el registro.

createRegistrationString construye un objeto CallbackReg y establece su puerto y tipo de registro. A continuación, createRegistrationString construye diversos eventos mediante los métodos del auxiliar createAllEvent, createMembershipEvent, createRgEvent y createREvent . Cada evento se agrega al objeto CallbackReg después de crearlo. Finalmente, createRegistrationString invoca el método convertToXml en el objeto CallbackReg para recuperar el mensaje de XML en la forma de String.

Tenga en cuenta que la variable del miembro regs almacena los argumentos de la línea de comandos proporcionados por el usuario para la aplicación. El quinto argumento y siguientes especifican los eventos para los cuales debería registrarse la aplicación. El cuarto argumento especifica el tipo de registro, pero se ignora en este ejemplo. El código completo incluido en Apéndice G, Aplicación CrnpClient.java muestra cómo utilizar este cuarto argumento.

Pasos
  1. Crear el código Java que aplica la lógica anterior.

    private String createRegistrationString() throws Exception
    {
            CallbackReg cbReg = new CallbackReg();
            cbReg.setPort("" + localPort);
    
            cbReg.setRegType(CallbackReg.ADD_CLIENT);
    
            // add the events
            for (int i = 4; i < regs.length; i++) {
                    if (regs[i].equals("M")) {
                            cbReg.addRegEvent(createMembershipEvent());
                    } else if (regs[i].equals("A")) {
                            cbReg.addRegEvent(createAllEvent());
                    } else if (regs[i].substring(0,2).equals("RG")) {
                            cbReg.addRegEvent(createRgEvent(regs[i].substring(3)));
                    } else if (regs[i].substring(0,1).equals("R")) {
                            cbReg.addRegEvent(createREvent(regs[i].substring(2))); 
                    }
            }
    
            String xmlStr = cbReg.convertToXml();
            return (xmlStr);
    }
    
    private Event createAllEvent()
    {
            Event allEvent = new Event();
            allEvent.setClass("EC_Cluster");
            return (allEvent);
    }
    
    private Event createMembershipEvent()
    {
            Event membershipEvent = new Event();
            membershipEvent.setClass("EC_Cluster");
            membershipEvent.setSubclass("ESC_cluster_membership");
            return (membershipEvent);
    }
    
    private Event createRgEvent(String rgname)
    {
            Event rgStateEvent = new Event();
            rgStateEvent.setClass("EC_Cluster");
            rgStateEvent.setSubclass("ESC_cluster_rg_state");
    
            NVPair rgNvpair = new NVPair();
            rgNvpair.setName("rg_name");
            rgNvpair.setValue(rgname);
            rgStateEvent.addNvpair(rgNvpair);
    
            return (rgStateEvent);
    }
    
    private Event createREvent(String rname)
    {
            Event rStateEvent = new Event();
            rStateEvent.setClass("EC_Cluster");
            rStateEvent.setSubclass("ESC_cluster_r_state");
    
            NVPair rNvpair = new NVPair();
            rNvpair.setName("r_name");
            rNvpair.setValue(rname);
            rStateEvent.addNvpair(rNvpair);
    
            return (rStateEvent);
    }
  2. Crear la cadena de anulación de registro.

    La creación la cadena de anulación de registro es un proceso más sencillo que la creación de la cadena de registro, ya que no es necesario adaptar eventos.

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

ProcedureCómo configurar el analizador XML

Una vez creado el código de generación de XML y de red para la aplicación, El constructor CrnpClient llama al método setupXmlProcessing , que crea un objeto DocumentBuilderFactory y establece varias propiedades de análisis en ese objeto. La documentación de JAXP describe de forma más detallada este método. Consulte http://java.sun.com/xml/jaxp/index.html.

Paso

    Crear el código Java que aplica la lógica anterior.

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

ProcedureCómo analizar el mensaje de respuesta de registro

Para analizar el mensaje de XML SC_REPLY que CRNP envía en respuesta a un mensaje de registro o de anulación del registro, es necesario disponer de una clase auxiliar RegReply , que se puede construir a partir de un documento de XML. Esta clase proporciona accesores para el código de estado y el mensaje de estado. Para analizar la secuencia de XML del servidor, es necesario crear un nuevo documento de XML y utilizar su método de análisis. La documentación de JAXP ubicada en http://java.sun.com/xml/jaxp/index.html describe de forma más detallada este método.

Pasos
  1. Crear el código Java que aplica la lógica anterior.

    Observe que el método readRegistrationReply utiliza la nueva clase 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);
    }
  2. Implementar la clase RegReply.

    Observe que el método retrieveValues recorre el árbol DOM del documento XML y extrae el código y el mensaje de estado. La documentación de JAXP ubicada en http://java.sun.com/xml/jaxp/index.html contiene más información.

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

ProcedureCómo analizar los eventos de rellamada

El paso final consiste en analizar y procesar los eventos reales de rellamada. Para ayudarle en esta tarea, debe modificar la clase Event creada en Cómo generar XML para que esta clase pueda construir un evento, Event, a partir de un documento de XML y crear un elemento, Element, XML: Este cambio requiere un constructor adicional (que incluya un documento de XML), un método retrieveValues, la adición de dos variables de miembros (vendor y publisher), métodos de acceso para todos los campos y, por último, un método de impresión.

Pasos
  1. Crear el código Java que aplica la lógica anterior.

    Tenga en cuenta que este código es similar al de la clase RegReply , que se describe en Cómo analizar el mensaje de respuesta de registro.

    public Event(Document doc)
            {
                    nvpairs = new Vector();
                    retrieveValues(doc);
            }
            public void print(PrintStream out)
            {
                    out.println("\tCLASS=" + regClass);
                    out.println("\tSUBCLASS=" + regSubclass);
                    out.println("\tVENDOR=" + vendor);
                    out.println("\tPUBLISHER=" + publisher);
                    for (int i = 0; i < nvpairs.size(); i++) {
                            NVPair tempNv = (NVPair)
                                (nvpairs.elementAt(i));
                            out.print("\t\t");
                            tempNv.print(out);
                    }
            }
    
            private void retrieveValues(Document doc)
            {
                    Node n;
                    NodeList nl;
                    String nodeName;
    
                    // Find the SC_EVENT element.
                    nl = doc.getElementsByTagName("SC_EVENT");
                    if (nl.getLength() != 1) {
                       System.out.println("Error in parsing: can't find "
                           + "SC_EVENT node.");
                       return;
                    }
    
                    n = nl.item(0);
    
                    //
                    // Retrieve the values of the CLASS, SUBCLASS,
                    // VENDOR and PUBLISHER attributes.
                    //
                    regClass = ((Element)n).getAttribute("CLASS");
                    regSubclass = ((Element)n).getAttribute("SUBCLASS");
                    publisher = ((Element)n).getAttribute("PUBLISHER");
                    vendor = ((Element)n).getAttribute("VENDOR");
    
                    // Retrieve all the nv pairs
                    for (Node child = n.getFirstChild(); child != null;
                        child = child.getNextSibling())
                    {
                           nvpairs.add(new NVPair((Element)child));
                    }
            }
    
            public String getRegClass()
            {
                    return (regClass);
            }
    
            public String getSubclass()
            {
                    return (regSubclass);
            }
    
            public String getVendor()
            {
                    return (vendor);
            }
    
            public String getPublisher()
            {
                    return (publisher);
            }
    
            public Vector getNvpairs()
            {
                    return (nvpairs);
            }
    
            private String vendor, publisher;
  2. Implemente los constructores y métodos adicionales de la clase NVPair compatibles con el análisis XML.

    Los cambios realizados en Event mostrados en el Paso 1 deben también realizarse en la clase 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);
            }
    }
  3. Implemente el bucle while en EventReceptionThread, que espera las rellamadas de eventos.

    EventReceptionThread se describe en Cómo definir el subproceso de recepción de eventos.

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

ProcedureCómo ejecutar la aplicación

Pasos
  1. Conviértase en superusuario o asuma una función similar.

  2. Ejecutar la aplicación.


    # java CrnpClient crnpHost crnpPort localPort ...
    

    El código completo de la aplicación CrnpClient se muestra en Apéndice G, Aplicación CrnpClient.java .