📄 server.java
字号:
/* * Copyright (c) 2000 David Flanagan. All rights reserved. * This code is from the book Java Examples in a Nutshell, 2nd Edition. * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. * You may study, use, and modify it for any non-commercial purpose. * You may distribute it non-commercially as long as you retain this notice. * For a commercial use license, or to purchase the book (recommended), * visit http://www.davidflanagan.com/javaexamples2. */package com.davidflanagan.examples.net;import java.io.*;import java.net.*;import java.util.*;/** * This class is a generic framework for a flexible, multi-threaded server. * It listens on any number of specified ports, and, when it receives a * connection on a port, passes input and output streams to a specified Service * object which provides the actual service. It can limit the number of * concurrent connections, and logs activity to a specified stream. **/public class Server { /** * A main() method for running the server as a standalone program. The * command-line arguments to the program should be pairs of servicenames * and port numbers. For each pair, the program will dynamically load the * named Service class, instantiate it, and tell the server to provide * that Service on the specified port. The special -control argument * should be followed by a password and port, and will start special * server control service running on the specified port, protected by the * specified password. **/ public static void main(String[] args) { try { if (args.length < 2) // Check number of arguments throw new IllegalArgumentException("Must specify a service"); // Create a Server object that uses standard out as its log and // has a limit of ten concurrent connections at once. Server s = new Server(System.out, 10); // Parse the argument list int i = 0; while(i < args.length) { if (args[i].equals("-control")) { // Handle the -control arg i++; String password = args[i++]; int port = Integer.parseInt(args[i++]); // add control service s.addService(new Control(s, password), port); } else { // Otherwise start a named service on the specified port. // Dynamically load and instantiate a Service class String serviceName = args[i++]; Class serviceClass = Class.forName(serviceName); Service service = (Service)serviceClass.newInstance(); int port = Integer.parseInt(args[i++]); s.addService(service, port); } } } catch (Exception e) { // Display a message if anything goes wrong System.err.println("Server: " + e); System.err.println("Usage: java Server " + "[-control <password> <port>] " + "[<servicename> <port> ... ]"); System.exit(1); } } // This is the state for the server Map services; // Hashtable mapping ports to Listeners Set connections; // The set of current connections int maxConnections; // The concurrent connection limit ThreadGroup threadGroup; // The threadgroup for all our threads PrintWriter logStream; // Where we send our logging output to /** * This is the Server() constructor. It must be passed a stream * to send log output to (may be null), and the limit on the number of * concurrent connections. **/ public Server(OutputStream logStream, int maxConnections) { setLogStream(logStream); log("Starting server"); threadGroup = new ThreadGroup(Server.class.getName()); this.maxConnections = maxConnections; services = new HashMap(); connections = new HashSet(maxConnections); } /** * A public method to set the current logging stream. Pass null * to turn logging off **/ public synchronized void setLogStream(OutputStream out) { if (out != null) logStream = new PrintWriter(out); else logStream = null; } /** Write the specified string to the log */ protected synchronized void log(String s) { if (logStream != null) { logStream.println("[" + new Date() + "] " + s); logStream.flush(); } } /** Write the specified object to the log */ protected void log(Object o) { log(o.toString()); } /** * This method makes the server start providing a new service. * It runs the specified Service object on the specified port. **/ public synchronized void addService(Service service, int port) throws IOException { Integer key = new Integer(port); // the hashtable key // Check whether a service is already on that port if (services.get(key) != null) throw new IllegalArgumentException("Port " + port + " already in use."); // Create a Listener object to listen for connections on the port Listener listener = new Listener(threadGroup, port, service); // Store it in the hashtable services.put(key, listener); // Log it log("Starting service " + service.getClass().getName() + " on port " + port); // Start the listener running. listener.start(); } /** * This method makes the server stop providing a service on a port. * It does not terminate any pending connections to that service, merely * causes the server to stop accepting new connections **/ public synchronized void removeService(int port) { Integer key = new Integer(port); // hashtable key // Look up the Listener object for the port in the hashtable final Listener listener = (Listener) services.get(key); if (listener == null) return; // Ask the listener to stop listener.pleaseStop(); // Remove it from the hashtable services.remove(key); // And log it. log("Stopping service " + listener.service.getClass().getName() + " on port " + port); } /** * This nested Thread subclass is a "listener". It listens for * connections on a specified port (using a ServerSocket) and when it gets * a connection request, it calls the servers addConnection() method to * accept (or reject) the connection. There is one Listener for each * Service being provided by the Server. **/ public class Listener extends Thread { ServerSocket listen_socket; // The socket to listen for connections int port; // The port we're listening on Service service; // The service to provide on that port volatile boolean stop = false; // Whether we've been asked to stop /** * The Listener constructor creates a thread for itself in the * threadgroup. It creates a ServerSocket to listen for connections * on the specified port. It arranges for the ServerSocket to be * interruptible, so that services can be removed from the server. **/ public Listener(ThreadGroup group, int port, Service service) throws IOException { super(group, "Listener:" + port); listen_socket = new ServerSocket(port); // give it a non-zero timeout so accept() can be interrupted listen_socket.setSoTimeout(600000); this.port = port; this.service = service; } /** * This is the polite way to get a Listener to stop accepting * connections ***/ public void pleaseStop() { this.stop = true; // Set the stop flag this.interrupt(); // Stop blocking in accept() try { listen_socket.close(); } // Stop listening. catch(IOException e) {} } /** * A Listener is a Thread, and this is its body. * Wait for connection requests, accept them, and pass the socket on * to the addConnection method of the server. **/ public void run() { while(!stop) { // loop until we're asked to stop. try { Socket client = listen_socket.accept(); addConnection(client, service); } catch (InterruptedIOException e) {} catch (IOException e) {log(e);} } } } /** * This is the method that Listener objects call when they accept a * connection from a client. It either creates a Connection object * for the connection and adds it to the list of current connections, * or, if the limit on connections has been reached, it closes the * connection. **/ protected synchronized void addConnection(Socket s, Service service) { // If the connection limit has been reached if (connections.size() >= maxConnections) { try { // Then tell the client it is being rejected. PrintWriter out = new PrintWriter(s.getOutputStream()); out.print("Connection refused; " + "the server is busy; please try again later.\n"); out.flush(); // And close the connection to the rejected client. s.close(); // And log it, of course log("Connection refused to " + s.getInetAddress().getHostAddress() + ":" + s.getPort() + ": max connections reached."); } catch (IOException e) {log(e);} } else { // Otherwise, if the limit has not been reached // Create a Connection thread to handle this connection Connection c = new Connection(s, service); // Add it to the list of current connections connections.add(c); // Log this new connection log("Connected to " + s.getInetAddress().getHostAddress() + ":" + s.getPort() + " on port " + s.getLocalPort() + " for service " + service.getClass().getName()); // And start the Connection thread to provide the service c.start(); } } /** * A Connection thread calls this method just before it exits. It removes * the specified Connection from the set of connections. **/ protected synchronized void endConnection(Connection c) { connections.remove(c); log("Connection to " + c.client.getInetAddress().getHostAddress() + ":" + c.client.getPort() + " closed."); } /** Change the current connection limit */ public synchronized void setMaxConnections(int max) { maxConnections = max; } /** * This method displays status information about the server on the * specified stream. It can be used for debugging, and is used by the * Control service later in this example. **/ public synchronized void displayStatus(PrintWriter out) { // Display a list of all Services that are being provided 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 + "\n"); } // Display the current connection limit out.print("MAX CONNECTIONS: " + maxConnections + "\n"); // Display a list of all current connections Iterator conns = 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() + "\n"); } } /** * This class is a subclass of Thread that handles an individual * connection between a client and a Service provided by this server. * Because each such connection has a thread of its own, each Service can * have multiple connections pending at once. Despite all the other * threads in use, this is the key feature that makes this a * multi-threaded server implementation. **/ public class Connection extends Thread {
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -