|
何千軍, 軟件工程師, 獨立顧問和自由撰稿人
2002 年 10 月 16 日 在很多大型軟件項目中,都有一些極為重要的后臺服務(wù)程序,它們并不處理具體的系統(tǒng)業(yè)務(wù)邏輯,但對整個系統(tǒng)資源和服務(wù)的協(xié)調(diào)管理卻是不可或缺。本文討論如何完整地編寫一個后臺服務(wù)管理程序,并通過一個具體的后臺服務(wù)管理例子來說明這一技術(shù)實現(xiàn)的技巧。 為什么需要后臺服務(wù)程序? 在許多大型軟件項目中,后臺服務(wù)程序都扮演著極為重要的角色。它們無處不在,例如操作系統(tǒng)的內(nèi)核程序處理各種對操作系統(tǒng)的內(nèi)部調(diào)用;數(shù)據(jù)庫系統(tǒng)的核心管理進(jìn)程處理各種對數(shù)據(jù)庫的讀寫操作和進(jìn)程、資源的管理;大型ERP軟件的內(nèi)核管理程序要完成各種應(yīng)用模塊的資源、通訊管理等等。它們使系統(tǒng)的各種服務(wù)、資源與應(yīng)用的表示之間形成了一個松耦合關(guān)系,這樣就極大地增加了軟件系統(tǒng)的穩(wěn)定性和伸縮性。后臺服務(wù)程序也就是相當(dāng)于軟件系統(tǒng)的管理調(diào)度中心,它是軟件系統(tǒng)的中央處理器,是保證應(yīng)用高效運行的內(nèi)核程序。 在不同的軟件系統(tǒng)中,由于軟件的復(fù)雜程度和功能的不同使得各種軟件系統(tǒng)的后臺服務(wù)程序都有存在較大的差異。但是后臺服務(wù)程序還是有很多共同的特點,一個基本的后臺服務(wù)程序大概可以由四個部分構(gòu)成:通用服務(wù)器框架、服務(wù)與監(jiān)聽、服務(wù)控制、服務(wù)器實現(xiàn)。下面我們就使用具體的代碼來實現(xiàn)一個基本的后臺服務(wù)器程序。
通用服務(wù)器框架 在開發(fā)后臺服務(wù)程序中,我們首先實現(xiàn)一個通用服務(wù)器框架類,它能在多個端口提供多線程的服務(wù)(由多個Service對象定義),并且能夠在系統(tǒng)運行時動態(tài)地調(diào)用和實例化Service類并加載新的服務(wù)或卸除已加載的服務(wù)。 清單 1顯示了如何編制一個通用服務(wù)器框架類文件。 【清單 1:通用服務(wù)器框架類文件Server.java】import java.util.*;import java.io.*;import java.net.*;public class Server { protected Map services; Set connections; int maxConnections; int freeConn; ThreadGroup threadGroup; private int currentConn; private PrintWriter log = new PrintWriter(System.out, true); public boolean connected = false; public Properties proPort, proNum; public synchronized void setControlFlag() { connected = true; } public synchronized void removeControlFlag() { connected = false; } public void setProperty(Properties proPort, Properties proNum) { this.proPort = proPort; this.proNum = proNum; } public Server(int maxConn) { this.maxConnections = maxConn; this.freeConn=maxConnections; this.threadGroup = new ThreadGroup(Server.class.getName()); currentConn = 0; this.services = new HashMap(); this.connections = new HashSet(maxConnections); } public synchronized void addService(Service service,int port, int maxConn) throws IOException { String servicename = service.getClass().getName(); Integer key = new Integer(port); if (services.get(key) != null) throw new IllegalArgumentException("端口:" + port + " 已經(jīng)被占用!"); if (getfreeConnections(maxConn)>=0) { Listener listener = new Listener(this, port, service, maxConn); services.put(key,listener); log.println("啟動" + servicename + "服務(wù)在" + port +"端口上"); listener.start(); } else { System.err.println("系統(tǒng)并發(fā)連接限制已經(jīng)達(dá)到最大值!"); System.err.println("服務(wù)" + servicename + " 啟動失敗!"); } } public synchronized void addService(Service service,int port) throws IOException { this.addService(service,port,10); } public synchronized boolean removeService(int port) { Integer key = new Integer(port); int maxConn =10; final Listener listener = (Listener) services.get(key); if (listener == null) { log.println("Service " + " isn‘t started on port " + port); return false; } services.remove(key); listener.pleaseStop(); freeConn+=listener.maxConn; log.println("Close " + listener.service + " on port " + port); return true; } public synchronized void displayStatus(PrintWriter out) { Iterator keys = services.keySet().iterator(); while (keys.hasNext()) { Integer port = (Integer) keys.next(); Listener listener = (Listener) services.get(port); out.println("服務(wù)" + listener.service + "運行" + port + "\n"); } out.println("連接限制為" + maxConnections); Iterator conns = connections.iterator(); while (conns.hasNext()) { Socket s = (Socket) conns.next(); int sport = s.getLocalPort(); Listener listen = (Listener) services.get(new Integer(sport)); String servicename = listen.service; out.println(servicename + "響應(yīng)請求在" + s.getInetAddress().getHostAddress() + "的" + sport + "端口上"); } out.println("當(dāng)前連接數(shù)為" + currentConn); out.println("當(dāng)前系統(tǒng)空閑連接數(shù)為" + freeConn); } private synchronized int getfreeConnections(int maxConn) { int num = -1; if (freeConn >= maxConn) { freeConn-=maxConn; num = freeConn; } return num; } public synchronized int getConnections() { return currentConn; } public synchronized int addConnections(Socket s) { connections.add(s); return currentConn++; } public synchronized int removeConnections(Socket s) { connections.remove(s); try { s.close(); } catch (Exception e) { System.out.println(e.getMessage()); } return currentConn--; } public synchronized int getConnections(int connections) { int num = 0; if ((num=freeConn-connections) >= 0) { freeConn = num; } else num = -1; return num; } private synchronized int getFreeConn() { return freeConn; }}
|
如上所述可知,服務(wù)器框架類Server主要是通過端口到監(jiān)聽器影射的散列表services來管理服務(wù)對象,Server類的幾個主要方法說明如下: - addService方法:此方法能夠在特定的端口上創(chuàng)建新的服務(wù),即在指定端口上運行指定的Service對象。
- removeService方法:此方法使服務(wù)器停止指定端口上的服務(wù),并不終止連接,僅使服務(wù)器停止接受新的連接。
- displayStatus方法:此方法用于打印指定流上服務(wù)器的狀態(tài)信息。
服務(wù)與監(jiān)聽 每個服務(wù)都對應(yīng)著一個監(jiān)聽對象,監(jiān)聽指定端口的連接并在獲得連接請求時調(diào)用addConnection( Socket s, Service service )方法來取得(釋放)一個連接。清單 2顯示了如何編制一個監(jiān)聽類文件。 【清單 2:Listener.java的一個簡單實現(xiàn)】import java.util.*;import java.io.*;import java.net.*;public class Listener extends Thread { private ServerSocket listener; private int port; String service; Set connections; private Service runService; private boolean stop_flag = false; int maxConn; private PrintWriter log = new PrintWriter(System.err, true); private ThreadGroup group; private int currentConnections = 0; Server server; Socket client = null; public Listener(Server server, int port, Service service, int maxConn, boolean bl) throws IOException { this.server = server; this.port = port; this.service = service.getClass().getName(); this.maxConn = maxConn; this.group = server.threadGroup; this.connections = server.connections; listener = new ServerSocket(this.port); if (bl == false) listener.setSoTimeout(600000); this.runService = service; if (!stop_flag) { for (int i=0;i<this.maxConn;i++) { ConnectionHandler currentThread = new ConnectionHandler(server,logStream); new Thread(this.group, currentThread, this.service+this.port+i).start(); this.logStream.realLog("向線程組" + group.toString() + "添加一個線程" + this.service+this.port+i); } } else throw new IllegalArgumentException("系統(tǒng)并發(fā)連接限制已經(jīng)超過最大值!!"); } public Listener(Server server, int port, Service service, int maxConn) throws IOException { this(server, port, service, maxConn, false); } public void pleaseStop() { this.stop_flag = true; try { listener.close(); } catch (Exception e) { } this.interrupt(); } public void run() { while(!stop_flag) { try { client = listener.accept(); addConnection(client,runService); } catch (Exception e) {} } try { client.close(); } catch (IOException e) {} } private synchronized void addConnection(Socket s, Service service) { ConnectionHandler.requestToHandler(s, service); }}
|
在實際處理過程中,Listener對象通過在指定端口上與指定服務(wù)的綁定實現(xiàn)監(jiān)聽過程,主要的幾個方法說明如下: - pleaseStop:以禮貌的方法停止接受連接。
- addConnection:把指定要處理的服務(wù)放到線程池中,以等待空閑的線程處理服務(wù)。
監(jiān)聽對象通過傳遞一個Service對象并喚醒它的serve()方法才真正提供了服務(wù)。下面我們就來實現(xiàn)一個具體的服務(wù): - 服務(wù)接口Service只有一個抽象方法serve(InputStream in, OutputStream out),它所有服務(wù)實現(xiàn)所必須重寫的一個方法,Listing 3顯示了一個Service接口的定義。
【清單 3:定義一個Service接口】import java.io.*;import java.net.*;public interface Service { public void serve(InputStream in, OutputStream out) throws IOException;}
|
- 編寫一個簡單的顯示時間服務(wù)類:見清單4。
【清單 4:一個顯示系統(tǒng)當(dāng)前時間的服務(wù)類Timer.java】import java.util.*;import java.text.*;import java.io.*;import java.net.*;public class Timer implements Service { public Timer() { } public void serve(InputStream in, OutputStream out) throws IOException { String timeFormat = "yyyy-MM-dd hh:mm:ss"; SimpleDateFormat timeFormatter = new SimpleDateFormat(timeFormat); BufferedReader from_client = new BufferedReader(new InputStreamReader(in)); PrintWriter outPrint = new PrintWriter(out); String sDate = timeFormatter.format(new Date()); outPrint.println("當(dāng)前時間是:" + sDate); outPrint.flush(); try { from_client.close(); outPrint.close(); }catch (Exception e){} }}
|
通過實現(xiàn)Service接口可以編寫很多的服務(wù)實現(xiàn)提供各種不同的服務(wù),讀者有興趣也可自己編寫一個服務(wù)來測試一下。
服務(wù)控制 服務(wù)控制是在服務(wù)器運行時動態(tài)地操作控制服務(wù)器,如系統(tǒng)運行時,動態(tài)地裝載(卸載)服務(wù),顯示服務(wù)器的狀態(tài)信息等等。為了簡化基本后臺服務(wù)系統(tǒng)的復(fù)雜程度,我們采用創(chuàng)建一個ControlService服務(wù)實例來在運行時管理服務(wù)器。ControlService實現(xiàn)了基于命令的協(xié)議,可用密碼保護操縱服務(wù)器,代碼如清單 5所示: 【清單 5:服務(wù)控制類文件ControlService.java】import java.io.*;import java.util.*;import java.net.*;import dvb.kuanshi.kssms.util.*;import dvb.kuanshi.kssms.server.Server;public class ControlService implements Service { Server server; String password; public ControlService(Server server, String password) { this.server = server; this.password = password; } public void serve(InputStream in, OutputStream out) throws IOException { boolean authorized = false; BufferedReader from_client = new BufferedReader(new InputStreamReader(in)); PrintWriter to_console = new PrintWriter(System.out, true); to_console.println("后臺管理服務(wù)響應(yīng)請求!\n"); PrintWriter to_client = new PrintWriter(out); synchronized (this) { if(server.connected) { to_client.println("已經(jīng)有用戶連接,本服務(wù)僅允許一個連接!\n"); to_client.close(); return; } else server.setControlFlag(); } to_client.println("Remote Console>"); to_client.flush(); String line; while ((line=from_client.readLine())!=null) { int len = line.indexOf("Remote Console>"); line = line.substring(len+1,line.length()); String printStr; try { StringTokenizer st = new StringTokenizer(line); int count = st.countTokens(); if (!st.hasMoreElements()) continue; String first = st.nextToken().toLowerCase(); if (first.equals("password")) { String pwd = st.nextToken(); if (pwd.equals(this.password)) { to_client.println("OK"); authorized = true; } else to_client.println("密碼無效,請重試!\n"); } else if (first.equals("add")) { if(!authorized) to_client.println("請登陸!\n"); else { count--; String servicename; int Port; boolean flag = true; if (count>0) { servicename = st.nextToken(); Port = Integer.parseInt(st.nextToken()); server.addService(loadClass(servicename1), Port); to_client.println("服務(wù)" + servicename + "已經(jīng)加載\n"); flag = false; } if (flag) to_client.println("系統(tǒng)不能啟動非法服務(wù):" + servicename); else {to_client.println("請輸入服務(wù)名!\n");} } } else if (first.equals("remove")) { if(!authorized) to_client.println("請登陸!\n"); else { count--; if (count>0) { int port = Integer.parseInt(st.nextToken()); boolean bl = server.removeService(port); if (bl) to_client.println("端口: " + port +"上的服務(wù)已經(jīng)卸載\n"); else to_client.println("端口: "+ port +"上無任何服務(wù)運行,卸載操作失??!\n"); } else to_client.println("請輸入端口名!\n"); } } else if(first.equals("status")) { if(!authorized) to_client.println("請登陸!\n"); else server.displayStatus(to_client); } else if(first.equals("help")) { if(!authorized) to_client.println("請登陸!\n"); else printHelp(to_client); } else if(first.equals("quit")) break; else to_client.println("命令不能識別!\n"); } catch(Exception e) {to_client.println("系統(tǒng)后臺出錯" + e.getMessage() +"\n"); printHelp(to_client); } to_client.println("Remote Console>"); to_client.flush(); } to_client.flush(); authorized = false; server.removeControlFlag(); to_client.close(); from_client.close(); } private void printHelp(PrintWriter out) { out.println("COMMANDS:" + "\tpassword <password>\n" + "\t\tadd <servicename> <port>\n" + "\t\tremove <port>\n" + "\t\tstatus\n" + "\t\thelp\n" + "\t\tquit\n"); } protected Service loadClass(String servicename) { Service s = null; try { Class serviceClass = Class.forName(servicename); s = (Service) serviceClass.newInstance(); } catch (Exception e) { } return s; }}
|
服務(wù)器實現(xiàn)和運行 服務(wù)器實現(xiàn)主要完成服務(wù)器的初始化,啟動服務(wù)控制實例等工作,代碼如清單 6所示: 【清單 6:runServer.java的一個簡單實現(xiàn)】import java.util.*;public class runServer { public runServer() { } public static void main(String[] args) { try { int argLen = args.length; System.out.println("正在初始化系統(tǒng)請等待......"); System.out.println(""); int maxConn = 30; Server server = new Server(maxConn); System.out.println("################################################################"); System.out.println("# #"); System.out.println("# 后臺服務(wù)管理系統(tǒng) #"); System.out.println("# #"); System.out.println("################################################################"); System.out.println(); if (argLen>2) { for (int i = 0;i<argLen;i++) { if (args[i].equals("-s")) { i++; String password = args[i]; i++; int port = Integer.parseInt(args[i]); server.addService(new ControlService(server,password), port, 2); } else { String servicename = args[i]; i++; int port = Integer.parseInt(args[i]); server.addService(loadClass(servicename), port); } } } else throw new IllegalArgumentException("參數(shù)數(shù)目不正確!"); System.out.println("系統(tǒng)啟動,進(jìn)入監(jiān)聽服務(wù)模式......"); } catch (Exception e) { throw new IllegalArgumentException(e.getMessage()); } } protected static Service loadClass(String servicename) { Service s = null; try { Class serviceClass = Class.forName(servicename); s = (Service) serviceClass.newInstance(); } catch (Exception e) { } return s; }}
|
下面我們就可以啟動示例程序來測試一下了。 如清單 7所示,以密碼保護方式(密碼為test)啟動后臺服務(wù),在6809啟動服務(wù)控制實例,在6810端口啟動。 【清單 7:啟動后臺服務(wù)程序】 % java runServer -s test 6809 Timer 6810
|
在另外一個窗口,執(zhí)行如下命令,將顯示當(dāng)前系統(tǒng)的時間。 % java clientConnect 6810
|
在另外一個窗口,執(zhí)行如下命令,你將能查看系統(tǒng)服務(wù)狀態(tài)信息,并動態(tài)地裝載你寫的服務(wù)對象,你可以測試一下。 % java clientConnect 6809
|
現(xiàn)在,一個基本的后臺服務(wù)程序即編制完成了。實際上,一個大型軟件的后臺服務(wù)程序是非常復(fù)雜的,上面的例子希望能起到拋磚引玉的效果。要寫出性能良好的后臺服務(wù)程序還有很多工作要做。
參考資料 - 要了解更多的 Java信息,請閱讀 java.sun.com的 主頁。
- 下載后臺服務(wù)程序示例全部代碼: code.zip
關(guān)于作者 | | 何千軍,軟件工程師,獨立顧問和自由撰稿人。參加了多項大型軟件系統(tǒng)設(shè)計,主要從事分布式Internet系統(tǒng)的開發(fā)??梢酝ㄟ^ heqianjun@163.net 和我聯(lián)系。 |
|
|