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();
}
}