All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.sapia.ubik.net.mplex.MultiplexServerSocket Maven / Gradle / Ivy

The newest version!
package org.sapia.ubik.net.mplex;

import java.io.IOException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;

import org.sapia.ubik.rmi.server.Log;


/**
 * This class is the main server socket that multiplexes the traditionnal
 * java.net.ServerSocket. That means that it listens on the port of the
 * server socket and it provides a mechanism to register different handlers for
 * these new incoming socket connections. These handlers are called socket connectors
 * and they can be used to process multiple types of data streams over the same socket.

* * For instance, using this MultiplexServerSocket you could handle serialized * Java objects and HTTP requests using two different connectors (two distinct handling * logic), same host/port. This is achieved through a read-ahead on the incoming stream of data * from a new client socket connection. When a new incoming connection is requested by a client, * all the registered StreamSelectors are used to determine which socket connector * will handle the new connection.

* * The internals of this MultiplexServerSocket are simple. An instance of this class * works on an asynchronous process were new socket connections are first accepted, and then selected. * These two steps are described as follows: *

    *
  1. acceptor: This first step involves the low-level logic of virtual machine that resides * under the Java I/O package. When a client connects to the server, a new MultiplexSocket * is created on the server-side; the acceptor adds that new connection/socket to the internal * pending queue. *
  2. *
  3. selector: The selector listens on the queue of accepted connections and performs * the logic of going through all the candidate socket connectors to determine which one of them * handles the new socket connection; if none of the registered connectors is. *
  4. *
* To perform this process, the MultiplexServerSocket contains two distinct pools of * threads that are configurable using the setAcceptorDaemonThread(int) and * setSelectorDaemonThread(int) methods.

* * The primary goal of the MultiplexServerSocket was to keep the "standard" (non-NIO) * programming model of the JDK regarding socket handling. Whether you use this server socket * directly or you use a socket connector, the logic is still the same for the client that * receives new incoming connections: it calls an accept() method that blocks * until a new connection is made. For integration with actual running systems, an adapter * is available to make a socket connector look like a server socket. We used that strategy * with the open-source Simple HTTP server and it worked transparently and fluidly. * * @see MultiplexSocket * @see MultiplexSocketConnector * @see ServerSocketAdapter * @see StreamSelector * @author Jean-Cedric Desrochers *

*
Copyright:
Copyright © 2002-2004 * Sapia Open Source Software. All Rights Reserved.
*
License:
Read the license.txt file of the jar or visit the * license page * at the Sapia OSS web site
*
*/ public class MultiplexServerSocket extends ServerSocket implements Runnable { /** * The default number of bytes read ahead fom incoming socket connections. */ public static final short DEFAULT_READ_AHEAD_BUFFER_SIZE = 64; /** * The default number of acceptor daemon thread used to accept connections. */ public static final short DEFAULT_ACCEPTOR_DAEMON_THREAD = 3; /** * The default number of selector daemon thread used to accept connections. */ public static final short DEFAULT_SELECTOR_DAEMON_THREAD = 3; /** The list of created connectors that can handle new connections on this server. */ private List _theConnectors = new ArrayList(); /** The default connector that handles incoming connections. */ private SocketConnectorImpl _theDefaultConnector; /** The list of running acceptor daemon threads. */ private List _theAcceptorDaemons = new ArrayList(); /** The list of running selector daemon threads. */ private List _theSelectorDaemons = new ArrayList(); /** The socket queue of accepted connection. */ private SocketQueue _theAcceptedQueue = new SocketQueue(); /** The number of acceptor daemon threads of this server. */ private int _theAcceptorDaemonThread = DEFAULT_ACCEPTOR_DAEMON_THREAD; /** The number of selector daemon threads of this server. */ private int _theSelectorDaemonThread = DEFAULT_SELECTOR_DAEMON_THREAD; /** The number of bytes read ahead when accepting a new connection. */ private int _theReadAheadBufferSize = DEFAULT_READ_AHEAD_BUFFER_SIZE; /** * Creates a new MultiplexServerSocket instance. The server * socket is unbound * * @throws IOException If an error occurs opening the socket. */ public MultiplexServerSocket() throws IOException { super(); } /** * Creates a new MultiplexServerSocket instance. The new server will * be bound on the speficied port, or to a free port if the value passed * in is 0 (zero). * * The maximum queue length for incoming connection indications (a * request to connect) is set to 50. If a connection * indication arrives when the queue is full, the connection is refused. * * @param port The port number to bind the server or 0 to use any port. * @throws IOException If an error occurs when opening the socket. */ public MultiplexServerSocket(int port) throws IOException { super(port, 50); } /** * Creates a new MultiplexServerSocket instance. The new server will * be bound on the speficied port, or to a free port if the value passed * in is 0 (zero). * * The maximum queue length for incoming connection indications (a * request to connect) is set to the backlog parameter. If * a connection indication arrives when the queue is full, the * connection is refused. * * @param port The port number to bind the server or 0 oi use any port. * @param backlog The maximum length of the queue. * @throws IOException If an error occurs when opening the socket. */ public MultiplexServerSocket(int port, int backlog) throws IOException { super(port, backlog); } /** * Creates a new MultiplexServerSocket instance. The new server will * be bound on the speficied port, or to a free port if the value passed * in is 0 (zero). The server will use the local IP address represented * by the bindAddr passed in. * * The maximum queue length for incoming connection indications (a * request to connect) is set to the backlog parameter. If * a connection indication arrives when the queue is full, the * connection is refused. * * @param port The port number to bind the server or 0 oi use any port. * @param backlog The maximum length of the queue. * @param bindAddr The local TCP address the server will bind to. If null * the server will accept connections on any/all local addresses. * @throws IOException If an error occurs when opening the socket. */ public MultiplexServerSocket(int port, int backlog, InetAddress bindAddr) throws IOException { super(port, backlog, bindAddr); } /** * Returns the size of the buffer used to pre-read the incoming bytes of the * accepted connection. * * @return The size of the read ahead buffer size. */ public int getReadAheadBufferSize() { return _theReadAheadBufferSize; } /** * Changes the size of the read ahead buffer size. This method can only be called before starting * the server (ie. the initial call to the method accept(). * * @param aSize The new size. * @exception IllegalStateException If the server is already running. */ public void setReadAheadBufferSize(int aSize) { if (aSize <= 0) { throw new IllegalArgumentException("The size is less than zero"); } else if (_theAcceptorDaemons.size() > 0) { throw new IllegalStateException( "Cannot change the read ahead buffer size on a running server socket"); } _theReadAheadBufferSize = aSize; } /** * Returns the number of daemon threads used to accept incoming connections. * * @return The number of daemon threads used to accept incoming connections. */ public int getAcceptorDaemonThread() { return _theAcceptorDaemonThread; } /** * Returns the number of daemon threads used to select an connector for incoming connections. * * @return The number of daemon threads used to select an connector for incoming connections. */ public int getSelectorDaemonThread() { return _theSelectorDaemonThread; } /** * Changes the number of daemon threads used to accept incoming connections. * * @param maxThread The new numbe of running daemon. * @exception IllegalStateException If the server is already running. */ public void setAcceptorDaemonThread(int maxThread) { if (maxThread <= 0) { throw new IllegalArgumentException("The size is less than zero"); } else if (_theAcceptorDaemons.size() > 0) { throw new IllegalStateException( "Cannot change the number of acceptor daemons on a running server socket"); } _theAcceptorDaemonThread = maxThread; } /** * Changes the number of daemon threads used to select connectors for incoming connections. * * @param maxThread The new numbe of running daemon. * @exception IllegalStateException If the server is already running. */ public void setSelectorDaemonThread(int maxThread) { if (maxThread <= 0) { throw new IllegalArgumentException("The size is less than zero"); } else if (_theSelectorDaemons.size() > 0) { throw new IllegalStateException( "Cannot change the number of selector daemons on a running server socket"); } _theSelectorDaemonThread = maxThread; } /** * This factory method creates a socket connector through which a client will be able * to receive incoming socket connections. The stream selector passed in will be used * y this multiplex server socket to determine if an incoming socket connection must * be handled by the created socket connector. * * @param aSelector The stream selector to assign to the created socket connector. * @return The created socket connector. * @throws IllegalStateException If this server socket is closed. */ public synchronized MultiplexSocketConnector createSocketConnector( StreamSelector aSelector) { if (aSelector == null) { throw new IllegalArgumentException("The selector passed in is null"); } else if (isClosed()) { throw new IllegalStateException( "Could not create a socket connector, the server socket is closed"); } MultiplexSocketConnector aConnector = new SocketConnectorImpl(this, aSelector, new SocketQueue()); _theConnectors.add(aConnector); return aConnector; } /** * Internal method that initializes the default connector that gets all the incoming socket * connections. It also creates all the acceptor and selector thread pools according to the * configuration. */ private synchronized void initializeDefaultConnector() { if (_theDefaultConnector == null) { // Create the default handler _theDefaultConnector = new SocketConnectorImpl(this, new PositiveStreamSelector(), new SocketQueue()); // Create the selector daemons for (int i = 1; i <= _theAcceptorDaemonThread; i++) { Thread aDaemon = new Thread(new SelectorTask(), "MultiplexServerSocket-Selector" + i); aDaemon.setDaemon(true); _theSelectorDaemons.add(aDaemon); aDaemon.start(); } // Create the accceptor daemons for (int i = 1; i <= _theAcceptorDaemonThread; i++) { Thread aDaemon = new Thread(this, "MultiplexServerSocket-Acceptor" + i); aDaemon.setDaemon(true); _theAcceptorDaemons.add(aDaemon); aDaemon.start(); } } } /** * Removes the socket connector passed in from the list of available connectors * associated with this server socket. After this method the multiplex server socket * will no longer send new incoming socket connection to the socket connector. * * @param aConnector The socket connector to remove from this server socket. */ public synchronized void removeSocketConnector( MultiplexSocketConnector aConnector) { if (aConnector == null) { throw new IllegalArgumentException("The connector passed in is null"); } _theConnectors.remove(aConnector); } /** * Extract the first bytes of the multiplex socket passed. * * @param aSocket The socket from which to get the data. * @param aMaxLength The maximum number of bytes to extract. * @return The array of bytes representingthe header. * @throws IOException If an error occurs extracting the header. */ private byte[] extractHeader(MultiplexSocket aSocket, int aMaxLength) throws IOException { // Extract the first bytes of the input stream byte[] preview = new byte[aMaxLength]; int length = aSocket.getPushbackInputStream().read(preview, 0, preview.length); // Put back the headers in the stream aSocket.getPushbackInputStream().unread(preview, 0, length); // Trim the array of bytes byte[] header; if (length < preview.length) { header = new byte[length]; System.arraycopy(preview, 0, header, 0, length); } else { header = preview; } return header; } /** * Listens for a connection to be made to this socket and accepts it. The default * connector of this server socket will be used to act as the main port of entry * for clients directly using the server socket instead of the default socket * connector. The method blocks until a connection is made. * * @return The new Socket * @exception IOException If an I/O error occurs when waiting for a connection. * @exception SocketTimeoutException if a timeout was previously set with setSoTimeout and * the timeout has been reached. */ public Socket accept() throws IOException { if (isClosed()) { throw new SocketException("Socket is closed"); } else if (!isBound()) { throw new SocketException("Socket is not bound yet"); } else if (_theDefaultConnector == null) { initializeDefaultConnector(); } return _theDefaultConnector.getQueue().getSocket(); } /** * Closes this multiplex server socket. If socket connectors are associated to * this server scket then they are closed as well. * * @exception IOException If an I/O error occurs when closing the socket. */ public synchronized void close() throws IOException { try { super.close(); } finally { if (_theDefaultConnector != null) { _theDefaultConnector.close(); } // May close the acceptor and selector threads here!!! if (_theConnectors != null) { // To avoid concurrent modifs when removing for (MultiplexSocketConnector connector: new ArrayList(_theConnectors)) { connector.close(); } } } } /** * Returns the implementation address and implementation port of * this socket as a String. * * @return A string representation of this socket. */ public String toString() { StringBuffer aBuffer = new StringBuffer(); aBuffer.append("MultiplexServerSocket[").append(super.toString()).append("]"); return aBuffer.toString(); } /** * Implements the Runnable interface and it performs the asynchronous acceptor logic * of the multiplex. Used by the acceptor threads, it listens on the underlying * server socket and dispacthes all the incoming socket connections to the queue * of accepted socket connection. The client socket connections put in that queue are * then processed asynchronously by the selector threads. */ public void run() { try { Log.warning(getClass(), new Date() + " [" + Thread.currentThread().getName() + "] MultiplexServerSocket * REPORT * Starting this acceptor thread"); // Loop for accepting incoming client socket connection while (!isClosed() && !Thread.interrupted()) { try { // Wait for a connection MultiplexSocket aClient = new MultiplexSocket(null, _theReadAheadBufferSize); implAccept(aClient); _theAcceptedQueue.add(aClient); } catch (IOException ioe) { _theDefaultConnector.getQueue().setException(ioe); } catch (RuntimeException re) { _theDefaultConnector.getQueue().setException(new IOException(re.getLocalizedMessage())); } } } catch (Exception e) { Log.error(getClass(), new Date() + " [" + Thread.currentThread().getName() + "] MultiplexServerSocket * ERROR * An unhandled exception occured in this acceptor thread... EXITING LOOP", e); } finally { Log.warning(getClass(), new Date() + " [" + Thread.currentThread().getName() + "] MultiplexServerSocket * REPORT * Stopping this acceptor thread"); } } /** * This utility method is used to select the socket connector that requests the new * client socket connection passed in. Note that this method will only use as candidate * selectors the one that are registered with this server socket (and not the default one). * * @param aClient The client socket connection for which to select a connector. * @return The first connector that selects the socket connection, or null * if no connector selects the client socket r if there is no registered connector. * @exception IOException If an error occurs selecting a connector for the socket. */ private SocketConnectorImpl selectConnector(MultiplexSocket aClient) throws IOException { SocketConnectorImpl aConnector = null; if (_theConnectors.size() > 0) { // Extract the first bytes of the socket input stream byte[] header = extractHeader(aClient, _theReadAheadBufferSize); // Select the right connector synchronized (this) { for (Iterator it = _theConnectors.iterator(); it.hasNext();) { SocketConnectorImpl aCandidate = (SocketConnectorImpl) it.next(); if (aCandidate.getSelector().selectStream(header)) { aConnector = aCandidate; break; } } } } return aConnector; } /** * This class is responsible of the selection logic of the multiplex. */ public class SelectorTask implements Runnable { /** * This method waits on the queue of accepted client socket connection * and will processes asynchonously, using the selector threads, to the election * of the appropriate socket selector for the new accepted socket connection. */ public void run() { try { Log.warning(getClass(), new Date() + " [" + Thread.currentThread().getName() + "] MultiplexServerSocket * REPORT * Starting this selector thread"); // Loop for selecting new client socket connections while (!isClosed() && !Thread.interrupted()) { MultiplexSocket aSocket; SocketConnectorImpl aConnector; try { // Get the next accepted client socket aSocket = (MultiplexSocket) _theAcceptedQueue.getSocket(); // Selects a registered connector aConnector = selectConnector(aSocket); // If no connector selected, use the default one if (aConnector == null) { _theDefaultConnector.getQueue().add(aSocket); } else { aConnector.getQueue().add(aSocket); } } catch (IOException ioe) { _theDefaultConnector.getQueue().setException(ioe); } catch (RuntimeException re) { _theDefaultConnector.getQueue().setException(new IOException(re.getLocalizedMessage())); } } } catch (Exception e) { Log.error(new Date() + " [" + Thread.currentThread().getName() + "] MultiplexServerSocket * ERROR * An unhandled exception occured in this selector thread... EXITING LOOP", e); } finally { Log.warning(getClass(), new Date() + " [" + Thread.currentThread().getName() + "] MultiplexServerSocket * REPORT * Stopping this selector thread"); } } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy