何千军 ([email protected])
软件工程师,独立顾问和自由撰稿人
2002 年 10 月
为什么需要后台服务程序?
在许多大型软件项目中,后台服务程序都扮演着极为重要的角色。它们无处不在,例如操作系统的内核程序处理各种对操作系统的内部调用;数据库系统的核心管理进程处理各种对数据库的读写操作和进程、资源的管理;大型ERP软件的内核管理程序要完成各种应用模块的资源、通讯管理等等。它们使系统的各种服务、资源与应用的表示之间形成了一个松耦合关系,这样就极大地增加了软件系统的稳定性和伸缩性。后台服务程序也就是相当于软件系统的管理调度中心,它是软件系统的中央处理器,是保证应用高效运行的内核程序。
在不同的软件系统中,由于软件的复杂程度和功能的不同使得各种软件系统的后台服务程序都有存在较大的差异。但是后台服务程序还是有很多共同的特点,一个基本的后台服务程序大概可以由四个部分构成:通用服务器框架、服务与监听、服务控制、服务器实现。下面我们就使用具体的代码来实现一个基本的后台服务器程序。
通用服务器框架
在开发后台服务程序中,我们首先实现一个通用服务器框架类,它能在多个端口提供多线程的服务(由多个Service对象定义),并且能够在系统运行时动态地调用和实例化Service类并加载新的服务或卸除已加载的服务。
清单 1显示了如何编制一个通用服务器框架类文件。
【清单 1:通用服务器框架类文件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 + " 已经被占用!");
if (getfreeConnections(maxConn)>=0) {
Listener listener = new Listener(this, port, service, maxConn);
services.put(key,listener);
log.println("启动" + servicename + "服务在" + port +"端口上");
listener.start();
} else {
System.err.println("系统并发连接限制已经达到最大值!");
System.err.println("服务" + 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("服务" + 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 + "响应请求在" + s.getInetAddress().getHostAddress() + "的" + sport + "端口上");
}
out.println("当前连接数为" + currentConn);
out.println("当前系统空闲连接数为" + 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;
}
}
如上所述可知,服务器框架类Server主要是通过端口到监听器影射的散列表services来管理服务对象,Server类的几个主要方法说明如下:
addService方法:此方法能够在特定的端口上创建新的服务,即在指定端口上运行指定的Service对象。
removeService方法:此方法使服务器停止指定端口上的服务,并不终止连接,仅使服务器停止接受新的连接。
displayStatus方法:此方法用于打印指定流上服务器的状态信息。
服务与监听【清单 2:Listener.java的一个简单实现】
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("系统并发连接限制已经超过最大值!!");
}
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对象通过在指定端口上与指定服务的绑定实现监听过程,主要的几个方法说明如下:
pleaseStop:以礼貌的方法停止接受连接。
addConnection:把指定要处理的服务放到线程池中,以等待空闲的线程处理服务。
监听对象通过传递一个Service对象并唤醒它的serve()方法才真正提供了服务。下面我们就来实现一个具体的服务:
服务接口Service只有一个抽象方法serve(InputStream in, OutputStream out),它所有服务实现所必须重写的一个方法,Listing 3显示了一个Service接口的定义。【清单 3:定义一个Service接口】 import java.io.*; import java.net.*; public interface Service { public void serve(InputStream in, OutputStream out) throws IOException; } 编写一个简单的显示时间服务类:见清单 4。
【清单 4:一个显示系统当前时间的服务类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("当前时间是:" + sDate); outPrint.flush(); try { from_client.close(); outPrint.close(); }catch (Exception e){} } }
通过实现Service接口可以编写很多的服务实现提供各种不同的服务,读者有兴趣也可自己编写一个服务来测试一下。
服务控制
服务控制是在服务器运行时动态地操作控制服务器,如系统运行时,动态地装载(卸载)服务,显示服务器的状态信息等等。为了简化基本后台服务系统的复杂程度,我们采用创建一个ControlService服务实例来在运行时管理服务器。ControlService实现了基于命令的协议,可用密码保护操纵服务器,代码如清单 5所示:
【清单 5:服务控制类文件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("后台管理服务响应请求!\n"); PrintWriter to_client = new PrintWriter(out); synchronized (this) { if(server.connected) { to_client.println("已经有用户连接,本服务仅允许一个连接!\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("服务" + servicename + "已经加载\n"); flag = false; } if (flag) to_client.println("系统不能启动非法服务:" + servicename); else {to_client.println("请输入服务名!\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 +"上的服务已经卸载\n"); else to_client.println("端口: "+ port +"上无任何服务运行,卸载操作失败!\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("系统后台出错" + 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; } }
服务器实现和运行
服务器实现主要完成服务器的初始化,启动服务控制实例等工作,代码如清单 6所示:
【清单 6:runServer.java的一个简单实现】
import java.util.*; public class runServer { public runServer() { } public static void main(String[] args) { try { int argLen = args.length; System.out.println("正在初始化系统请等待......"); System.out.println(""); int maxConn = 30; Server server = new Server(maxConn); System.out.println("################################################################"); System.out.println("# #"); System.out.println("# 后台服务管理系统 #"); 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("参数数目不正确!"); System.out.println("系统启动,进入监听服务模式......"); } 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)启动后台服务,在6809启动服务控制实例,在6810端口启动。
【清单 7:启动后台服务程序】
% java runServer -s test 6809 Timer 6810
在另外一个窗口,执行如下命令,将显示当前系统的时间。
% java clientConnect 6810
在另外一个窗口,执行如下命令,你将能查看系统服务状态信息,并动态地装载你写的服务对象,你可以测试一下。
% java clientConnect 6809
现在,一个基本的后台服务程序即编制完成了。实际上,一个大型软件的后台服务程序是非常复杂的,上面的例子希望能起到抛砖引玉的效果。要写出性能良好的后台服务程序还有很多工作要做。
参考资源
要了解更多的 Java信息,请阅读 java.sun.com的 主页。 下载后台服务程序示例全部代码:code.zip本文地址:http://com.8s8s.com/it/it18148.htm