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 阅读(166) 评论(0)  编辑  收藏 所属分类: j2se


只有注册用户登录后才能发表评论。


网站导航: