⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 generalserver.java

📁 java jdk 实例宝典 源码 夏先波 编著 随书光盘源码
💻 JAVA
📖 第 1 页 / 共 2 页
字号:
package book.net;

import java.io.*;
import java.net.*;
import java.util.*;

/**
 * 这个类实现了一个灵活的、支持多线程的服务器的通用框架。
 * 它能够侦听任何端口,当收到来自某个端口的连接请求时,
 * 将连接的输入和输出传递给特定的服务对象。由服务对象处理请求。
 * 支持一定数量的并发访问,支持日志功能,将日志写到输出流中。
 **/
public class GeneralServer {

	// 服务器上行的分割符
	// 出于java的安全限制,System.getProperty("line.seperator")是不能够直接取得的
	// 通过下面的方法获取
	public static final String LINE_SEPERATOR = (String) java.security.AccessController
			.doPrivileged(new sun.security.action.GetPropertyAction(
					"line.separator")); 
	// 帮助信息,指示启动服务器必须带有参数,
	// 参数包括:
	// (1)服务器启动的服务的类名、服务对应的端口号
	// (2)如果需要对服务器进行控制,则需要指定控制密码和端口。
	public static final String HELP_MESSAGE = "Usage: java book.net.GeneralServer "
				+ "[-control <password> <port>] "
				+ "[<servicename> <port> ... ]";
	
	// 保存侦听器及其侦听端口的映射
	Map services; 
	// 保存当前的连接信息
	Set connections; 
	// 支持的最大并发连接数
	int maxConnections; 
	// 管理服务器启动的所有线程
	ThreadGroup threadGroup;
	// 日志消息的输出流
	PrintWriter logStream;

	/**
	 * 构造方法
	 * 指定日志消息输出流和最大并发连接数。
	 **/
	public GeneralServer(OutputStream logStream, int maxConnections) {
		// 初始化各实例变量
		this.setLogStream(logStream);
		this.log("Starting server");
		// 创建一个线程组,所有启动的线程都在该组内
		this.threadGroup = new ThreadGroup(GeneralServer.class.getName());
		this.maxConnections = maxConnections;
		this.services = new HashMap();
		this.connections = new HashSet(maxConnections);
	}

	/** 
	 * 设置日志消息输出流,允许参数为null
	 **/
	public synchronized void setLogStream(OutputStream out) {
		if (out != null){
			this.logStream = new PrintWriter(out);
		} else {
			this.logStream = null;
		}
	}
	/**
	 * 写字符串类型的日志信息到日志输出流。
	 */
	protected synchronized void log(String s) {
		if (this.logStream != null) {
			this.logStream.println("[" + new Date() + "] " + s);
			this.logStream.flush();
		}
	}
	/**
	 * 写对象类型的日志信息到日志输出流
	 */
	protected void log(Object o) {
		this.log(o.toString());
	}

	/**
	 * 服务器启动一个新服务,该服务对象运行在指定的端口上。
	 * @param service	待启动的服务对象
	 * @param port		服务对象使用的端口
	 * @throws IOException
	 */
	public synchronized void addService(Service service, int port)
			throws IOException {
		// 首先判断该端口是否已经被服务器使用了
		Integer key = new Integer(port);
		if (this.services.get(key) != null)
			throw new IllegalArgumentException("Port " + port
					+ " already in use.");
		// 为服务和端口创建一个侦听器,侦听连接请求
		Listener listener = new Listener(threadGroup, port, service);
		// 将端口和侦听器保存
		this.services.put(key, listener);
		// 写日志
		this.log("Starting service " + service.getClass().getName() + " on port "
				+ port);
		// 启动侦听器
		listener.start();
	}

	/**
	 * 服务器停止一个服务,它不会中止任何已经接受了的连接,
	 * 但是会使服务器停止接受关于该端口的连接请求
	 * @param port	待停止服务的端口
	 */
	public synchronized void removeService(int port) {
		// 找到该端口上的侦听器
		Integer key = new Integer(port);
		final Listener listener = (Listener) services.get(key);
		// 将侦听器停止
		if (listener == null) {
			return;
		}
		listener.pleaseStop();
		// 将端口上的服务去掉
		this.services.remove(key);
		// 写日志
		this.log("Stopping service " + listener.service.getClass().getName()
				+ " on port " + port);
	}

	/**
	 * 启动服务器的方法,需要配置参数。
	 */
	public static void main(String[] args) {
		
		try {
			// 参数数目必须大于等于2。
			if (args.length < 2) // Check number of arguments
				throw new IllegalArgumentException("Must specify a service");

			// 本例使用标准的输出流当作日志信息输出流,同时连接数最大为10
			GeneralServer server = new GeneralServer(System.out, 10);

			// 解析参数
			int i = 0;
			while (i < args.length) {
				// 处理-control参数
				if (args[i].equals("-control")) {
					i++;
					// 获取控制的密码
					String password = args[i++];
					// 获取控制的端口
					int port = Integer.parseInt(args[i++]);
					// 加载控制服务实例,在端口上工作。
					server.addService(new Control(server, password), port);
				} else {
					// 处理初始启动的服务参数,并动态加载服务实例
					// 获取服务的类名
					String serviceName = args[i++];
					// 根据服务类名生成实例
					Class serviceClass = Class.forName(serviceName);
					Service service = (Service) serviceClass.newInstance();
					// 获取端口
					int port = Integer.parseInt(args[i++]);
					// 启动服务
					server.addService(service, port);
				}
			}
		} catch (Exception e) {
			// 参数错误
			System.err.println("Server: " + e);
			System.err.println(HELP_MESSAGE);
			System.exit(1);
		}
	}
	
	/**
	 * 增加一个连接。
	 * 当侦听器收到客户端的连接请求时,会调用该方法。
	 * 这里会创建一个连接对象,并保存,如果连接数已满,则关闭连接。
	 * @param s		连接的客户端socket
	 * @param service	连接请求的服务
	 */
	protected synchronized void addConnection(Socket s, Service service) {
		// 判断连接数是否已满
		if (this.connections.size() >= this.maxConnections) {
			try {
				// 拒绝客户端
				PrintWriter out = new PrintWriter(s.getOutputStream());
				out.print("Connection refused; "
						+ "the server is busy; please try again later." + LINE_SEPERATOR);
				out.flush();
				// 关闭socket连接
				s.close();
				// 写日志
				this.log("Connection refused to "
						+ s.getInetAddress().getHostAddress() + ":"
						+ s.getPort() + ": max connections reached.");
			} catch (IOException e) {
				this.log(e);
			}
		} else {
			// 如果连接数没满,则接受连接请求
			// 创建一个连接Connection对象
			Connection c = new Connection(s, service);
			// 保存并写日志
			this.connections.add(c);
			this.log("Connected to " + s.getInetAddress().getHostAddress() + ":"
					+ s.getPort() + " on port " + s.getLocalPort()
					+ " for service " + service.getClass().getName());
			// 启动连接线程
			c.start();
		}
	}

	/**
	 * 结束一个连接
	 * @param c
	 */
	protected synchronized void endConnection(Connection c) {
		// 从连接列表中清除
		this.connections.remove(c);
		this.log("Connection to " + c.client.getInetAddress().getHostAddress() + ":"
				+ c.client.getPort() + " closed.");
	}

	/**
	 * 设置服务器的并行最大访问数
	 * @param max
	 */
	public synchronized void setMaxConnections(int max) {
		this.maxConnections = max;
	}
	
	/**
	 * 显示服务器状态,有利于调试和控制服务器
	 * @param out	状态信息的输出流
	 */
	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.print("SERVICE " + listener.service.getClass().getName()
					+ " ON PORT " + port + LINE_SEPERATOR);
		}

		// 显示服务器当前连接数的限制
		out.print("MAX CONNECTIONS: " + this.maxConnections + LINE_SEPERATOR);

		// 显示当前所有连接的信息
		Iterator conns = this.connections.iterator();
		while (conns.hasNext()) {
			Connection c = (Connection) conns.next();
			out.print("CONNECTED TO "
					+ c.client.getInetAddress().getHostAddress() + ":"
					+ c.client.getPort() + " ON PORT "
					+ c.client.getLocalPort() + " FOR SERVICE "
					+ c.service.getClass().getName() + LINE_SEPERATOR);
		}
	}

	/** 
	 * 内部类,实现侦听器,负责侦听端口的连接请求,使用了ServerSocket
	 * 当收到一个连接请求时,调用Server的addConnection方法,决定是否接受连接请求。
	 * 服务器上每个服务都有一个侦听器。
	 **/
	public class Listener extends Thread {
		// 侦听连接的socket
		ServerSocket listen_socket;
		// 侦听端口
		int port;
		// 在该端口上的服务
		Service service; 

		/**
		 * 表示是否需要停止侦听
		 * 使用volatile 声明的变量的值的时候,系统总是重新从它所在的内存读取数据,
		 * 即使它前面的指令刚刚从该处读取过数据。而且读取的数据立刻被保存
		 */
		volatile boolean stop = false; 
	
		/**
		 * 构造方法
		 * 创建了一个线程,放入服务器的线程组中。
		 * 创建一个ServerSocket对象用于侦听指定端口。
		 * @param group		线程组
		 * @param port		端口
		 * @param service	端口上的服务
		 * @throws IOException
		 */
		public Listener(ThreadGroup group, int port, Service service)
				throws IOException {
			super(group, "Listener:" + port);
			listen_socket = new ServerSocket(port);
			// 如果10分钟没有收到连接请求,ServerSocket自动关闭
			listen_socket.setSoTimeout(600000);
			this.port = port;
			this.service = service;
		}

		/** 
		 * 停止侦听器工作
		 ***/
		public void pleaseStop() {
			// 设置停止标志
			this.stop = true;
			// 中断接受操作
			this.interrupt();
			try {
				// 关闭ServerSocket
				listen_socket.close();
			} catch (IOException e) {
			}
		}

		/**
		 * 侦听器的线程体,等待连接请求,接受连接。
		 **/

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -