package org.limewire.nio; import java.io.IOException; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.Socket; import java.net.SocketAddress; import java.net.SocketException; import java.net.UnknownHostException; import java.nio.channels.SocketChannel; import java.nio.channels.UnsupportedAddressTypeException; import org.limewire.nio.channel.InterestReadableByteChannel; import org.limewire.nio.channel.InterestWritableByteChannel; import org.limewire.nio.observer.ConnectObserver; /** * A {@link Socket} that does all of its connecting/reading/writing using NIO. * <p> * Input/OutputStreams are provided for blocking I/O (although * internally non-blocking I/O is used). To use event-based reads, * use {@link #setReadObserver(org.limewire.nio.channel.ChannelReadObserver)} * and read-events are passed to the {@link ReadObserver}. * <p> * A {@link ChannelReadObserver} must be used so the <code>Socket</code> can * set the appropriate underlying channel. */ public class NIOSocket extends AbstractNBSocket { /** The underlying channel the socket is using. */ private final SocketChannel channel; /** The Socket that this delegates to. */ private final Socket socket; /** The remote socket address. */ private volatile SocketAddress remoteSocketAddress; /** * Constructs an <code>NIOSocket</code> using a pre-existing <code>Socket</code>. * To be used by <code>NIOServerSocket</code> while accepting incoming connections. */ protected NIOSocket(Socket s) { channel = s.getChannel(); socket = channel.socket(); remoteSocketAddress = s.getRemoteSocketAddress(); initIncomingSocket(); setInitialReader(); setInitialWriter(); NIODispatcher.instance().register(channel, this); } /** Creates an unconnected <code>NIOSocket</code>. */ public NIOSocket() throws IOException { channel = SocketChannel.open(); socket = channel.socket(); initOutgoingSocket(); setInitialReader(); setInitialWriter(); } /** Creates an <code>NIOSocket</code> and connects (with no timeout) to addr/port. */ public NIOSocket(InetAddress addr, int port) throws IOException { channel = SocketChannel.open(); socket = channel.socket(); initOutgoingSocket(); setInitialReader(); setInitialWriter(); connect(new InetSocketAddress(addr, port)); } /** Creates an <code>NIOSocket</code> locally bound to localAddr/localPort * and connects (with no timeout) to addr/port. */ public NIOSocket(InetAddress addr, int port, InetAddress localAddr, int localPort) throws IOException { channel = SocketChannel.open(); socket = channel.socket(); initOutgoingSocket(); setInitialReader(); setInitialWriter(); bind(new InetSocketAddress(localAddr, localPort)); connect(new InetSocketAddress(addr, port)); } /** Creates an <code>NIOSocket</code> and connects (with no timeout) to addr/port. */ public NIOSocket(String addr, int port) throws UnknownHostException, IOException { this(InetAddress.getByName(addr), port); } /** Creates an <code>NIOSocket</code> locally bound to localAddr/localPort * and connects (with no timeout) to addr/port. */ public NIOSocket(String addr, int port, InetAddress localAddr, int localPort) throws IOException { this(InetAddress.getByName(addr), port, localAddr, localPort); } /** Performs initialization for an incoming Socket. Doesn't do anything right now. */ protected void initIncomingSocket() { } /** Performs initialization for this <code>NIOSocket</code>. Currently just * makes the channel non-blocking. */ protected void initOutgoingSocket() throws IOException { channel.configureBlocking(false); } /** Binds the socket to the <code>SocketAddress</code>. */ @Override public void bind(SocketAddress endpoint) throws IOException { socket.bind(endpoint); } /** Stores the connecting address so we can retrieve it later. */ @Override public boolean connect(SocketAddress addr, int timeout, ConnectObserver observer) { remoteSocketAddress = addr; return super.connect(addr, timeout, observer); } @Override public SocketAddress getRemoteSocketAddress() { return remoteSocketAddress; } @Override public InetAddress getInetAddress() { //The separate variable for storage is necessary because Sockets created //with SocketChannel.open() return null when there's no connection. if(remoteSocketAddress != null) return ((InetSocketAddress)remoteSocketAddress).getAddress(); else return null; } @Override public int getPort() { //The separate variable for storage is necessary because Sockets created //with SocketChannel.open() return null when there's no connection. if(remoteSocketAddress != null) return ((InetSocketAddress)remoteSocketAddress).getPort(); else return 0; } /** Constructs an InterestReadChannel adapter around the SocketChannel. */ @Override protected InterestReadableByteChannel getBaseReadChannel() { return new SocketInterestReadAdapter(channel); } /** Constructs an InterestWriteChannel adapter around the SocketChannel. */ @Override protected InterestWritableByteChannel getBaseWriteChannel() { return new SocketInterestWriteAdapter(channel); } /** Shuts down input, output and the socket. */ @Override protected void shutdownImpl() { try { shutdownInput(); } catch (IOException ignored) { } try { shutdownOutput(); } catch (IOException ignored) { } try { socket.close(); } catch (IOException ignored) { } catch (Error ignored) { } } // ///////////////////////////////////////////// // / BELOW ARE ALL WRAPPERS FOR SOCKET. // ///////////////////////////////////////////// @Override public SocketChannel getChannel() { return channel; } @Override public int getLocalPort() { return socket.getLocalPort(); } @Override public SocketAddress getLocalSocketAddress() { return socket.getLocalSocketAddress(); } @Override public InetAddress getLocalAddress() { try { return socket.getLocalAddress(); } catch (Error osxSucks) { // On OSX 10.3 w/ Java 1.4.2_05, if the connection dies // prior to this method being called, an Error is thrown. try { return InetAddress.getLocalHost(); } catch (UnknownHostException uhe) { return null; } } catch (UnsupportedAddressTypeException uate) { SocketAddress localAddr = socket.getLocalSocketAddress(); throw new RuntimeException("wrong address type: " + (localAddr == null ? null : localAddr.getClass()), uate); } } @Override public boolean getOOBInline() throws SocketException { return socket.getOOBInline(); } @Override public int getReceiveBufferSize() throws SocketException { return socket.getReceiveBufferSize(); } @Override public boolean getReuseAddress() throws SocketException { return socket.getReuseAddress(); } @Override public int getSendBufferSize() throws SocketException { return socket.getSendBufferSize(); } @Override public int getSoLinger() throws SocketException { return socket.getSoLinger(); } @Override public int getSoTimeout() throws SocketException { return socket.getSoTimeout(); } @Override public boolean getTcpNoDelay() throws SocketException { return socket.getTcpNoDelay(); } @Override public int getTrafficClass() throws SocketException { return socket.getTrafficClass(); } @Override public boolean isBound() { return socket.isBound(); } @Override public boolean isClosed() { return socket.isClosed(); } @Override public boolean isConnected() { return socket.isConnected(); } @Override public boolean isInputShutdown() { return socket.isInputShutdown(); } @Override public boolean isOutputShutdown() { return socket.isOutputShutdown(); } @Override public void sendUrgentData(int data) { throw new UnsupportedOperationException("No urgent data."); } @Override public void setKeepAlive(boolean on) throws SocketException { socket.setKeepAlive(on); } @Override public void setOOBInline(boolean on) throws SocketException { socket.setOOBInline(on); } @Override public void setReceiveBufferSize(int size) throws SocketException { socket.setReceiveBufferSize(size); } @Override public void setReuseAddress(boolean on) throws SocketException { socket.setReuseAddress(on); } @Override public void setSendBufferSize(int size) throws SocketException { socket.setSendBufferSize(size); } @Override public void setSoLinger(boolean on, int linger) throws SocketException { socket.setSoLinger(on, linger); } @Override public void setSoTimeout(int timeout) throws SocketException { socket.setSoTimeout(timeout); } @Override public void setTcpNoDelay(boolean on) throws SocketException { socket.setTcpNoDelay(on); } @Override public void setTrafficClass(int tc) throws SocketException { socket.setTrafficClass(tc); } @Override public void shutdownInput() throws IOException { socket.shutdownInput(); } @Override public void shutdownOutput() throws IOException { socket.shutdownOutput(); } @Override public String toString() { return "NIOSocket::" + remoteSocketAddress + ", channel: " + channel.toString(); } }