package net.i2p.util; import java.io.IOException; import java.io.PipedInputStream; import java.io.PipedOutputStream; import java.net.InetAddress; import java.net.ServerSocket; import java.net.Socket; import java.net.SocketAddress; import java.nio.channels.ServerSocketChannel; import java.util.concurrent.BlockingQueue; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.LinkedBlockingQueue; /** * A simple in-JVM ServerSocket using Piped Streams. * We use port numbers just like regular sockets. * Can only be connected by InternalSocket. * * Warning - this uses Piped Streams, which don't like multiple writers from threads * that may vanish. If you do use multipe writers, * you may get intermittent 'write end dead' or 'pipe broken' IOExceptions on the reader side. * See http://techtavern.wordpress.com/2008/07/16/whats-this-ioexception-write-end-dead/ * @since 0.7.9 */ public class InternalServerSocket extends ServerSocket { private static final ConcurrentHashMap<Integer, InternalServerSocket> _sockets = new ConcurrentHashMap<Integer, InternalServerSocket>(4); private final BlockingQueue<InternalSocket> _acceptQueue; private final Integer _port; private volatile boolean _running; //private static Log _log = I2PAppContext.getGlobalContext().logManager().getLog(InternalServerSocket.class); /** * @param port > 0 */ public InternalServerSocket(int port) throws IOException { if (port <= 0) throw new IOException("Bad port: " + port); _port = Integer.valueOf(port); InternalServerSocket previous = _sockets.putIfAbsent(_port, this); if (previous != null) throw new IOException("Internal port in use: " + port); _running = true; _acceptQueue = new LinkedBlockingQueue<InternalSocket>(); //if (_log.shouldLog(Log.DEBUG)) // _log.debug("Registered " + _port); } @Override public void close() { //if (_log.shouldLog(Log.DEBUG)) // _log.debug("Closing " + _port); _running = false; _sockets.remove(_port); _acceptQueue.clear(); try { // use null streams as a poison _acceptQueue.put(new InternalSocket(null, null)); } catch (InterruptedException ie) {} } @Override public Socket accept() throws IOException { InternalSocket serverSock = null; while (_running) { //if (_log.shouldLog(Log.DEBUG)) // _log.debug("Accepting " + _port); try { serverSock = _acceptQueue.take(); } catch (InterruptedException ie) { continue; } if (serverSock.getInputStream() == null) // poison throw new IOException("closed"); //if (_log.shouldLog(Log.DEBUG)) // _log.debug("Accepted " + _port); break; } return serverSock; } @Override public String toString() { return ("Internal server socket on port " + _port); } /** * This is how the client connects. * * @param port > 0 */ static void internalConnect(int port, InternalSocket clientSock) throws IOException { InternalServerSocket iss = _sockets.get(Integer.valueOf(port)); if (iss == null) throw new IOException("No server for port: " + port); PipedInputStream cis = BigPipedInputStream.getInstance(); PipedInputStream sis = BigPipedInputStream.getInstance(); PipedOutputStream cos = new PipedOutputStream(sis); PipedOutputStream sos = new PipedOutputStream(cis); clientSock.setInputStream(cis); clientSock.setOutputStream(cos); iss.queueConnection(new InternalSocket(sis, sos)); } private void queueConnection(InternalSocket sock) throws IOException { if (!_running) throw new IOException("Server closed for port: " + _port); //if (_log.shouldLog(Log.DEBUG)) // _log.debug("Queueing " + _port); try { _acceptQueue.put(sock); } catch (InterruptedException ie) {} } @Override public int getLocalPort() { return _port.intValue(); } // ignored stuff /** warning - unsupported */ @Override public void setSoTimeout(int timeout) {} @Override public int getSoTimeout () { return 0; } // everything below here unsupported /** @deprecated unsupported */ @Deprecated @Override public void bind(SocketAddress endpoint) { throw new IllegalArgumentException("unsupported"); } /** @deprecated unsupported */ @Deprecated @Override public void bind(SocketAddress endpoint, int backlog) { throw new IllegalArgumentException("unsupported"); } /** @deprecated unsupported */ @Deprecated @Override public ServerSocketChannel getChannel() { throw new IllegalArgumentException("unsupported"); } /** @deprecated unsupported */ @Deprecated @Override public InetAddress getInetAddress() { throw new IllegalArgumentException("unsupported"); } /** @deprecated unsupported */ @Deprecated @Override public SocketAddress getLocalSocketAddress() { throw new IllegalArgumentException("unsupported"); } /** @deprecated unsupported */ @Deprecated @Override public int getReceiveBufferSize() { throw new IllegalArgumentException("unsupported"); } /** @deprecated unsupported */ @Deprecated @Override public boolean getReuseAddress() { throw new IllegalArgumentException("unsupported"); } /** @deprecated unsupported */ @Deprecated @Override public boolean isBound() { throw new IllegalArgumentException("unsupported"); } /** @deprecated unsupported */ @Deprecated @Override public boolean isClosed() { throw new IllegalArgumentException("unsupported"); } /** @deprecated unsupported */ @Deprecated @Override public void setReceiveBufferSize(int size) { throw new IllegalArgumentException("unsupported"); } /** @deprecated unsupported */ @Deprecated @Override public void setReuseAddress(boolean on) { throw new IllegalArgumentException("unsupported"); } }