package com.limegroup.gnutella.util;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.net.SocketException;
import java.net.UnknownHostException;
import com.limegroup.gnutella.ErrorService;
import com.limegroup.gnutella.settings.ConnectionSettings;
/**
* Provides socket operations that are not available on all platforms,
* like connecting with timeouts and settings the SO_KEEPALIVE option.
* Obsoletes the old SocketOpener class.
*/
public class Sockets {
/**
* The maximum number of concurrent connection attempts.
*/
private static final int MAX_CONNECTING_SOCKETS = 8;
/**
* The current number of waiting socket attempts.
*/
private static int _socketsConnecting = 0;
private static volatile int _attempts=0;
/**
* Ensure this cannot be constructed.
*/
private Sockets() {}
/**
* Sets the SO_KEEPALIVE option on the socket, if this platform supports it.
* (Otherwise, it does nothing.)
*
* @param socket the socket to modify
* @param on the desired new value for SO_KEEPALIVE
* @return true if this was able to set SO_KEEPALIVE
*/
public static boolean setKeepAlive(Socket socket, boolean on) {
try {
socket.setKeepAlive(on);
return true;
} catch(SocketException se) {
return false;
}
}
/**
* Connects and returns a socket to the given host, with a timeout.
*
* @param host the address of the host to connect to
* @param port the port to connect to
* @param timeout the desired timeout for connecting, in milliseconds,
* or 0 for no timeout. In case of a proxy connection, this timeout
* might be exceeded
* @return the connected Socket
* @throws IOException the connections couldn't be made in the
* requested time
* @throws <tt>IllegalArgumentException</tt> if the port is invalid
*/
public static Socket connect(String host, int port, int timeout)
throws IOException {
if(!NetworkUtils.isValidPort(port)) {
throw new IllegalArgumentException("port out of range: "+port);
}
_attempts++;
return connectPlain(host, port, timeout);
}
/**
* connect to a host directly
* @see connect(String, int, int)
*/
private static Socket connectPlain(String host, int port, int timeout)
throws IOException {
//return Sockets14.getSocket(host, port, timeout);
if (timeout!=0)
//b) Emulation using threads
return (new SocketOpener(host, port)).connect(timeout);
else
//c) No timeouts
return new Socket(host, port);
}
public static int getAttempts() {
return _attempts;
}
public static void clearAttempts() {
_attempts=0;
}
private static class SocketOpener {
private String host;
private int port;
/** The established socket, or null if not established OR couldn't be
* established.. Notify this when socket becomes non-null. */
private Socket socket=null;
/** True iff the connecting thread should close the socket if/when it
* is established. */
private boolean timedOut=false;
private boolean completed=false;
public SocketOpener(String host, int port) {
if((port & 0xFFFF0000) != 0) {
throw new IllegalArgumentException("port out of range: "+port);
}
this.host=host;
this.port=port;
}
/**
* Returns a new socket to the given host/port. If the socket couldn't be
* established withing timeout milliseconds, throws IOException. If
* timeout==0, no timeout occurs. If this thread is interrupted while
* making connection, throws IOException.
*
* @requires connect has only been called once, no other thread calling
* connect. Timeout must be non-negative.
*/
public synchronized Socket connect(int timeout)
throws IOException {
//Asynchronously establish socket.
Thread t = new ManagedThread(new SocketOpenerThread(), "SocketOpener");
t.setDaemon(true);
t.start();
//Wait for socket to be established, or for timeout.
try {
this.wait(timeout);
} catch (InterruptedException e) {
if (socket==null)
timedOut=true;
else
try { socket.close(); } catch (IOException e2) { }
throw new IOException();
}
// Ensure that the SocketOpener is killed.
if( !completed )
t.interrupt();
//a) Normal case
if (socket!=null) {
return socket;
}
//b) Timeout case
else {
timedOut=true;
throw new IOException();
}
}
private class SocketOpenerThread implements Runnable {
public void run() {
Socket sock = null;
try {
try {
sock=new Socket(host, port);
} catch (IOException e) { }
synchronized (SocketOpener.this) {
completed = true;
if (timedOut && sock!=null)
try { sock.close(); } catch (IOException e) { }
else {
socket=sock; //may be null
SocketOpener.this.notify();
}
}
} catch(Throwable t) {
//We actively call Thread.interrupt() on this thread,
//and we've received reports of the Socket constructor
//throwing InterruptedException.
//(See: http://www9.limewire.com:82/dev/exceptions/3.4.4/
// java.lang.InterruptedException/Socket.19534.txt)
//However, nothing declares it to be thrown, so we can't
//catch and discard seperately.
//As a workaround, we only error if t is not an
//instanceof InterruptedException.
if(!(t instanceof InterruptedException))
ErrorService.error(t);
}
}
}
}
}