net.lightbody.bmp.proxy.jetty.util.ThreadedServer Maven / Gradle / Ivy
// ========================================================================
// $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
*
* The class is abstract and derived classes must provide the handling for the connections.
*
* The properties THREADED_SERVER_MIN_THREADS and THREADED_SERVER_MAX_THREADS can be set to control
* the number of threads created.
*
*
* @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);
}
}
}
}
}