El següent exemple mostra com desenvolupar una aplicació de Java anomenada CrnpClient que utilitza CRNP. L’aplicació registra tornar a marcar de esdeveniments amb el servidor de CRNP de l’clúster, escolta tornar a marcar de esdeveniments i processa aquests esdeveniments mitjançant la impressió del seu contingut. Abans de finalitzar, l’aplicació anul·la el registre de la seva sol·licitud de tornar a marcar d’esdeveniments.
Tingueu en compte els següents punts a l’examinar aquest exemple:
-
L’aplicació d’exemple genera i analitza XML amb JAXP (Java API for XML Processing). Aquest exemple no mostra com utilitzar JAXP. Aquesta eina es descriu més detalladament en http://java.sun.com/xml/jaxp/index.html.
-
Aquest exemple presenta parts d’una aplicació, que es descriu en la seva totalitat en apèndix G, Aplicació CrnpClient.java. Per explicar de forma més eficaç determinats conceptes, l’exemple d’aquest capítol varia lleugerament de l’aplicació completa presentada en apèndix G, Aplicació CrnpClient.java.
-
Per a ser més concisos, no s’han inclòs els delimitadors de el codi d’exemple en aquest capítol. Per veure’ls, consulteu l’aplicació completa en apèndix G, Aplicació CrnpClient.java.
-
L’aplicació que apareix en aquest exemple gestiona la majoria de les condicions d’error simplement tancant l’aplicació. L’aplicació real hauria de tractar els errors amb més fermesa.
Com configurar l’entorn
Passos
-
Descarregueu i instal JAXP i la versió correcta de l’compilador de Java i la màquina virtual Java.
Podeu trobar instruccions a http://java.sun.com/xml/jaxp/index.html.
Nota –
Aquest exemple requereix al menys Java 1.3.1.
-
al directori en el qual està ubicat el fitxer d’origen, escriviu el següent:
a
% 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
a
on JAXP-root és la ruta absoluta o relativa a directori en el qual resideixen els arxius jar de JAXP i source-filename és el nom de l’arxiu de Java d’origen.
Si s’inclou classpath en la línia d’ordres de compilació, s’ha d’assegurar que el compilador pugui trobar les classes de JAXP.
-
a l’ executar l’aplicació, especifiqueu la ruta de classe en classpath perquè l’aplicació pugui executar els arxius de classes de JAXP correctes (tingui en cuent al fet que la primera ruta de classpath assenyala a directori actual):
a
% 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
a
Ara que s’ha configurat l’entorn es pot desenvolupar l’aplicació .
Com començar a desenvolupar l’aplicació
En aquesta part de l’exemple, ha de crear una classe bàsica anomenada CrnpClient amb un mètode principal que analitzi els arguments de la línia d’ordres i construeixi un objecte CrnpClient. Aquest objecte passa els arguments de la línia d’ordres a la classe, espera que l’usuari acabi l’aplicació, crida a shutdown en la classe CrnpClient i surt.
El constructor de la classe CrnpClient ha d’executar les següents tasques:
-
Configura els objectes de procés de XML.
-
Crear un subprocés que rebi tornar a marcar de esdeveniments.
-
Contactar amb el servidor CRNP i registrar tornar a marcar de esdeveniments.
Pas
Crear el codi Java que aplica la lògica anterior.
L’exemple següent mostra el codi de l’estructura bàsica de la classe CrnpClient. Les implementacions dels quatre mètodes de l’auxiliar als quals es fa referència en els mètodes de tancament i el constructor es mostren més endavant en aquest capítol Recordeu que es mostra el codi que importa tots els paquets necessaris.
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); regPort = (new Integer(args)).intValue(); localPort = (new Integer(args)).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 de l’membre es descriuen més endavant en aquest capítol.
Com analitzar els arguments de la línia de ordres
Pas
Per analitzar els arguments de la línia d’ordres, consulteu el codi inclòs en apèndix G, Aplicació CrnpClient.java.
Com definir el subprocés de recepció d’esdeveniments
en el codi, cal comprovar que la recepció d’esdeveniments es realitzi en un subprocés a part de manera que l’aplicació pugui seguir realitzant l’altre treball, mentre el subprocés d’esdeveniment es bloqueja i espera tornar a marcar de esdeveniments.
Nota –
La configuració de XML es descriu més endavant en aquest capítol.
Passos
-
en el codi, defineixi la subclasse Thread anomenada EventReceptionThread que crea un socket, ServerSocket, i espera la recepció dels esdeveniments en el sòcol.
en aquesta part de el codi d’exemple, els esdeveniments no es llegeixen ni es processen. La lectura i el processament d’esdeveniments es descriuen més endavant en aquest capítol.EventReceptionThread crea un sòcol, ServerSocket, en una adreça de protocol d’inteconexión amb comodins. EventReceptionThread també manté una referència a l’objecte CrnpClient perquè EventReceptionThread pugui enviar esdeveniments a l’objecte CrnpClient per al seu processament.
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;}
-
Construeixi un objecte createEvtRecepThr.
private void createEvtRecepThr() throws Exception{ evtThr = new EventReceptionThread(this); evtThr.start();}
Com registrar retrucades o anul·lar el registre
El procés de registre comporta les següents accions:
-
Obrir un sòcol de TCP bàsic per al port i protocol d’interred de registre
-
Crea el missatge de registre de XML
-
Envia el missatge de registre de XML en el sòcol
-
Llegir el missatge de resposta d’XML fora de el sòcol
-
Tanca el sòcol
Passos
-
Crear el codi Java que aplica la lògica anterior.
El següent codi d’exemple mostra la implementació de l’mètode registerCallbacks de la classe CrnpClient (anomenada pel constructor CrnpClient). Les trucades a createRegistrationString () i readRegistrationReply () es descriuen de forma més detallada posteriorment en aquest capítol.
regIp i regPort són objectes membres configurats pel 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();}
-
Apliqui el mètode unregister.
El mètode shutdown de CrnpClient crida a aquest mètode. La implementació d’createUnregistrationString es descriu de forma més detallada posteriorment en aquest capítol.
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();}
Com generar XML
Ara que s’ha configurat l’estructura de l’aplicació i s’ha escrit tot el codi de xarxa, cal escriure el codi que genera i analitza XML. En primer lloc, escriviu el codi que genera el missatge de registre XML SC_CALLBACK_REG.
Un missatge SC_CALLBACK_REG està format per un tipus de registre (ADD_CLIENT, REMOVE_CLIENT, ADD_EVENTS, or REMOVE_EVENTS), un port de tornar a marcar i una llista d’esdeveniments d’interès. Cada esdeveniment consta d’una classe i una subclasse, seguits d’una llista de parells de noms i valors.
En aquesta part de l’exemple, s’escriu una classe CallbackReg que guarda el tipus de registre, el port de tornar a marcar i la llista d’esdeveniments de registre. Aquesta classe també pot serializarse per a un missatge de XML SC_CALLBACK_REG.
Un mètode interessant d’aquesta classe és el convertToXml, que crea una cadena de missatge de XML SC_CALLBACK_REG a partir dels membres de la classe. La documentació de JAXP ubicada a http://java.sun.com/xml/jaxp/index.html descriu de forma més detallada el codi d’aquest mètode.
La implementació de la classe Event es mostra en el següent codi d’exemple . Recordeu que la classe CallbackReg utilitza una classe Event, que permet emmagatzemar un esdeveniment i convertir-lo en un element, Element, XML.
Passos
-
Crear el codi 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;}
-
Implementeu les classes Event i NVPair.
Tingueu en compte que la classe CallbackReg utilitza Event, que al seu torn utilitza una 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;}
Com crear els missatges de registre i d’anul·lació de l’registre
Un cop creades les classes de l’auxiliar que generen missatges de XML, pot escriure la implementació de l’mètode createRegistrationString. registerCallbacks crida a aquest mètode; aquest procés es descriu en Com registrar retrucades o anul·lar el registre.
createRegistrationString construeix un objecte CallbackReg i estableix el seu port i tipus de registre. A continuació, createRegistrationString construeix diversos esdeveniments mitjançant els mètodes de l’auxiliar createAllEvent, createMembershipEvent, createRgEvent i createREvent. Cada esdeveniment s’afegeix a l’objecte CallbackReg després de crear-lo. Finalment, createRegistrationString invoca el mètode convertToXml en l’objecte CallbackReg per recuperar el missatge de XML en la forma d’String.
Tingueu en compte que la variable de l’membre regs emmagatzema els arguments de la línia d’ordres proporcionats pel usuari per a l’aplicació. El cinquè argument i següents s’especifiquen els esdeveniments per als quals s’hauria de registrar l’aplicació. El quart argument especifica el tipus de registre, però s’ignora en aquest exemple. El codi complet inclòs en apèndix G, Aplicació CrnpClient.java mostra com utilitzar aquest quart argument.
Passos
-
Crear el codi 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.equals("M")) { cbReg.addRegEvent(createMembershipEvent()); } else if (regs.equals("A")) { cbReg.addRegEvent(createAllEvent()); } else if (regs.substring(0,2).equals("RG")) { cbReg.addRegEvent(createRgEvent(regs.substring(3))); } else if (regs.substring(0,1).equals("R")) { cbReg.addRegEvent(createREvent(regs.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);}
-
Crear la cadena d’anul·lació de registre.
La creació la cadena d’anul·lació de registre és un procés més senzill que la creació de la cadena de registre, ja que no és necessari adaptar esdeveniments.
private String createUnregistrationString() throws Exception{ CallbackReg cbReg = new CallbackReg(); cbReg.setPort("" + localPort); cbReg.setRegType(CallbackReg.REMOVE_CLIENT); String xmlStr = cbReg.convertToXml(); return (xmlStr);}
Com configurar l’analitzador XML
Un cop creat el codi de generació de XML i de xarxa per a l’aplicació, el constructor CrnpClient crida a mètode setupXmlProcessing, que crea un objecte DocumentBuilderFactory i estableix diverses propietats d’anàlisi en aquest objecte. La documentació de JAXP descriu de forma més detallada aquest mètode. Consulti http://java.sun.com/xml/jaxp/index.html.
Pas
Crear el codi 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);}
Com analitzar el missatge de resposta de registre
Per analitzar el missatge de XML SC_REPLY que CRNP envia a resposta a un missatge de registre o d’anul·lació de l’registre, cal disposar d’una classe auxiliar RegReply, que es pot construir a partir d’un document de XML. Aquesta classe proporciona accesores per al codi d’estat i el missatge d’estat. Per analitzar la seqüència de XML de servidor, és necessari crear un nou document de XML i utilitzar el seu mètode d’anàlisi. La documentació de JAXP ubicada a http://java.sun.com/xml/jaxp/index.html descriu de forma més detallada aquest mètode.
Passos
-
Crear el codi Java que aplica la lògica anterior.
Tingueu present que el mètode readRegistrationReply utilitza la nova 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);}
-
Implementar la classe RegReply.
Tingueu present que el mètode retrieveValues recorre l’arbre DOM de el document XML i extreu el codi i el missatge d’estat. La documentació de JAXP ubicada a http://java.sun.com/xml/jaxp/index.html conté més informació.
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;}
Com analitzar els esdeveniments de tornar a marcar
El pas final consisteix en analitzar i processar els esdeveniments reals de tornar a marcar. Per ajudar en aquesta tasca, de modificar la classe Event creada el Com generar XML perquè aquesta classe pugui construir un esdeveniment, Event, a partir d’un document de XML i crear un element, Element, XML: Aquest canvi requereix un constructor addicional ( que inclogui un document de XML), un mètode retrieveValues, l’addició de dues variables de membres (vendor i publisher), mètodes d’accés per a tots els camps i, finalment, un mètode d’impressió.
Passos
-
Crear el codi Java que aplica la lògica anterior.
Tingueu en compte que aquest codi és similar a el de la classe RegReply, que es descriu en Com analitzar el missatge de resposta de registre.
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;
-
Implementeu els constructors i mètodes addicionals de la classe NVPair compatibles amb l’anàlisi XML.
Els canvis realitzats en Event mostrats en el Pas 1 han també de fer en 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); }}
-
Implementeu el bucle while en EventReceptionThread, que espera les tornar a marcar de esdeveniments.
EventReceptionThread es descriu en Com definir el subprocés de recepció d’esdeveniments.
while(true) { Socket sock = listeningSock.accept(); Document doc = db.parse(sock.getInputStream()); Event event = new Event(doc); client.processEvent(event); sock.close(); }
Com executar l’aplicació
Passos
-
Esdevén root o assumeixi una funció similar.
-
Executar l’aplicació.
a
# java CrnpClient crnpHost crnpPort localPort ...
a
El codi complet de l’aplicació CrnpClient es mostra en apèndix G, Aplicació CrnpClient.java.