| |
这是一个非常好的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."); 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 + ""); } // display the current connection limit out.print("max connections: " + maxconnections + "");
// 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() + ""); } }
/** * 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() + ""); 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."); out.print("enter lines. end with a ´.´ on a line by itself."); 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(""); } 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 "); out.print("content-type: text/plain"); string line; while((line = in.readline()) != null) { if (line.length() == 0) break; out.print(line + ""); } 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() + ""); 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."); 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"); // say so authorized = true; // grant authorization } else out.print("invalid password"); // otherwise fail } else if (command.equals("add")) { // add service command // check whether password has been given if (!authorized) out.print("password required"); 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"); // acknowledge } } else if (command.equals("remove")) { // remove service if (!authorized) out.print("password required"); else { int port = integer.parseint(t.nexttoken()); server.removeservice(port); // remove the service out.print("service removed"); // acknowledge } } else if (command.equals("max")) { // set connection limit if (!authorized) out.print("password required"); else { int max = integer.parseint(t.nexttoken()); server.setmaxconnections(max); out.print("max connections changed"); } } else if (command.equals("status")) { // status display if (!authorized) out.print("password required"); else server.displaystatus(out); } else if (command.equals("help")) { // help command // display command syntax. password not required out.print("commands:" + " password " + " add " + " remove " + " max " + " status" + " help" + " quit"); } else if (command.equals("quit")) break; // quit command. else out.print("unrecognized command"); // 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:" + e + ""); } } // 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(); } } }
|
|