package gralej.server; import gralej.controller.StreamInfo; import gralej.util.Log; import java.io.BufferedInputStream; import java.io.IOException; import java.net.InetAddress; import java.net.ServerSocket; import java.net.Socket; import java.net.UnknownHostException; import java.util.Vector; /** * A multi-client (=threaded) grale server binding to a TCP/IP socket. * * @author Niels * @version $Id:SocketServer.java 18 2007-11-13 16:26:47Z niels@drni.de $ */ public class SocketServer extends ServerBaseImpl { private int port; private InetAddress bindIP; private ServerSocket socket; private ConnectionWaiter waiter; private Vector<ConnectionHandler> handlerList; /** * A helper class that waits for incoming connections as a separate thread. * This class will invoke a {@link ConnectionHandler} for each incoming * connection. */ private class ConnectionWaiter extends Thread { private boolean shutdown_state = false; public ConnectionWaiter() { super(); // informative thread name for debugging this.setName("ConnectionWaiter (bind: " + socket.getInetAddress().getHostAddress() + ":" + socket.getLocalPort() + ")"); } /** * Run this ConnectionWaiter: This waits for incoming connections and * invokes new handler threads if required. Must not be run elsehwere * but in {@link SocketServer#startListening()} */ public void run() { try { // server main loop while (! shutdown_state) { Socket clientSocket = socket.accept(); new ConnectionHandler(clientSocket).start(); } } catch (IOException e) { //e.printStackTrace(); if (! shutdown_state) { Log.error("An exception " + "occured while waiting for incoming connections. " + "Server thread terminates now, restart the server " + "to regain networking functionality. "); } else { Log.debug(this.getName() + ": Caught exception during server shutdown, " + "this may be normal."); } } // if this was a shutdown, then it's finished now shutdown_state = false; } synchronized void shutdownWaiter() throws IOException { shutdown_state = true; socket.close(); } } /** * A class resp. thread that handles an incoming connection and informs the * listeners of this {@link SocketServer}. */ private class ConnectionHandler extends Thread { private Socket clientSocket; private boolean shutdown_state = false; public ConnectionHandler(Socket clientSocket) { super(); this.clientSocket = clientSocket; // informative thread name for debugging this.setName("ConnectionHandler (remote: " + clientSocket.getInetAddress().getHostAddress() + ":" + clientSocket.getPort() + ")"); } /** * Runs this ConnectionHandler: This will detect the input protocol and * hand over to the listeners of the {@link SocketServer} afterwards. */ public void run() { // System.err.println("- SocketServer accepted new connection!"); registerConnHandler(this); try { BufferedInputStream s = new BufferedInputStream(clientSocket .getInputStream()); StreamInfo info = new StreamInfo(StreamProtocolMagic .stream2type(s)); notifyListeners(s, info); } catch (IOException e) { // the remote host closed the connection before something // useful has happened, we can ignore this. //e.printStackTrace(); if ( ! shutdown_state) { Log.debug(this.getName() + ": Remote closed connection " + "before sending something useful. Closing handler."); } else { Log.debug(this.getName() + ": " + "Caught exception during connection shutdown, " + "this may be normal."); } } removeConnHandler(this); } synchronized private void killConnection() throws IOException { shutdown_state = true; clientSocket.close(); } } /** * Instantiates a new socket server listening to 127.0.0.1 (aka localhost) * only. * * @param port * the port to bind to on localhost. */ public SocketServer(int port) { try { bindIP = InetAddress.getByName("127.0.0.1"); } catch (UnknownHostException e) { // this should never ever happen throw new RuntimeException(e); } this.port = port; handlerList = new Vector<ConnectionHandler>(); } private void registerConnHandler(ConnectionHandler c) { // vectors are synchronized, this should be thread-safe handlerList.add(c); } private void removeConnHandler(ConnectionHandler c) { // vectors are synchronized, this should be thread-safe handlerList.removeElement(c); } /** * A new socket server listening to a given IP address, allowing binding to * public specific network interfaces. * * @param port * the port to bind to. * @param bind * address of the interface to bind to. * @throws NotImplementedInServerException */ public SocketServer(int port, InetAddress bind) throws NotImplementedInServerException { this.port = port; this.bindIP = bind; // TODO: binding to public interfaces without access control is disabled throw new NotImplementedInServerException(); } /** * @see IGraleServer#startListening() */ public void startListening() throws IOException { if ( waiter != null ) { return; } // open the port, this may go wrong socket = new ServerSocket(port, 0, bindIP); // run the server main loop thread waiter = new ConnectionWaiter(); waiter.start(); } public boolean isListening() { return ( waiter != null && socket.isBound() ); } public void stopListening() throws IOException { waiter.shutdownWaiter(); // hopefully the garbage collector will do its job now... waiter = null; } public void killActiveConnections() throws IOException { // copy vector because it will be modified by terminating // connections // clone manually to ensure the right outcome Vector<ConnectionHandler> handlers = new Vector<ConnectionHandler>(); for ( ConnectionHandler c : handlerList ) { handlers.add(c); } for ( ConnectionHandler c : handlers) { c.killConnection(); } } }