/**
* ForwardServerClientThread handles the clients of Nakov Forward Server. It
* finds suitable server from the server pool, connects to it and starts
* the TCP forwarding between given client and its assigned server. After
* the forwarding is failed and the two threads are stopped, closes the sockets.
*/
import java.net.Socket;
import java.net.SocketException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
public class ForwardServerClientThread extends Thread
{
private NakovForwardServer mNakovForwardServer = null;
private NakovForwardServer.ServerDescription mServer = null;
private Socket mClientSocket = null;
private Socket mServerSocket = null;
private boolean mBothConnectionsAreAlive = false;
private String mClientHostPort;
private String mServerHostPort;
/**
* Creates a client thread for handling clients of NakovForwardServer.
* A client socket should be connected and passed to this constructor.
* A server socket is created later by run() method.
*/
public ForwardServerClientThread(NakovForwardServer aNakovForwardServer, Socket aClientSocket)
{
mNakovForwardServer = aNakovForwardServer;
mClientSocket = aClientSocket;
}
/**
* Obtains a destination server socket to some of the servers in the list.
* Starts two threads for forwarding : "client in <--> dest server out" and
* "dest server in <--> client out", waits until one of these threads stop
* due to read/write failure or connection closure. Closes opened connections.
*/
public void run()
{
try {
mClientHostPort = mClientSocket.getInetAddress().getHostAddress() + ":" + mClientSocket.getPort();
// Create a new socket connection to one of the servers from the list
mServerSocket = createServerSocket();
if (mServerSocket == null) { // If all the servers are down
System.out.println("Can not establish connection for client " +
mClientHostPort + ". All the servers are down.");
try { mClientSocket.close(); } catch (IOException e) {}
return;
}
// Obtain input and output streams of server and client
InputStream clientIn = mClientSocket.getInputStream();
OutputStream clientOut = mClientSocket.getOutputStream();
InputStream serverIn = mServerSocket.getInputStream();
OutputStream serverOut = mServerSocket.getOutputStream();
mServerHostPort = mServer.host + ":" + mServer.port;
mNakovForwardServer.log("TCP Forwarding " + mClientHostPort + " <--> " + mServerHostPort + " started.");
// Start forwarding of socket data between server and client
ForwardThread clientForward = new ForwardThread(this, clientIn, serverOut);
ForwardThread serverForward = new ForwardThread(this, serverIn, clientOut);
mBothConnectionsAreAlive = true;
clientForward.start();
serverForward.start();
} catch (IOException ioe) {
ioe.printStackTrace();
}
}
/**
* connectionBroken() method is called by forwarding child threads to notify
* this thread (their parent thread) that one of the connections (server or client)
* is broken (a read/write failure occured). This method disconnects both server
* and client sockets causing both threads to stop forwarding.
*/
public synchronized void connectionBroken()
{
if (mBothConnectionsAreAlive) {
// One of the connections is broken. Close the other connection and stop forwarding
// Closing these socket connections will close their input/output streams
// and that way will stop the threads that read from these streams
try { mServerSocket.close(); } catch (IOException e) {}
try { mClientSocket.close(); } catch (IOException e) {}
mBothConnectionsAreAlive = false;
mServer.clientsConectedCount--;
mNakovForwardServer.log("TCP Forwarding " + mClientHostPort + " <--> " + mServerHostPort + " stopped.");
}
}
/**
* @return a new socket connected to some of the servers in the destination
* servers list. Sequentially a connection to the least loaded server from
* the list is tried to be established. If connecting to some alive server
* fail, this server it marked as dead and next alive server is tried. If all
* the servers are dead, null is returned. Thus if at least one server is alive,
* a connection will be established (of course after some delay) and the system
* will not fail (it is fault tolerant). Dead servers can be marked as alive if
* revived, but this is done later by check alive thread.
*/
private Socket createServerSocket() throws IOException
{
while (true) {
mServer = getServerWithMinimalLoad();
if (mServer == null) // All the servers are down
return null;
try {
Socket socket = new Socket(mServer.host, mServer.port);
mServer.clientsConectedCount++;
return socket;
} catch (IOException ioe) {
mServer.isAlive = false;
}
}
}
/**
* @return the least loaded alive server from the server list if load balancing
* is enabled or first alive server from the list if load balancing algorithm is
* disabled or null if all the servers in the list are dead.
*/
private NakovForwardServer.ServerDescription getServerWithMinimalLoad()
{
NakovForwardServer.ServerDescription minLoadServer = null;
NakovForwardServer.ServerDescription[] servers = mNakovForwardServer.getServersList();
for (int i=0; i