net.lightbody.bmp.proxy.jetty.util.ThreadedServer.java Source code

Java tutorial

Introduction

Here is the source code for net.lightbody.bmp.proxy.jetty.util.ThreadedServer.java

Source

// ========================================================================
// $Id: ThreadedServer.java,v 1.41 2005/12/10 00:38:20 gregwilkins Exp $
// Copyright 199-2004 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at 
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ========================================================================

package net.lightbody.bmp.proxy.jetty.util;

import net.lightbody.bmp.proxy.jetty.log.LogFactory;
import org.apache.commons.logging.Log;

import java.io.IOException;
import java.io.InputStream;
import java.io.InterruptedIOException;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.UnknownHostException;

/* ======================================================================= */
/**
 * Threaded socket server. This class listens at a socket and gives the connections received to a
 * pool of Threads
 * <P>
 * The class is abstract and derived classes must provide the handling for the connections.
 * <P>
 * The properties THREADED_SERVER_MIN_THREADS and THREADED_SERVER_MAX_THREADS can be set to control
 * the number of threads created.
 * <P>
 * 
 * @version $Id: ThreadedServer.java,v 1.41 2005/12/10 00:38:20 gregwilkins Exp $
 * @author Greg Wilkins
 */
abstract public class ThreadedServer extends ThreadPool {
    private static Log log = LogFactory.getLog(ThreadedServer.class);

    /* ------------------------------------------------------------------- */
    private InetAddrPort _address = null;
    private int _soTimeOut = -1;
    private int _lingerTimeSecs = 30;
    private boolean _tcpNoDelay = true;
    private int _acceptQueueSize = 0;
    private int _acceptors = 1;

    private transient Acceptor[] _acceptor;
    private transient ServerSocket _listen = null;
    private transient boolean _running = false;

    /* ------------------------------------------------------------------- */
    /*
     * Construct
     */
    public ThreadedServer() {
    }

    /* ------------------------------------------------------------ */
    /**
     * @return The ServerSocket
     */
    public ServerSocket getServerSocket() {
        return _listen;
    }

    /* ------------------------------------------------------------------- */
    /**
     * Construct for specific port.
     */
    public ThreadedServer(int port) {
        setInetAddrPort(new InetAddrPort(port));
    }

    /* ------------------------------------------------------------------- */
    /**
     * Construct for specific address and port.
     */
    public ThreadedServer(InetAddress address, int port) {
        setInetAddrPort(new InetAddrPort(address, port));
    }

    /* ------------------------------------------------------------------- */
    /**
     * Construct for specific address and port.
     */
    public ThreadedServer(String host, int port) throws UnknownHostException {
        setInetAddrPort(new InetAddrPort(host, port));
    }

    /* ------------------------------------------------------------------- */
    /**
     * Construct for specific address and port.
     */
    public ThreadedServer(InetAddrPort address) {
        setInetAddrPort(address);
    }

    /* ------------------------------------------------------------ */
    /**
     * Set the server InetAddress and port.
     * 
     * @param address The Address to listen on, or 0.0.0.0:port for all interfaces.
     */
    public synchronized void setInetAddrPort(InetAddrPort address) {
        if (_address != null && _address.equals(address))
            return;

        if (isStarted())
            log.warn(this + " is started");

        _address = address;
    }

    /* ------------------------------------------------------------ */
    /**
     * @return IP Address and port in a new Instance of InetAddrPort.
     */
    public InetAddrPort getInetAddrPort() {
        if (_address == null)
            return null;
        return new InetAddrPort(_address);
    }

    /* ------------------------------------------------------------ */
    /**
     * @param host
     */
    public synchronized void setHost(String host) throws UnknownHostException {
        if (_address != null && _address.getHost() != null && _address.getHost().equals(host))
            return;

        if (isStarted())
            log.warn(this + " is started");

        if (_address == null)
            _address = new InetAddrPort(host, 0);
        else
            _address.setHost(host);
    }

    /* ------------------------------------------------------------ */
    /**
     * @return Host name
     */
    public String getHost() {
        if (_address == null || _address.getInetAddress() == null)
            return null;
        return _address.getHost();
    }

    /* ------------------------------------------------------------ */
    /**
     * @param addr
     */
    public synchronized void setInetAddress(InetAddress addr) {
        if (_address != null && _address.getInetAddress() != null && _address.getInetAddress().equals(addr))
            return;

        if (isStarted())
            log.warn(this + " is started");

        if (_address == null)
            _address = new InetAddrPort(addr, 0);
        else
            _address.setInetAddress(addr);
    }

    /* ------------------------------------------------------------ */
    /**
     * @return IP Address
     */
    public InetAddress getInetAddress() {
        if (_address == null)
            return null;
        return _address.getInetAddress();
    }

    /* ------------------------------------------------------------ */
    /**
     * @param port
     */
    public synchronized void setPort(int port) {
        if (_address != null && _address.getPort() == port)
            return;

        if (isStarted())
            log.warn(this + " is started");

        if (_address == null)
            _address = new InetAddrPort(port);
        else
            _address.setPort(port);
    }

    /* ------------------------------------------------------------ */
    /**
     * @return port number
     */
    public int getPort() {
        if (_address == null)
            return 0;
        return _address.getPort();
    }

    /* ------------------------------------------------------------ */
    /**
     * Set Max Read Time.
     * 
     * @deprecated maxIdleTime is used instead.
     */
    public void setMaxReadTimeMs(int ms) {
        log.warn("setMaxReadTimeMs is deprecated. Use setMaxIdleTimeMs()");
    }

    /* ------------------------------------------------------------ */
    /**
     * @return milliseconds
     */
    public int getMaxReadTimeMs() {
        return getMaxIdleTimeMs();
    }

    /* ------------------------------------------------------------ */
    /**
     * @param ls seconds to linger or -1 to disable linger.
     */
    public void setLingerTimeSecs(int ls) {
        _lingerTimeSecs = ls;
    }

    /* ------------------------------------------------------------ */
    /**
     * @return seconds.
     */
    public int getLingerTimeSecs() {
        return _lingerTimeSecs;
    }

    /* ------------------------------------------------------------ */
    /**
     * @param tcpNoDelay if true then setTcpNoDelay(true) is called on accepted sockets.
     */
    public void setTcpNoDelay(boolean tcpNoDelay) {
        _tcpNoDelay = tcpNoDelay;
    }

    /* ------------------------------------------------------------ */
    /**
     * @return true if setTcpNoDelay(true) is called on accepted sockets.
     */
    public boolean getTcpNoDelay() {
        return _tcpNoDelay;
    }

    /* ------------------------------------------------------------ */
    /**
     * @return Returns the acceptQueueSize or -1 if not set.
     */
    public int getAcceptQueueSize() {
        return _acceptQueueSize;
    }

    /* ------------------------------------------------------------ */
    /**
     * The size of the queue for unaccepted connections. If not set, will default to greater of
     * maxThreads or 50.
     * 
     * @param acceptQueueSize The acceptQueueSize to set.
     */
    public void setAcceptQueueSize(int acceptQueueSize) {
        _acceptQueueSize = acceptQueueSize;
    }

    /* ------------------------------------------------------------ */
    /**
     * Set the number of threads used to accept connections. This should normally be 1, except when
     * multiple CPUs are available and low latency is a high priority.
     */
    public void setAcceptorThreads(int n) {
        _acceptors = n;
    }

    /* ------------------------------------------------------------ */
    /**
     * Get the nmber of threads used to accept connections
     */
    public int getAcceptorThreads() {
        return _acceptors;
    }

    /* ------------------------------------------------------------------- */
    /**
     * Handle new connection. This method should be overridden by the derived class to implement the
     * required handling. It is called by a thread created for it and does not need to return until
     * it has finished it's task
     */
    protected void handleConnection(InputStream in, OutputStream out) {
        throw new Error("Either handlerConnection must be overridden");
    }

    /* ------------------------------------------------------------------- */
    /**
     * Handle new connection. If access is required to the actual socket, override this method
     * instead of handleConnection(InputStream in,OutputStream out). The default implementation of
     * this just calls handleConnection(InputStream in,OutputStream out).
     */
    protected void handleConnection(Socket connection) throws IOException {
        if (log.isDebugEnabled())
            log.debug("Handle " + connection);
        InputStream in = connection.getInputStream();
        OutputStream out = connection.getOutputStream();

        handleConnection(in, out);
        out.flush();

        in = null;
        out = null;
        connection.close();
    }

    /* ------------------------------------------------------------ */
    /**
     * Handle Job. Implementation of ThreadPool.handle(), calls handleConnection.
     * 
     * @param job A Connection.
     */
    public void handle(Object job) {
        Socket socket = (Socket) job;
        try {
            if (_tcpNoDelay)
                socket.setTcpNoDelay(true);
            handleConnection(socket);
        } catch (Exception e) {
            log.debug("Connection problem", e);
        } finally {
            try {
                socket.close();
            } catch (Exception e) {
                log.debug("Connection problem", e);
            }
        }
    }

    /* ------------------------------------------------------------ */
    /**
     * New server socket. Creates a new servers socket. May be overriden by derived class to create
     * specialist serversockets (eg SSL).
     * 
     * @param address Address and port
     * @param acceptQueueSize Accept queue size
     * @return The new ServerSocket
     * @exception java.io.IOException
     */
    protected ServerSocket newServerSocket(InetAddrPort address, int acceptQueueSize) throws java.io.IOException {
        if (address == null)
            return new ServerSocket(0, acceptQueueSize);

        return new ServerSocket(address.getPort(), acceptQueueSize, address.getInetAddress());
    }

    /* ------------------------------------------------------------ */
    /**
     * Accept socket connection. May be overriden by derived class to create specialist
     * serversockets (eg SSL).
     * 
     * @deprecated use acceptSocket(int timeout)
     * @param ignored
     * @param timeout The time to wait for a connection. Normally passed the ThreadPool maxIdleTime.
     * @return Accepted Socket
     */
    protected Socket acceptSocket(ServerSocket ignored, int timeout) {
        return acceptSocket(timeout);
    }

    /* ------------------------------------------------------------ */
    /**
     * Accept socket connection. May be overriden by derived class to create specialist
     * serversockets (eg SSL).
     * 
     * @param serverSocket
     * @param timeout The time to wait for a connection. Normally passed the ThreadPool maxIdleTime.
     * @return Accepted Socket
     */
    protected Socket acceptSocket(int timeout) {
        try {
            Socket s = null;

            if (_listen != null) {
                if (_soTimeOut != timeout) {
                    _soTimeOut = timeout;
                    _listen.setSoTimeout(_soTimeOut);
                }

                s = _listen.accept();

                try {
                    if (getMaxIdleTimeMs() >= 0)
                        s.setSoTimeout(getMaxIdleTimeMs());
                    if (_lingerTimeSecs >= 0)
                        s.setSoLinger(true, _lingerTimeSecs);
                    else
                        s.setSoLinger(false, 0);
                } catch (Exception e) {
                    LogSupport.ignore(log, e);
                }
            }
            return s;
        } catch (java.net.SocketException e) {
            // TODO - this is caught and ignored due strange
            // exception from linux java1.2.v1a
            LogSupport.ignore(log, e);
        } catch (InterruptedIOException e) {
            LogSupport.ignore(log, e);
        } catch (IOException e) {
            log.warn(LogSupport.EXCEPTION, e);
        }
        return null;
    }

    /* ------------------------------------------------------------------- */
    /**
     * Open the server socket. This method can be called to open the server socket in advance of
     * starting the listener. This can be used to test if the port is available.
     * 
     * @exception IOException if an error occurs
     */
    public void open() throws IOException {
        if (_listen == null) {
            _listen = newServerSocket(_address, _acceptQueueSize);

            if (_address == null)
                _address = new InetAddrPort(_listen.getInetAddress(), _listen.getLocalPort());
            else {
                if (_address.getInetAddress() == null)
                    _address.setInetAddress(_listen.getInetAddress());
                if (_address.getPort() == 0)
                    _address.setPort(_listen.getLocalPort());
            }

            _soTimeOut = getMaxIdleTimeMs();
            if (_soTimeOut >= 0)
                _listen.setSoTimeout(_soTimeOut);
        }
    }

    /* ------------------------------------------------------------------- */
    /*
     * Start the ThreadedServer listening
     */
    public synchronized void start() throws Exception {
        try {
            if (isStarted())
                return;

            open();

            _running = true;
            _acceptor = new Acceptor[_acceptors];
            for (int a = 0; a < _acceptor.length; a++) {
                _acceptor[a] = new Acceptor();
                _acceptor[a].setDaemon(isDaemon());
                _acceptor[a].start();
            }

            super.start();
        } catch (Exception e) {
            log.warn("Failed to start: " + this);
            throw e;
        }
    }

    /* --------------------------------------------------------------- */
    public void stop() throws InterruptedException {
        synchronized (this) {
            // Signal that we are stopping
            _running = false;

            // Close the listener socket.
            if (log.isDebugEnabled())
                log.debug("closing " + _listen);
            try {
                if (_listen != null)
                    _listen.close();
                _listen = null;
            } catch (IOException e) {
                log.warn(LogSupport.EXCEPTION, e);
            }

            // Do we have an acceptor thread (running or not)
            Thread.yield();
            for (int a = 0; _acceptor != null && a < _acceptor.length; a++) {
                Acceptor acc = _acceptor[a];
                if (acc != null)
                    acc.interrupt();
            }
            Thread.sleep(100);

            for (int a = 0; _acceptor != null && a < _acceptor.length; a++) {
                Acceptor acc = _acceptor[a];

                if (acc != null) {
                    acc.forceStop();
                    _acceptor[a] = null;
                }
            }
        }

        // Stop the thread pool
        try {
            super.stop();
        } catch (Exception e) {
            log.warn(LogSupport.EXCEPTION, e);
        } finally {
            synchronized (this) {
                _acceptor = null;
            }
        }
    }

    /* ------------------------------------------------------------ */
    /* ------------------------------------------------------------ */
    /**
     * Kill a job. This method closes IDLE and socket associated with a job
     * 
     * @param thread
     * @param job
     */
    protected void stopJob(Thread thread, Object job) {
        if (job instanceof Socket) {
            try {
                ((Socket) job).close();
            } catch (Exception e) {
                LogSupport.ignore(log, e);
            }
        }
        super.stopJob(thread, job);
    }

    /* ------------------------------------------------------------ */
    public String toString() {
        if (_address == null)
            return getName() + "@0.0.0.0:0";
        if (_listen != null)
            return getName() + "@" + _listen.getInetAddress().getHostAddress() + ":" + _listen.getLocalPort();
        return getName() + "@" + getInetAddrPort();
    }

    /* ------------------------------------------------------------ */
    /* ------------------------------------------------------------ */
    /* ------------------------------------------------------------ */
    private class Acceptor extends Thread {
        /* ------------------------------------------------------------ */
        public void run() {
            ThreadedServer threadedServer = ThreadedServer.this;
            try {
                this.setName("Acceptor " + _listen);
                while (_running) {
                    try {
                        // Accept a socket
                        Socket socket = acceptSocket(_soTimeOut);

                        // Handle the socket
                        if (socket != null) {
                            if (_running)
                                threadedServer.run(socket);
                            else
                                socket.close();
                        }
                    } catch (Throwable e) {
                        if (_running)
                            log.warn(LogSupport.EXCEPTION, e);
                        else
                            log.debug(LogSupport.EXCEPTION, e);
                    }
                }
            } finally {
                if (_running)
                    log.warn("Stopping " + this.getName());
                else
                    log.info("Stopping " + this.getName());
                synchronized (threadedServer) {
                    if (_acceptor != null) {
                        for (int a = 0; a < _acceptor.length; a++)
                            if (_acceptor[a] == this)
                                _acceptor[a] = null;
                    }
                    threadedServer.notifyAll();
                }
            }
        }

        /* ------------------------------------------------------------ */
        void forceStop() {
            if (_listen != null && _address != null) {
                InetAddress addr = _address.getInetAddress();
                try {
                    if (addr == null || addr.toString().startsWith("0.0.0.0"))
                        addr = InetAddress.getByName("127.0.0.1");
                    if (log.isDebugEnabled())
                        log.debug("Self connect to close listener " + addr + ":" + _address.getPort());
                    Socket socket = new Socket(addr, _address.getPort());
                    Thread.yield();
                    socket.close();
                    Thread.yield();
                } catch (IOException e) {
                    if (log.isDebugEnabled())
                        log.debug("problem stopping acceptor " + addr + ": ", e);
                }
            }
        }
    }

}