package com.limegroup.gnutella.io; import java.io.IOException; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; import java.net.InetAddress; import java.net.Socket; import java.net.InetSocketAddress; import java.net.SocketException; import java.net.SocketTimeoutException; import java.net.SocketAddress; import java.net.ServerSocket; import java.util.List; import java.util.LinkedList; import com.util.LOG; /** * A ServerSocket that does all of its accepting using NIO, but psuedo-blocks. * * Phase-1 in converting to NIO. */ public class NIOServerSocket extends ServerSocket implements AcceptHandler { private final ServerSocketChannel channel; private final ServerSocket socket; private final List pendingSockets = new LinkedList(); private IOException storedException = null; private final Object LOCK = new Object(); /** * Constructs a new, unbound, NIOServerSocket. * You must call 'bind' to start listening for incoming connections. */ public NIOServerSocket() throws IOException { channel = ServerSocketChannel.open(); socket = channel.socket(); init(); } /** Constructs a new NIOServerSocket bound to the given port */ public NIOServerSocket(int port) throws IOException { channel = ServerSocketChannel.open(); socket = channel.socket(); init(); bind(new InetSocketAddress(port)); } /** * Constructs a new NIOServerSocket bound to the given port, able to accept * the given backlog of connections. */ public NIOServerSocket(int port, int backlog) throws IOException { channel = ServerSocketChannel.open(); socket = channel.socket(); init(); bind(new InetSocketAddress(port), backlog); } /** * Constructs a new NIOServerSocket bound to the given port & addr, able to accept * the given backlog of connections. */ public NIOServerSocket(int port, int backlog, InetAddress bindAddr) throws IOException { channel = ServerSocketChannel.open(); socket = channel.socket(); init(); bind(new InetSocketAddress(bindAddr, port), backlog); } /** * Initializes the connection. * Currently this sets the channel to blocking & reuse addr to false. */ private void init() throws IOException { channel.configureBlocking(false); socket.setReuseAddress(false); } /** * Accepts an incoming connection. */ public Socket accept() throws IOException { synchronized(LOCK){ boolean looped = false; int timeout = getSoTimeout(); while(!isClosed() && isBound() && storedException == null && pendingSockets.isEmpty()) { if(looped && timeout != 0) throw new SocketTimeoutException("accept timed out: " + timeout); LOG.debug("Waiting for incoming socket..."); try { LOCK.wait(timeout); } catch(InterruptedException ix) { throw new InterruptedIOException(ix); } looped = true; } IOException x = storedException; storedException = null; if(x != null) throw x; else if(isClosed()) throw new SocketException("Socket Closed"); else if(!isBound()) throw new SocketException("Not Bound!"); else { LOG.debug("Retrieved a socket!"); return new NIOSocket((Socket)pendingSockets.remove(0)); } } } /** * Notification that a socket has been accepted. */ public void handleAccept(SocketChannel channel) { synchronized(LOCK) { pendingSockets.add(channel.socket()); LOCK.notify(); } } /** * Notification that an IOException occurred while accepting. */ public void handleIOException(IOException iox) { synchronized(LOCK) { storedException = iox; } } /** Binds the socket to the endpoint & starts listening for incoming connections */ public void bind(SocketAddress endpoint) throws IOException { socket.bind(endpoint); NIODispatcher.instance().registerAccept(channel, this); } /** Binds the socket to the endpoint & starts listening for incoming connections */ public void bind(SocketAddress endpoint, int backlog) throws IOException { socket.bind(endpoint, backlog); NIODispatcher.instance().registerAccept(channel, this); } /** Shuts down this NIOServerSocket */ public void close() throws IOException { synchronized(LOCK) { LOCK.notify(); socket.close(); } } ///////////////////////////////////////////////////////////// /////////// Below are simple wrappers for the socket. ///////////////////////////////////////////////////////////// public ServerSocketChannel getChannel() { return socket.getChannel(); } public InetAddress getInetAddress() { return socket.getInetAddress(); } public int getLocalPort() { return socket.getLocalPort(); } public SocketAddress getLocalSocketAddress() { return socket.getLocalSocketAddress(); } public int getReceiveBufferSize() throws SocketException { return socket.getReceiveBufferSize(); } public boolean getReuseAddress() throws SocketException { return socket.getReuseAddress(); } public int getSoTimeout() throws IOException { return socket.getSoTimeout(); } public boolean isBound() { return socket.isBound(); } public boolean isClosed() { return socket.isClosed(); } public void setReceiveBufferSize(int size) throws SocketException { socket.setReceiveBufferSize(size); } public void setReuseAddress(boolean on) throws SocketException { socket.setReuseAddress(on); } public void setSoTimeout(int timeout) throws SocketException { socket.setSoTimeout(timeout); } public String toString() { return "NIOServerSocket::" + socket.toString(); } }