以下範例說明如何開發使用 CRNP 的名為 CrnpClient 的簡單 Java 應用程式。 該應用程式在叢集上的 CRNP 伺服器中註冊事件回呼、偵聽事件回呼、透過列印事件內容來處理這些事件。 在終止之前,該應用程式會取消註冊其對事件回呼的要求。
應用程式範例執行 XML 的產生,並使用 JAXP (用於 XML 處理的 Java API ) 進行剖析。 此範例並不教您如何使用 JAXP。 在 http://java.sun.com/xml/jaxp/index.html 上對 JAXP 進行了更詳細的說明。
此範例展示完整應用程式的片段,在附錄 G, CrnpClient.java 應用程式 中可以找到整個應用程式。 為了更有效地說明一些特殊概念,本章中所展示的範例與附錄 G, CrnpClient.java 應用程式 中所展示的完整應用程式稍有不同。
為了簡潔,本章範例中的範例程式碼不包括註釋。 附錄 G, CrnpClient.java 應用程式 中的完整應用程式包括註釋。
本範例中所顯示的應用程式僅需結束應用程式就可處理大多數錯誤狀況。 實際的應用程式需要更牢固地處理錯誤。
首先,您需要設定環境。
下載並安裝 JAXP、正確版本的 Java 編譯器、虛擬機器。
您可以在 http://java.sun.com/xml/jaxp/index.html 上找到說明。
此範例需要 Java 1.3.1 或更高版本的 Java。
確定您在編譯指令行中指定 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 類別 (由 CrnpClient 建構元呼叫) 的 registerCallbacks 方法的實作。 將在以後對 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 類別 (其自身使用 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 方法呼叫。
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 文件建構此類別。 此類別為狀況碼與狀況訊息提供 accessor。 若要剖析來自伺服器的 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)、適用於所有欄位的 accessor 方法,最後還需要列印方法。
建立實施前導邏輯的 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 應用程式的完整程式碼。