Java tutorial
/* * $Header: /home/cvs/jakarta-tomcat-connectors/util/java/org/apache/tomcat/util/net/PoolTcpEndpoint.java,v 1.31 2004/01/13 10:16:38 remm Exp $ * $Revision: 1.31 $ * $Date: 2004/01/13 10:16:38 $ * * ==================================================================== * * The Apache Software License, Version 1.1 * * Copyright (c) 1999 The Apache Software Foundation. All rights * reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * 3. The end-user documentation included with the redistribution, if * any, must include the following acknowlegement: * "This product includes software developed by the * Apache Software Foundation (http://www.apache.org/)." * Alternately, this acknowlegement may appear in the software itself, * if and wherever such third-party acknowlegements normally appear. * * 4. The names "The Jakarta Project", "Tomcat", and "Apache Software * Foundation" must not be used to endorse or promote products derived * from this software without prior written permission. For written * permission, please contact apache@apache.org. * * 5. Products derived from this software may not be called "Apache" * nor may "Apache" appear in their names without prior written * permission of the Apache Group. * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * ==================================================================== * * This software consists of voluntary contributions made by many * individuals on behalf of the Apache Software Foundation. For more * information on the Apache Software Foundation, please see * <http://www.apache.org/>. * * [Additional notices, if required by prior licensing conditions] * */ package org.apache.tomcat.util.net; import java.io.IOException; import java.io.InterruptedIOException; import java.net.BindException; import java.net.InetAddress; import java.net.ServerSocket; import java.net.Socket; import java.net.SocketException; import java.security.AccessControlException; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.tomcat.util.collections.SimplePool; import org.apache.tomcat.util.res.StringManager; import org.apache.tomcat.util.threads.ThreadPool; import org.apache.tomcat.util.threads.ThreadPoolRunnable; /* Similar with MPM module in Apache2.0. Handles all the details related with "tcp server" functionality - thread management, accept policy, etc. It should do nothing more - as soon as it get a socket ( and all socket options are set, etc), it just handle the stream to ConnectionHandler.processConnection. (costin) */ /** * Handle incoming TCP connections. * * This class implement a simple server model: one listener thread accepts on a socket and * creates a new worker thread for each incoming connection. * * More advanced Endpoints will reuse the threads, use queues, etc. * * @author James Duncan Davidson [duncan@eng.sun.com] * @author Jason Hunter [jch@eng.sun.com] * @author James Todd [gonzo@eng.sun.com] * @author Costin@eng.sun.com * @author Gal Shachor [shachor@il.ibm.com] */ public class PoolTcpEndpoint { // implements Endpoint { private StringManager sm = StringManager.getManager("org.apache.tomcat.util.net.res"); private static final int BACKLOG = 100; private static final int TIMEOUT = 1000; private final Object threadSync = new Object(); private boolean isPool = true; private int backlog = BACKLOG; private int serverTimeout = TIMEOUT; TcpConnectionHandler handler; private InetAddress inet; private int port; private ServerSocketFactory factory; private ServerSocket serverSocket; ThreadPoolRunnable listener; private volatile boolean running = false; private boolean initialized = false; private boolean reinitializing = false; static final int debug = 0; ThreadPool tp; // XXX Do we need it for backward compat ? //protected Log _log=Log.getLog("tc/PoolTcpEndpoint", "PoolTcpEndpoint"); static Log log = LogFactory.getLog(PoolTcpEndpoint.class); protected boolean tcpNoDelay = false; protected int linger = 100; protected int socketTimeout = -1; public PoolTcpEndpoint() { // super("tc_log"); // initialize default logger tp = new ThreadPool(); } public PoolTcpEndpoint(ThreadPool tp) { this.tp = tp; } // -------------------- Configuration -------------------- public void setPoolOn(boolean isPool) { this.isPool = isPool; } public boolean isPoolOn() { return isPool; } public void setMaxThreads(int maxThreads) { if (maxThreads > 0) tp.setMaxThreads(maxThreads); } public int getMaxThreads() { return tp.getMaxThreads(); } public void setMaxSpareThreads(int maxThreads) { if (maxThreads > 0) tp.setMaxSpareThreads(maxThreads); } public int getMaxSpareThreads() { return tp.getMaxSpareThreads(); } public void setMinSpareThreads(int minThreads) { if (minThreads > 0) tp.setMinSpareThreads(minThreads); } public int getMinSpareThreads() { return tp.getMinSpareThreads(); } public int getPort() { return port; } public void setPort(int port) { this.port = port; } public InetAddress getAddress() { return inet; } public void setAddress(InetAddress inet) { this.inet = inet; } public void setServerSocket(ServerSocket ss) { serverSocket = ss; } public void setServerSocketFactory(ServerSocketFactory factory) { this.factory = factory; } ServerSocketFactory getServerSocketFactory() { return factory; } public void setConnectionHandler(TcpConnectionHandler handler) { this.handler = handler; } public TcpConnectionHandler getConnectionHandler() { return handler; } public boolean isRunning() { return running; } /** * Allows the server developer to specify the backlog that * should be used for server sockets. By default, this value * is 100. */ public void setBacklog(int backlog) { if (backlog > 0) this.backlog = backlog; } public int getBacklog() { return backlog; } /** * Sets the timeout in ms of the server sockets created by this * server. This method allows the developer to make servers * more or less responsive to having their server sockets * shut down. * * <p>By default this value is 1000ms. */ public void setServerTimeout(int timeout) { this.serverTimeout = timeout; } public boolean getTcpNoDelay() { return tcpNoDelay; } public void setTcpNoDelay(boolean b) { tcpNoDelay = b; } public int getSoLinger() { return linger; } public void setSoLinger(int i) { linger = i; } public int getSoTimeout() { return socketTimeout; } public void setSoTimeout(int i) { socketTimeout = i; } public int getServerSoTimeout() { return serverTimeout; } public void setServerSoTimeout(int i) { serverTimeout = i; } // -------------------- Public methods -------------------- public void initEndpoint() throws IOException, InstantiationException { try { if (factory == null) factory = ServerSocketFactory.getDefault(); if (serverSocket == null) { try { if (inet == null) { serverSocket = factory.createSocket(port, backlog); } else { serverSocket = factory.createSocket(port, backlog, inet); } } catch (BindException be) { throw new BindException(be.getMessage() + ":" + port); } } if (serverTimeout >= 0) serverSocket.setSoTimeout(serverTimeout); } catch (IOException ex) { // log("couldn't start endpoint", ex, Logger.DEBUG); throw ex; } catch (InstantiationException ex1) { // log("couldn't start endpoint", ex1, Logger.DEBUG); throw ex1; } initialized = true; } public void startEndpoint() throws IOException, InstantiationException { if (!initialized) { initEndpoint(); } if (isPool) { tp.start(); } running = true; if (isPool) { listener = new TcpWorkerThread(this); tp.runIt(listener); } else { log.error("XXX Error - need pool !"); } } public void stopEndpoint() { if (running) { tp.shutdown(); running = false; if (serverSocket != null) { closeServerSocket(); } } } protected void closeServerSocket() { Socket s = null; try { // Need to create a connection to unlock the accept(); if (inet == null) { s = new Socket("127.0.0.1", port); } else { s = new Socket(inet, port); // setting soLinger to a small value will help shutdown the // connection quicker s.setSoLinger(true, 0); } } catch (Exception e) { log.error("Caught exception trying to unlock accept on " + port + " " + e.toString()); } finally { if (s != null) { try { s.close(); } catch (Exception e) { // Ignore } } } try { if (serverSocket != null) serverSocket.close(); } catch (Exception e) { log.error("Caught exception trying to close socket.", e); } serverSocket = null; } // -------------------- Private methods Socket acceptSocket() { if (!running || serverSocket == null) return null; Socket accepted = null; try { if (factory == null) { accepted = serverSocket.accept(); } else { accepted = factory.acceptSocket(serverSocket); } if (null == accepted) { log.warn("Null socket returned by accept"); } else { if (!running) { accepted.close(); // rude, but unlikely! accepted = null; } else if (factory != null) { factory.initSocket(accepted); } } } catch (InterruptedIOException iioe) { // normal part -- should happen regularly so // that the endpoint can release if the server // is shutdown. } catch (AccessControlException ace) { // When using the Java SecurityManager this exception // can be thrown if you are restricting access to the // socket with SocketPermission's. // Log the unauthorized access and continue String msg = sm.getString("endpoint.warn.security", serverSocket, ace); log.warn(msg); } catch (IOException e) { String msg = null; if (running) { msg = sm.getString("endpoint.err.nonfatal", serverSocket, e); log.error(msg, e); } if (accepted != null) { try { accepted.close(); } catch (Throwable ex) { msg = sm.getString("endpoint.err.nonfatal", accepted, ex); log.warn(msg, ex); } accepted = null; } if (!running) return null; reinitializing = true; // Restart endpoint when getting an IOException during accept synchronized (threadSync) { if (reinitializing) { reinitializing = false; // 1) Attempt to close server socket closeServerSocket(); initialized = false; // 2) Reinit endpoint (recreate server socket) try { msg = sm.getString("endpoint.warn.reinit"); log.warn(msg); initEndpoint(); } catch (Throwable t) { msg = sm.getString("endpoint.err.nonfatal", serverSocket, t); log.error(msg, t); } // 3) If failed, attempt to restart endpoint if (!initialized) { msg = sm.getString("endpoint.warn.restart"); log.warn(msg); try { stopEndpoint(); initEndpoint(); startEndpoint(); } catch (Throwable t) { msg = sm.getString("endpoint.err.fatal", serverSocket, t); log.error(msg, t); } finally { // Current thread is now invalid: kill it throw new ThreadDeath(); } } } } } return accepted; } /** @deprecated */ public void log(String msg) { log.info(msg); } /** @deprecated */ public void log(String msg, Throwable t) { log.error(msg, t); } /** @deprecated */ public void log(String msg, int level) { log.info(msg); } /** @deprecated */ public void log(String msg, Throwable t, int level) { log.error(msg, t); } void setSocketOptions(Socket socket) throws SocketException { if (linger >= 0) socket.setSoLinger(true, linger); if (tcpNoDelay) socket.setTcpNoDelay(tcpNoDelay); if (socketTimeout > 0) socket.setSoTimeout(socketTimeout); } } // -------------------- Threads -------------------- /* * I switched the threading model here. * * We used to have a "listener" thread and a "connection" * thread, this results in code simplicity but also a needless * thread switch. * * Instead I am now using a pool of threads, all the threads are * simmetric in their execution and no thread switch is needed. */ class TcpWorkerThread implements ThreadPoolRunnable { /* This is not a normal Runnable - it gets attached to an existing thread, runs and when run() ends - the thread keeps running. It's better to keep the name ThreadPoolRunnable - avoid confusion. We also want to use per/thread data and avoid sync wherever possible. */ PoolTcpEndpoint endpoint; public TcpWorkerThread(PoolTcpEndpoint endpoint) { this.endpoint = endpoint; } public Object[] getInitData() { // no synchronization overhead, but 2 array access Object obj[] = new Object[2]; obj[1] = endpoint.getConnectionHandler().init(); obj[0] = new TcpConnection(); return obj; } public void runIt(Object perThrData[]) { // Create per-thread cache if (endpoint.isRunning()) { Socket s = null; try { s = endpoint.acceptSocket(); } finally { // Continue accepting on another thread... if (endpoint.isRunning()) { endpoint.tp.runIt(this); } } if (null != s) { TcpConnection con = null; int step = 1; try { // 1: Set socket options: timeout, linger, etc endpoint.setSocketOptions(s); // 2: SSL handshake step = 2; if (endpoint.getServerSocketFactory() != null) { endpoint.getServerSocketFactory().handshake(s); } // 3: Process the connection step = 3; con = (TcpConnection) perThrData[0]; con.setEndpoint(endpoint); con.setSocket(s); endpoint.getConnectionHandler().processConnection(con, (Object[]) perThrData[1]); } catch (SocketException se) { endpoint.log .error("Remote Host " + s.getInetAddress() + " SocketException: " + se.getMessage()); // Try to close the socket try { s.close(); } catch (IOException e) { } } catch (Throwable t) { if (step == 2) { endpoint.log.debug("Handshake failed", t); } else { endpoint.log.error("Unexpected error", t); } // Try to close the socket try { s.close(); } catch (IOException e) { } } finally { if (con != null) { con.recycle(); } } } } } }