Vincent

          Vicent's blog
          隨筆 - 74, 文章 - 0, 評論 - 5, 引用 - 0
          數據加載中……

          一個Socket服務器樣板程序

          這是一個非常好的Socket服務器樣板程序,這個socket服務器可以為你建立指定的監聽端口、客戶端請求響應機制等一些服務器所具備的基本框架



          /*

          * 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.

          */



          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 ] " +

          "[ ... ]");

          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 {

          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 echos back 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/plain\n\n");

          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: disconnect

          *

          * This service displays a prompt, and sends all of its output to the user

          * in capital 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("OK\n"); // Say so

          authorized = true; // Grant authorization

          }

          else out.print("INVALID PASSWORD\n"); // Otherwise fail

          }

          else if (command.equals("add")) { // Add Service command

          // Check whether password has been given

          if (!authorized) out.print("PASSWORD REQUIRED\n");

          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 ADDED\n"); // acknowledge

          }

          }

          else if (command.equals("remove")) { // Remove service

          if (!authorized) out.print("PASSWORD REQUIRED\n");

          else {

          int port = Integer.parseInt(t.nextToken());

          server.removeService(port); // remove the service

          out.print("SERVICE REMOVED\n"); // acknowledge

          }

          }

          else if (command.equals("max")) { // Set connection limit

          if (!authorized) out.print("PASSWORD REQUIRED\n");

          else {

          int max = Integer.parseInt(t.nextToken());

          server.setMaxConnections(max);

          out.print("MAX CONNECTIONS CHANGED\n");

          }

          }

          else if (command.equals("status")) { // Status Display

          if (!authorized) out.print("PASSWORD REQUIRED\n");

          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" +

          "\tstatus\n" +

          "\thelp\n" +

          "\tquit\n");

          }

          else if (command.equals("quit")) break; // Quit command.

          else out.print("UNRECOGNIZED COMMAND\n"); // 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();

          }

          }

          }

          posted on 2006-08-24 15:13 Binary 閱讀(180) 評論(0)  編輯  收藏 所屬分類: j2se

          主站蜘蛛池模板: 莎车县| 昌乐县| 佛学| 洛川县| 读书| 峨眉山市| 瓦房店市| 阿拉善左旗| 隆回县| 化州市| 汕尾市| 安图县| 酉阳| 青川县| 肇东市| 宜兰县| 东兰县| 杨浦区| 富民县| 泰兴市| 老河口市| 加查县| 黎川县| 潞西市| 龙井市| 蒙阴县| 普安县| 丹凤县| 邢台市| 抚顺县| 新建县| 吉安县| 石阡县| 宝兴县| 盈江县| 苍梧县| 靖江市| 高邑县| 重庆市| 沾化县| 和平区|