以下实例说明了如何开发名为 CrnpClient 的、使用 CRNP 的简单 Java 应用程序。 应用程序通过群集上的 CRNP 服务器登记事件回叫,侦听事件回叫,并通过打印事件的内容对事件进行处理。 应用程序终止前将撤销登记事件回叫请求。
样例应用程序使用 JAXP(Java API for XML Processing)生成和分析 XML。 此实例并未介绍如何使用 JAXP。 有关 JAXP 的详细信息,请访问 http://java.sun.com/xml/jaxp/index.html。
此实例提供了应用程序的几个片断,完整的应用程序可以从附录 G,CrnpClient.java 应用程序 中找到。 为了更有效地说明某些概念,本章中的实例与附录 G,CrnpClient.java 应用程序 中的完整应用程序略有不同。
为了简洁起见,本章实例中的样例代码不包含注释。 附录 G,CrnpClient.java 应用程序 中的完整应用程序包含注释。
此实例中的应用程序只是通过退出应用程序来处理大多数错误状态。 实际的应用程序需要更有效地处理错误。
首先需要设置环境。
下载并安装 JAXP 以及 Java 编译器和虚拟机的正确版本。
相关指令可以从 http://java.sun.com/xml/jaxp/index.html 上找到。
此实例要求使用 Java 1.3.1 或更高版本。
确保在编译命令行中指定 classpath,以使编译器能够找到 JAXP 类。 在源文件所在的目录下键入以下内容:
% 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 |
其中 JAXP_ROOT 是 JAXP jar 文件所在目录的绝对路径或相对路径,SOURCE_FILENAME 是 Java 源文件的名称。
运行应用程序时,请指定 classpath,以使应用程序能够装入正确的 JAXP 类文件(请注意,classpath 中的第一个路径是当前目录):
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 |
现在已完成环境配置,可以开发应用程序了。
在实例的这一部分,您可以使用分析命令行参数并构造 CrnpClient 对象的主要方法创建一个名为 CrnpClient 的基类。 此对象将命令行参数传递给类,等候用户终止应用程序, 对 CrnpClient 调用 shutdown 命令,然后退出。
设置 XML 处理对象。
创建一个侦听事件回叫的线程。
联系 CRNP 服务器并登记事件回叫。
创建实现上述逻辑的 Java 代码。
以下实例显示了 CrnpClient 类的骨架代码。 后文介绍了构造函数中引用的这四个帮助程序方法以及停机方法的实现。 请注意,用来输入所需软件包的代码如下。
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; } |
后文详细介绍了成员变量。
要了解如何分析命令行参数,请参考附录 G,CrnpClient.java 应用程序 中的代码。
在本代码中,必须确保在单独的线程中执行事件接收,这样应用程序才能在事件线程中断以及等候事件回叫时继续执行其它工作。
后文介绍了如何设置 XML。
在代码中定义一个名为 EventReceptionThread 的 Thread 子类,用于创建 ServerSocket 并等候事件到达套接字。
在实例代码的这一部分,既没有读取事件也没有处理事件。 后文介绍了如何读取和处理事件。 EventReceptionThread 在通配符网络互联协议地址上创建一个 ServerSocket。 EventReceptionThread 还保留 CrnpClient 对象的一个引用,这样 EventReceptionThread 才能够将事件发送到 CrnpClient 对象进行处理。
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; } |
了解 EventReceptionThread 类的工作原理后,就可以构造 createEvtRecepThr 对象了:
private void createEvtRecepThr() throws Exception { evtThr = new EventReceptionThread(this); evtThr.start(); } |
向登记网络互联协议和端口打开一个基本 TCP 套接字
构造 XML 登记消息
通过套接字发送 XML 登记消息
脱离套接字并读取 XML 应答消息
关闭套接字
创建实现上述逻辑的 Java 代码。
以下实例显示了 CrnpClient 类的 registerCallbacks 方法的实现,该方法由 CrnpClient 构造函数调用。 后文详细介绍了 createRegistrationString() 和 readRegistrationReply() 的调用。
regIp 和 regPort 是由构造函数设置的对象成员。
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(); } |
实现 unregister 方法。 此方法是由 CrnpClient 的 shutdown 方法调用的。 后文详细介绍了 createUnregistrationString 的实现。
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(); } |
设置应用程序结构并编写所有联网代码后, 就可以编写生成和分析 XML 的代码了。 第一步是编写生成 SC_CALLBACK_REG XML 登记消息的代码。
SC_CALLBACK_REG 消息包括登记类型(ADD_CLIENT、REMOVE_CLIENT、ADD_EVENTS 或 REMOVE_EVENTS)、回叫端口和感兴趣的事件列表。 每个事件都包括一个类和一个子类,后跟一个名称和值对列表。
在实例的这一部分,需要编写一个存储登记类型、回叫端口和登记事件列表的 CallbackReg 类。 此类还可以将自身串行化为一个 SC_CALLBACK_REG XML 消息。
此类中有一个有趣的方法,即 convertToXml 方法,它可以从类成员中创建一个 SC_CALLBACK_REG XML 消息字符串。 位于 http://java.sun.com/xml/jaxp/index.html 上的 JAXP 文档详细介绍了此方法的代码。
Event 类的实现如下所示。 请注意,CallbackReg 类使用一个可以存储一个事件并可以将其转换成 XML Element 的 Event 类。
创建实现上述逻辑的 Java 代码。
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; } |
实现 Event 和 NVPair 类。
请注意,CallbackReg 类使用一个 Event 类,该 Event 类使用一个 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; } |
创建生成 XML 消息的帮助程序类后,就可以编写 createRegistrationString 方法的实现了。 此方法是由 registerCallbacks 方法调用的,登记和撤销登记回叫中对 registerCallbacks 方法进行了介绍。
createRegistrationString 构造一个 CallbackReg 对象并设置其登记类型和端口。 然后 createRegistrationString 使用 createAllEvent、createMembershipEvent、createRgEvent 和 createREvent 帮助程序方法构造各种事件。 创建 CallbackReg 对象后,每个事件都被添加到该对象中。 最后,createRegistrationString 对 CallbackReg 对象调用 convertToXml 方法,以便在 String 表单中检索 XML 消息。
请注意,regs 成员变量可以存储用户向应用程序提供的命令行参数。 第五个参数及后续参数用于指定应用程序应该登记的事件。 第四个参数用于指定登记的类型,但本实例中忽略了该参数。 附录 G,CrnpClient.java 应用程序 中的完整代码显示了如何使用这个参数。
创建实现上述逻辑的 Java 代码。
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); } |
创建撤销登记字符串。
创建撤销登记字符串比创建登记字符串要简单,因为不必考虑事件:
private String createUnregistrationString() throws Exception { CallbackReg cbReg = new CallbackReg(); cbReg.setPort("" + localPort); cbReg.setRegType(CallbackReg.REMOVE_CLIENT); String xmlStr = cbReg.convertToXml(); return (xmlStr); } |
现在已经为应用程序创建了联网和 XML 生成代码。 最后一步是分析及处理登记应答和事件回叫。 CrnpClient 构造函数将调用 setupXmlProcessing 方法。 此方法将创建 DocumentBuilderFactory 对象并设置该对象的各种分析特性。 位于 http://java.sun.com/xml/jaxp/index.html 上的 JAXP 文档详细介绍了此方法。
创建实现上述逻辑的 Java 代码。
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); } |
要分析 CRNP 服务器为响应登记消息和撤销登记消息而发送的 SC_REPLY XML 消息,需要使用 RegReply 帮助程序类。 可以从 XML 文档构造此类。 此类提供了状态代码和状态消息的存取程序。 要分析来自服务器的 XML 数据流,需要创建一个新的 XML 文档并使用该文档的分析方法(位于 http://java.sun.com/xml/jaxp/index.html 上的 JAXP 文档详细介绍了此方法)。
创建实现上述逻辑的 Java 代码。
请注意,readRegistrationReply 方法使用新的 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); } |
实现 RegReply 类。
请注意,retrieveValues 方法将检索 XML 文档中的 DOM 树,并提取状态代码和状态消息。 位于 http://java.sun.com/xml/jaxp/index.html 上的 JAXP 文档包含了详细的信息。
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; } |
最后一步是分析和处理实际的回叫事件。 要协助执行此任务,需要修改您在生成 XML中创建的 Event 类,使其能够从 XML 文档中构造一个 Event,并创建一个 XML Element。 此更改需要一个附加构造函数(调用 XML 文档)、一个 retrieveValues 方法、两个附加成员变量(vendor 和 publisher)、所有字段的存取程序方法,以及一个打印方法。
创建实现上述逻辑的 Java 代码。
请注意,此代码与分析登记应答中描述的 RegReply 类的代码相似。
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; |
实现支持 XML 分析的 NVPair 类的其它构造函数和方法。
步骤 1 中对 Event 类的更改要求对 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); } } |
在 EventReceptionThread 中实现等候事件回叫的 while 循环(定义事件接收线程中对 EventReceptionThread 进行了介绍)。
while(true) { Socket sock = listeningSock.accept(); Document doc = db.parse(sock.getInputStream()); Event event = new Event(doc); client.processEvent(event); sock.close(); } |
运行应用程序。
# java CrnpClient crnpHost crnpPort localPort ... |
附录 G,CrnpClient.java 应用程序 中列出了CrnpClient 应用程序的完整代码。