package gameserver;
import common.Client;
import common.Latency;
import common.ReceiveHandleLoop;
import common.State;
import common.TimeoutHandler;
import common.packet.Authorization;
import common.packet.ChatMessage;
import common.packet.ClientInfo;
import common.packet.Input;
import common.packet.Packet;
import common.packet.PacketHandler;
import common.packet.Error;
import common.packet.OK;
import common.PingHandler;
import java.io.IOException;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/**
* Stores data for a connected client and provides methods to control it. It owns
* the RHL of the client and runs it in its own run method. It performs the
* authorization process when it is first started and adds and removes packet
* handlers to its RHL as necessary. All its packet handlers are inner classes
* because they don't make sense outside of here.
*
* @author xissburg
*/
public class ClientThread implements Runnable
{
private final Game game;
private final ReceiveHandleLoop receiveHandleLoop;
private Latency latency;
/**
* Stores the last input packets sent by the client. The InputHandler below
* adds stuff into this list.
*/
private final List<Input> inputList;
public ClientThread(Client client, final Game game)
{
this.game = game;
latency = new Latency(client);
inputList = new ArrayList<Input>();
receiveHandleLoop = new ReceiveHandleLoop(client);
//setup the error handler for the authorization process
receiveHandleLoop.setErrorHandler(new ReceiveHandleLoop.ErrorHandler()
{
public boolean onSocketTimeoutException(SocketTimeoutException ex)
{
try { //Try to send error message
receiveHandleLoop.getClient().write(new Error("Timeout exception."));
} catch (IOException ex1) {
ex1.printStackTrace();
}
return false;
}
public boolean onIOException(IOException ex) {
ex.printStackTrace();
return false;
}
});
}
public void addPacketHandler(PacketHandler handler) {
receiveHandleLoop.addPacketHandler(handler);
}
public void removePacketHandler(Packet.Code code) {
receiveHandleLoop.removePacketHandler(code);
}
public Client getClient() {
return receiveHandleLoop.getClient();
}
/**
* Returns a list of the last input packets received from the client.
* @return
*/
public List<Input> getInput()
{
synchronized(inputList)
{
if(inputList.isEmpty())
return null;
List<Input> input = new ArrayList<Input>(inputList);
inputList.clear();
return input;
}
}
public long getLatency() {
return latency.getLatency();
}
public boolean refreshLatency() {
return latency.refreshLatency();
}
public void run()
{
Client client = receiveHandleLoop.getClient();
//Set 8 seconds timeout
try {
client.setSocketTimeout(8000);
}
catch (SocketException ex) {
try {
client.closeSocket();
} catch (IOException ex1) {
ex1.printStackTrace();
} finally {
return;
}
}
//Add one AuthorizationHandler
addPacketHandler(new AuthorizationHandler());
//Set the default packet handler for this operation
receiveHandleLoop.setDefaultHandler(new PacketHandler() {
public void handle(Packet packet) {
try { //Try to send error message
Client client = receiveHandleLoop.getClient();
client.write(new Error("Invalid object. Authorization packet required."));
receiveHandleLoop.stop();
} catch (IOException ex) {
ex.printStackTrace();
}
}
public Packet.Code getPacketCode() {
return null;
}
});
//And go
receiveHandleLoop.run();
}
//---------------------------------------------------------------------------------
/**
* Packet Handlers for this class. They are very specific for this class
* only, thats why they are private inner classes. They don't make sense
* outside of here.
*/
// <editor-fold defaultstate="collapsed" desc="local packet handlers">
/**
* Handles the Authorization packet sent during the authorization process.
* It setsup the initial client structure with data from the Authorization
* packet, removes itself from the RHL, adds an OKHandler to it, sets a new
* default handler just t send a different error message to the client and
* send an OK packet to the client to notify he was successfully authorized
* and must now send another OK packet to notify the server he is ready to
* continue.
*/
private class AuthorizationHandler implements PacketHandler
{
public AuthorizationHandler() {}
public void handle(Packet packet) {
Authorization auth = (Authorization) packet;
String name = auth.getUsername();
Client client = receiveHandleLoop.getClient();
if(name.length() < 3)
{
try { //Try to send error message
client.write(new Error("User name must be at least 3 characters long."));
receiveHandleLoop.stop();
} catch (IOException ex) {
ex.printStackTrace();
} finally {
return;
}
}
//Check whether the name is unique among all existing clients
Collection<ClientThread> clientCol = game.getClients();
for (ClientThread ct : clientCol) {
if(name.equals(ct.getClient().getClientInfo().getName()))
{
try {
client.write(new Error("User name currently in use, please try another one."));
receiveHandleLoop.stop();
} catch (IOException ex) {
ex.printStackTrace();
} finally {
return;
}
}
}
client.getClientInfo().setName(name);
//Authentication successful. Remove this handler and add an OK handler,
//because this is what the server hopes ro receive from the client.
receiveHandleLoop.removePacketHandler(getPacketCode());
receiveHandleLoop.addPacketHandler(new OKHandler());
//Set the default packet handler for this operation
receiveHandleLoop.setDefaultHandler(new PacketHandler() {
public void handle(Packet packet) {
try { //Try to send error message
Client client = receiveHandleLoop.getClient();
client.write(new Error("Invalid object. OK packet required."));
receiveHandleLoop.stop();
} catch (IOException ex) {
ex.printStackTrace();
}
}
public Packet.Code getPacketCode() {
return null;
}
});
//Send OK packet
try {
client.write(new OK());
} catch (IOException ex) {
try { //If it fails stop the recv-handle loop
client.closeSocket();
receiveHandleLoop.stop();
} catch (IOException ex1) {
ex1.printStackTrace();
}
}
}
public Packet.Code getPacketCode() {
return Packet.Code.AUTHORIZATION;
}
}
/**
* Handles an OK packet in the authorization process. Now the client is ready
* to continue. First it resets the client's socket timeout to infinity,
* then removes itself from the RHL, sets the default handler to none, adds
* this client thread to the game, sends the current game state to the client,
* and adds the another handlers to RHL: ChatMessage, ClientState and Logout,
* and also if the client is the owner/first ti also adds a StartGameHandler.
*
*/
private class OKHandler implements PacketHandler
{
public OKHandler() {}
public void handle(Packet packet) {
Client client = receiveHandleLoop.getClient();
//Now the client was accepted and it is ready to continue.
//First set its socket timeout to the maximum innactivity time
int maxInactivityTime = 10000;
try {
client.setSocketTimeout(maxInactivityTime);
} catch (SocketException ex) {
try {
client.closeSocket();
receiveHandleLoop.stop();
} catch (IOException ex1) {
ex1.printStackTrace();
} finally {
return;
}
}
//Remove this OK handler, set the default to null and set the error
//handler to be a TimeoutHandler
receiveHandleLoop.removePacketHandler(getPacketCode());
receiveHandleLoop.setDefaultHandler(null);
receiveHandleLoop.setErrorHandler(
new TimeoutHandler(receiveHandleLoop, 8000));
//Set the error handler to be a
//receiveHandleLoop.setErrorHandler(new )
//Setup initial client info
//...
//Set the finish code. If the receive-handle loop terminates, it means
//the client is dead. Then it must notify all other clients and remove
//this dead client from the global client list.
receiveHandleLoop.setFinishRunnable(new Runnable() {
public void run() {
Client thisClient = receiveHandleLoop.getClient();
//Remove client from the global client list
game.removeClientThread(thisClient.getClientInfo().getName());
try { //make sure the socket is closed
thisClient.closeSocket();
} catch (IOException ex) {
ex.printStackTrace();
}
System.out.println(thisClient.getClientInfo().getName() + " left the game.");
}
});
//Now the client is ready to be added to the global client list. Now he
//may receive packets written from other ClientThreads. Also, notify all
//other clients about this new client, if any.
game.addClientThread(ClientThread.this);
try { //Send the current game state
client.write(game.getGameState());
} catch (IOException ex) {
try {
receiveHandleLoop.stop();
} catch (IOException ex1) {
ex1.printStackTrace();
} finally {
return;
}
}
//Now setup the actual handlers for the client
receiveHandleLoop.addPacketHandler(new PingHandler(client));
receiveHandleLoop.addPacketHandler(latency.getPongHandler());
receiveHandleLoop.addPacketHandler(new ChatMessageHandler());
receiveHandleLoop.addPacketHandler(new LogoutHandler());
receiveHandleLoop.addPacketHandler(new TimeoutHandler.
AreYouAliveHandler(receiveHandleLoop.getClient()));
if(game.getState() == State.WAITING) {
receiveHandleLoop.addPacketHandler(new ClientInfoHandler());
}
//only the onwer can start the game, hence add a StartGameHandler for it only
if (client.getClientInfo().isOwner())
receiveHandleLoop.addPacketHandler(new StartGameHandler());
System.out.println(client.getClientInfo().getName() + " joined the game.");
}
public Packet.Code getPacketCode() {
return Packet.Code.OK;
}
}
/**
* Stops a ReceiveHandleLoop consequently removing all resources allocated to this
* client in the game server and also notifies all other clients (the finishRunnable
* of the receiveHandleLoop does the job).
*
* @author xissburg
*/
private class LogoutHandler implements PacketHandler
{
public void handle(Packet packet)
{
try {
receiveHandleLoop.stop();
} catch (IOException ex) {
ex.printStackTrace();
}
}
public Packet.Code getPacketCode() {
return Packet.Code.LOGOUT;
}
}
/**
* Handles a client state change request sent by the client to the server.
*
* @author xissburg
*/
class ClientInfoHandler implements PacketHandler
{
public void handle(Packet packet)
{
ClientInfo clientInfo = (ClientInfo)packet;
game.setClientInfo(clientInfo);
}
public Packet.Code getPacketCode() {
return Packet.Code.CLIENT_INFO;
}
}
/**
* Handles a chat message received by the game server.
*
* @author xissburg
*/
public class ChatMessageHandler implements PacketHandler
{
@Override
public void handle(Packet packet)
{
ChatMessage chatMessage = (ChatMessage)packet;
String message = chatMessage.getMessage();
Client client = getClient();
//Make sure the message is not empty or null
if(message != null && message.length() > 0)
{
ChatMessage cmsg = new ChatMessage("[" +
client.getClientInfo().getName() + "] " + message);
Collection<ClientThread> clients = game.getClients();
for(ClientThread ct: clients)
{
try {
ct.getClient().write(cmsg);
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
}
public Packet.Code getPacketCode()
{
return Packet.Code.CHAT_MESSAGE;
}
}
private class StartGameHandler implements PacketHandler
{
public StartGameHandler() {}
public void handle(Packet packet) {
//Remove the StartGame handler from the RHL since the game cannot be started
//while it is already running
receiveHandleLoop.removePacketHandler(getPacketCode());
Collection<ClientThread> clients = game.getClients();
//Remove the ClientInfo handler from all clients
for (ClientThread ct : clients)
ct.removePacketHandler(Packet.Code.CLIENT_INFO);
//Add Input handlers to all clients which are players
for (ClientThread ct : clients)
{
if(ct.getClient().getClientInfo().getType() == ClientInfo.Type.PLAYER)
{
InputHandler inputHandler = ct.new InputHandler();
ct.addPacketHandler(inputHandler);
}
}
//Start game
game.start();
}
public Packet.Code getPacketCode() {
return Packet.Code.START_GAME;
}
}
private class InputHandler implements PacketHandler
{
public InputHandler() {}
public void handle(Packet packet)
{
Input input = (Input)packet;
input.setTimestamp(System.nanoTime());
synchronized(inputList) {
inputList.add(input);
}
}
public Packet.Code getPacketCode() {
return Packet.Code.INPUT;
}
}// </editor-fold>
}
|