package gdwNet.client;
import gdwNet.NETCONSTANTS;
import gdwNet.RESPONCECODES;
import java.io.IOException;
import java.net.InetAddress;
import java.net.SocketException;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;
import java.nio.channels.SocketChannel;
/**
* Die Hauptlientklasse. Sie hat keinen öffentlichen Konstruktor, sondern gibt euch über einen
* registierten {@link IBasicClientListener} ein Objekt diese Klassen, wenn die Verbindung erfolgreich
* aufgebaut wurde.
*
* Diese Klasse stellt die Verbindung zum Server da. Sie gibt einen NachrichtenbyteBuffer und
* kann Nachrichten zum Server senden.
* @author firen
*
*/
public class BasicClient
{
private static final int messagesPerUpdate = 5;
private final DatagramChannel udpConnection;
private final SocketChannel tcpConnection;
private static ServerlistPendingThread pendingThread = null;
protected final int id;
protected final int sharedSecret;
private static BasicClient ClientSingelton = null;
private static IBasicClientListener listener = null;
private long lastHearthbeat;
private long pongRequest;
private boolean discoFlag;
private final ServerInfo lastServer;
private BasicClient(DatagramChannel udpConnection,
SocketChannel tcpConnection, int id, int secret, ServerInfo server)
{
this.udpConnection = udpConnection;
this.tcpConnection = tcpConnection;
try
{
this.tcpConnection.configureBlocking(false);
} catch (IOException e)
{
}
this.id = id;
this.lastHearthbeat = System.currentTimeMillis();
this.pongRequest = -1L;
this.discoFlag = false;
this.sharedSecret = secret;
this.lastServer = server;
}
/**
* Registiert einen {@link IBasicClientListener} beim Client.
* Erforderlich, da ihr sonst überhaupt nicht verbinden könnt^^.
* @param lis Einen Referenz auf eueren Listener
*/
public static void setListener(IBasicClientListener lis)
{
BasicClient.listener = lis;
}
/**
* @return Gibt eine Referenz auf den aktuellen {@link IBasicClientListener} zurück.
* Null wenn nichts registiert ist.
*/
public static IBasicClientListener getListener()
{
return BasicClient.listener;
}
/**
* Diese Methode weißt den Client auf das Netzwerk nach Servern zu durchsuchen.
*/
public static void refreshServerList()
{
if (BasicClient.pendingThread != null)
{
BasicClient.pendingThread.interrupt();
}
try
{
BasicClient.pendingThread = new ServerlistPendingThread(
listener);
} catch (SocketException e)
{
e.printStackTrace();
}
}
/**
* Weißt den Client an sich zu einen geben Server zu Verbinden.
* Benutzt diese Methode wenn ihr den Server nicht im Netzwerk gefunden habt,
* aber sicher seit das er auf den übergebenen Addresse zu finden ist.
* @param address IPaddresse des Servers, muss nicht IPv4 sein, aber empfholen
* @param port Der Port aufdem der Server lauscht
* @param additionalData daten die ihr zur Anmeldung zusätzlich schicken wollt
*/
public static void connectToServer(InetAddress address, int port,
ByteBuffer additionalData)
{
ServerInfo info = new ServerInfo("", 0, 0, 0, port, address, 0);
BasicClient.connectToServer(info, additionalData);
}
/**
* Weißt den Client an sich zu einem Server zu verbinden.
* Benutzt diese Methode mit den Daten die ihr bekommten habt beim Durchsuchen
* des Netzwerkes
* @param info Serverinfodaten
* @param additionalData daten die ihr zur Anmeldung zusätzlich schicken wollt
*/
public static void connectToServer(ServerInfo info, ByteBuffer additionalData)
{
try
{
SocketChannel tcpSocket = SocketChannel.open();
DatagramChannel udpSocket = DatagramChannel.open();
udpSocket.socket().bind(null);
ByteBuffer buf = ByteBuffer.allocate(NETCONSTANTS.PACKAGELENGTH);
buf.clear();
buf.put(NETCONSTANTS.MAGIC_LOGIN_CODE);// magic code
buf.putInt(udpSocket.socket().getLocalPort());// udp port
buf.put(NETCONSTANTS.CONNECT);//what we want
additionalData.flip();
buf.put(additionalData);
buf.flip();
// connect
IBasicClientListener lis = BasicClient.getListener();
lis.connectionUpdate(RESPONCECODES.CONNECTING);
new ConnectionResponceThread(tcpSocket, udpSocket, buf, info);
} catch (IOException e)
{
BasicClient.getListener().connectionUpdate(
RESPONCECODES.UNREACHABLE);
}
}
public void reconnect()
{
try
{
SocketChannel tcpSocket = SocketChannel.open();
DatagramChannel udpSocket = DatagramChannel.open();
udpSocket.socket().bind(null);
ByteBuffer buf = ByteBuffer.allocate(NETCONSTANTS.PACKAGELENGTH);
buf.clear();
buf.put(NETCONSTANTS.MAGIC_LOGIN_CODE);// magic code
buf.putInt(udpSocket.socket().getLocalPort());// udp port
buf.put(NETCONSTANTS.RECONNECT);//what we want
buf.putInt(this.id);
buf.putInt(this.sharedSecret);
buf.flip();
new ConnectionResponceThread(tcpSocket, udpSocket, buf, this.lastServer);
} catch (IOException e)
{
BasicClient.getListener().connectionUpdate(
RESPONCECODES.UNREACHABLE);
}
}
protected static void registerClient(SocketChannel tcp,
DatagramChannel udp, int id, int sharedSecret, ServerInfo server)
{
BasicClient client = new BasicClient(udp, tcp, id, sharedSecret , server);
BasicClient.ClientSingelton = client;
BasicClient.listener.connectionEstablished(ClientSingelton);
}
/**
* Ruft die Methode auf um die eingehenden Nachrichten vom Server zu erhalten.
* Sie überpüft ob ihr einen Verbindungsverlust hattet und ruft eure Netzwerkprotokol-
* implementierung auf.
* @return true wenn kein disconnect, false bei disconnect
*/
public boolean pollInput()
{
if ((this.discoFlag) || (checkDisconnect(System.currentTimeMillis())))
{
System.out.println("client schließt verbindung");
disconnect();
return false;
}
int counter = 0;
try
{
for (; counter < messagesPerUpdate; ++counter)
{
ByteBuffer buf = ByteBuffer
.allocate(NETCONSTANTS.PACKAGELENGTH);
if (tcpConnection.read(buf) > 0)
{
this.incommingMessage(buf, true);
continue;
}
if (udpConnection.read(buf) > 0)
{
this.incommingMessage(buf, false);
continue;
}
break;
}
} catch (IOException e)
{
e.printStackTrace();
return false;
}
if (counter > 0)
{
this.lastHearthbeat = System.currentTimeMillis();
this.pongRequest = -1L;
}
return true;
}
private void incommingMessage(ByteBuffer buf, boolean wasReliable)
{
buf.position(0);
switch (buf.get())
{
case NETCONSTANTS.PING:
sendPong();
break;
case NETCONSTANTS.PONG:
this.pongRequest = -1L;
break;
case NETCONSTANTS.MESSAGE:
listener.incommingMessage(buf, wasReliable);
default:
break;
}
}
private boolean checkDisconnect(long currentTimeStamp)
{
if ((this.lastHearthbeat + NETCONSTANTS.HEARTBEAT_REQUESTIME) < currentTimeStamp)
{
// check if ping request is needed
if (pongRequest > -1L)
{
if ((pongRequest + NETCONSTANTS.PONG_TIMEOUT) < currentTimeStamp)
{
// not alive
this.discoFlag = true;
return true;
}
} else
{
// send ping
sendPing(currentTimeStamp);
}
}
return false;
}
private void disconnect()
{
listener.connectionUpdate(RESPONCECODES.DISCONNECTED);
try
{
this.tcpConnection.close();
this.udpConnection.close();
} catch (IOException e)
{
}
}
/**
* Ruft die Methode auf um einen Nachricht an den Server zu senden.
* @param msg Eure Nachricht
* @param wasReliable true wenn gesichert(TCP), false wenn ungesichert (UDP)
*/
public void sendMSG(ByteBuffer msg, boolean wasReliable)
{
msg.flip();
try
{
if (wasReliable)
{
this.tcpConnection.write(msg);
} else
{
this.udpConnection.write(msg);
}
} catch (IOException e)
{
this.discoFlag = true;
}
}
private void sendPing(long currentTimeStamp)
{
ByteBuffer buf = ByteBuffer.allocate(1);
buf.put(NETCONSTANTS.PING);
buf.flip();
try
{
this.udpConnection.write(buf);
this.pongRequest = currentTimeStamp;
} catch (IOException e)
{
// somethings wrong shutdown the connection
e.printStackTrace();
discoFlag = true;
}
}
private void sendPong()
{
ByteBuffer buf = ByteBuffer.allocate(1);
buf.put(NETCONSTANTS.PONG);
buf.flip();
try
{
this.udpConnection.write(buf);
} catch (IOException e)
{
// somethings wrong shutdown the connection
e.printStackTrace();
discoFlag = true;
}
}
/**
* Gibt einen NachrichtenByteBuffer zurück. Benutzt ihn um Nachrichten zu versenden.
* @return
*/
public ByteBuffer getMessageBuffer()
{
ByteBuffer buf = ByteBuffer.allocate(NETCONSTANTS.PACKAGELENGTH);
buf.clear();
buf.put(NETCONSTANTS.MESSAGE);
return buf;
}
}