Java Scoket (轉)

gugu99發表於2008-05-02
Java Scoket (轉)[@more@]

/*
 * Copyright (c) 2000 David Flanagan.  All rights reserved.
 * This code is from the book Examples in a Nut, 2nd Edition.
 * It is provd 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 .
 */
package com.davidflanagan.examples;
import java.io.*;
import java.net.*;
import java.util.*;

/**
 * This class is a generic 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
 * 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 pass 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 ] " +
  "[ ... ]");
  System.exit(1);
  }
  }

  // This is the state for the server
  Map services;  // Hashtable map 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 tenate 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) {  // l 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 deging, 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 {
  Socket client;  // The socket to talk to the client through
  Service service;  // The service being provided to that client
 
  /**
  * This constructor just saves some state and calls the superclass
  * constructor to create a thread to handle the connection.  Connection
  * objects are created by Listener threads.  These threads are part of
  * the server's ThreadGroup, so all Connection threads are part of that
  * group, too.
  **/
  public Connection(Socket client, Service service) {
  super("Server.Connection:" +
  client.getInetAddress().getHostAddress() +
  ":" + client.getPort());
  this.client = client;
  this.service = service;
  }
 
  /**
  * This is the body of each and every Connection thread.
  * All it does is pass the client input and output streams to the
  * serve() method of the specified Service object.  That method is
  * responsible for reading from and writing to those streams to
  * provide the actual service.  Recall that the Service object has
  * been passed from the Server.addService() method to a Listener
  * object to the addConnection() method to this Connection object, and
  * is now finally being used to provide the service.  Note that just
  * before this thread exits it always calls the endConnection() method
  * to remove itself from the set of connections
  **/
  public void run() {
  try {
  InputStream in = client.getInputStream();
  OutputStream out = client.getOutputStream();
  service.serve(in, out);
  }
  catch (IOException e) {log(e);}
  finally { endConnection(this); }
  }
  }
 
  /**
  * Here is the Service interface that we have seen so much of.  It defines
  * only a single method which is invoked to provide the service.  serve()
  * will be passed an input stream and an output stream to the client.  It
  * should do whatever it wants with them, and should close them before
  * returning.
  *
  * All connections through the same port to this service share a single
  * Service object.  Thus, any state local to an individual connection must
  * be stored in local variables within the serve() method.  State that
  * should be global to all connections on the same port should be stored
  * in instance variables of the Service class.  If the same Service is
  * running on more than one port, there will typically be different
  * Service instances for each port.  Data that should be global to all
  * connections on any port should be stored in static variables.
  *
  * Note that implementations of this interface must have a no-argument
  * constructor if they are to be dynamically instantiated by the main()
  * method of the Server class.
  **/
  public interface Service {
  public void serve(InputStream in, OutputStream out) throws IOException;
  }

  /**
  * A very simple service.  It displays the current time on the server
  * to the client, and closes the connection.
  **/
  public static class Time implements Service {
  public void serve(InputStream i, OutputStream o) throws IOException {
  PrintWriter out = new PrintWriter(o);
  out.print(new Date() + "n");
  out.close();
  i.close();
  }
  }
 
  /**
  * This is another example service.  It reads lines of input from the
  * client, and sends them back, reversed.  It also displays a welcome
  * message and instructions, and closes the connection when the user
  * enters a '.' on a line by itself.
  **/
  public static class Reverse implements Service {
  public void serve(InputStream i, OutputStream o) throws IOException {
  BufferedReader in = new BufferedReader(new InputStreamReader(i));
  PrintWriter out =
  new PrintWriter(new BufferedWriter(new OutputStreamWriter(o)));
  out.print("Welcome to the line reversal server.n");
  out.print("Enter lines.  End with a '.' on a line by itself.n");
  for(;;) {
  out.print("> ");
  out.flush();
  String line = in.readLine();
  if ((line == null) || line.equals(".")) break;
  for(int j = line.length()-1; j >= 0; j--)
  out.print(line.charAt(j));
  out.print("n");
  }
  out.close();
  in.close();
  }
  }
 
  /**
  * This service is an HTTP mirror, just like the HttpMirror class
  * implemented earlier in this chapter.  It echback the client's
  * HTTP request
  **/
  public static class HTTPMirror implements Service {
  public void serve(InputStream i, OutputStream o) throws IOException {
  BufferedReader in = new BufferedReader(new InputStreamReader(i));
  PrintWriter out = new PrintWriter(o);
  out.print("HTTP/1.0 200 n");
  out.print("Content-Type: text/plainnn");
  String line;
  while((line = in.readLine()) != null) {
  if (line.length() == 0) break;
  out.print(line + "n");
  }
  out.close();
  in.close();
  }
  }
 
  /**
  * This service demonstrates how to maintain state across connections by
  * saving it in instance variables and using synchronized access to those
  * variables.  It maintains a count of how many clients have connected and
  * tells each client what number it is
  **/
  public static class UniqueID implements Service {
  public int id=0;
  public synchronized int nextId() { return id++; }
  public void serve(InputStream i, OutputStream o) throws IOException {
  PrintWriter out = new PrintWriter(o);
  out.print("You are client #: " + nextId() + "n");
  out.close();
  i.close();
  }
  }
 
  /**
  * This is a non-trivial service.  It implements a command-based protocol
  * that gives password-protected runtime control over the operation of the
  * server.  See the main() method of the Server class to see how this
  * service is started. 
  *
  * The recognized commands are:
  *  password: give password; authorization is required for most commands
  *  add:  dynamically add a named service on a specified port
  *  remove:  dynamically remove the service running on a specified port
  *  max:  change the current maximum connection limit.
  *  status:  display current services, connections, and connection limit
  *  help:  display a help message
  *  quit:  dinnect
  *
  * This service displays a prompt, and sends all of its output to the user
  * in ctal letters.  Only one client is allowed to connect to this
  * service at a time.
  **/
  public static class Control implements Service {
  Server server;  // The server we control
  String password;  // The password we require
  boolean connected = false; // Whether a client is already connected
 
  /**
  * Create a new Control service.  It will control the specified Server
  * object, and will require the specified password for authorization
  * Note that this Service does not have a no argument constructor,
  * which means that it cannot be dynamically instantiated and added as
  * the other, generic services above can be.
  **/
  public Control(Server server, String password) {
  this.server = server;
  this.password = password;
  }

  /**
  * This is the serve method that provides the service.  It reads a
  * line the client, and uses java.util.StringTokenizer to parse it
  * into commands and arguments.  It does various things depending on
  * the command.
  **/
  public void serve(InputStream i, OutputStream o) throws IOException {
  // Setup the streams
  BufferedReader in = new BufferedReader(new InputStreamReader(i));
  PrintWriter out = new PrintWriter(o);
  String line;  // For reading client input lines
  // Has the user has given the password yet?
  boolean authorized = false;

  // If there is already a client connected to this service, display
  // a message to this client and close the connection.  We use a
  // synchronized block to prevent a race condition.
  synchronized(this) {
  if (connected) {
  out.print("ONLY ONE CONTROL CONNECTION ALLOWED.n");
  out.close();
  return;
  }
  else connected = true;
  }

  // This is the main loop: read a command, parse it, and handle it
  for(;;) {  // infinite loop
  out.print("> ");  // Display a prompt
  out.flush();  // Make it appear right away
  line = in.readLine();  // Get the user's input
  if (line == null) break;  // Quit if we get EOF.
  try {
  // Use a StringTokenizer to parse the user's command
  StringTokenizer t = new StringTokenizer(line);
  if (!t.hasMoreTokens()) continue;  // if input was empty
  // Get first word of the input and convert to lower case
  String command = t.nextToken().toLowerCase();
  // Now compare to each of the possible commands, doing the
  // appropriate thing for each command
  if (command.equals("password")) {  // Password command
  String p = t.nextToken();  // Get the next word
  if (p.equals(this.password)) { // Is it the password?
  out.print("OKn");  // Say so
  authorized = true;  // Grant authorization
  }
  else out.print("INVALID PASSWORDn"); // Otherwise fail
  }
  else if (command.equals("add")) {  // Add Service command
  // Check whether password has been given
  if (!authorized) out.print("PASSWORD REQUIREDn");
  else {
  // Get the name of the service and try to
  // dynamically load and instantiate it.
  // Exceptions will be handled below
  String serviceName = t.nextToken();
  Class serviceClass = Class.forName(serviceName);
  Service service;
  try {
  service = (Service)serviceClass.newInstance();
  }
  catch (NoSuchMethodError e) {
  throw new IllegalArgumentException(
  "Service must have a " +
  "no-argument constructor");
  }
  int port = Integer.parseInt(t.nextToken());
  // If no exceptions occurred, add the service
  server.addService(service, port);
  out.print("SERVICE ADDEDn");  // acknowledge
  }
  }
  else if (command.equals("remove")) { // Remove service
  if (!authorized) out.print("PASSWORD REQUIREDn");
  else {
  int port = Integer.parseInt(t.nextToken());
  server.removeService(port); // remove the service
  out.print("SERVICE REMOVEDn"); // acknowledge
  }
  }
  else if (command.equals("max")) { // Set connection limit
  if (!authorized) out.print("PASSWORD REQUIREDn");
  else {
  int max = Integer.parseInt(t.nextToken());
  server.setMaxConnections(max);
  out.print("MAX CONNECTIONS CHANGEDn");
  }
  }
  else if (command.equals("status")) { // Status Display
  if (!authorized) out.print("PASSWORD REQUIREDn");
  else server.displayStatus(out);
  }
  else if (command.equals("help")) {  // Help command
  // Display command syntax.  Password not required
  out.print("COMMANDS:n" +
  "tpassword n" +
  "tadd n" +
  "tremove n" +
  "tmax n" +
  "tstatusn" +
  "thelpn" +
  "tquitn");
  }
  else if (command.equals("quit")) break; // Quit command.
  else out.print("UNRECOGNIZED COMMANDn"); // Error
  }
  catch (Exception e) {
  // If an exception occurred during the command, print an
  // error message, then output details of the exception.
  out.print("ERROR WHILE PARSING OR EXECUTING COMMAND:n" +
  e + "n");
  }
  }
  // Finally, when the loop command loop ends, close the streams
  // and set our connected flag to false so that other clients can
  // now connect.
  connected = false;
  out.close();
  in.close();
  } 
  }
}


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10748419/viewspace-1003223/,如需轉載,請註明出處,否則將追究法律責任。

相關文章