package pong.server.model;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.LinkedList;
import java.util.Queue;
import pong.common.Debugger;
import pong.common.Message;
import pong.common.PlayerMovementInformation;
import pong.common.Position;
import pong.server.ServerSettings;
/**
* This class manages the communication among the server and a client.
*
* @author Lorenzo Gatto
*
*/
public class ServerProtocolThread extends Thread {
private final Socket socket;
private final Player player;
private final ServerModel server;
private ObjectOutputStream output;
private ObjectInputStream input;
private final Integer playerIndex;
private boolean roundRunning;
private final Queue<Message> messagesQueue;
/**
* Initializes the ServerPRotocolThread and set the player to be connected.
*
* @param socket
* the socket to which the player connected
* @param player
* the player that connected to that socket
* @param server
* a pointer to the server model
* @param playerIndex
* the player index
*/
public ServerProtocolThread(final Socket socket, final Player player, final ServerModel server,
final int playerIndex) {
super();
this.socket = socket;
this.player = player;
this.server = server;
this.player.setConnected(true);
this.playerIndex = playerIndex;
this.messagesQueue = new LinkedList<>();
}
/**
* Create the connection, send player index and send messages periodically.
*/
@Override
public synchronized void run() {
try {
try {
this.socket.setTcpNoDelay(true);
OutputStream o;
InputStream i;
o = socket.getOutputStream();
i = socket.getInputStream();
this.output = new ObjectOutputStream(o);
this.input = new ObjectInputStream(i);
} catch (IOException e) {
onDisconnect();
return;
}
sendPlayerIndex();
while (true) {
// Pause to allow other thread to access synchonized method and not to saturate the
// connection
this.wait(ServerSettings.SERVER_SLEEP_TIME);
if (!this.roundRunning) {
// Debugger.printLine("Checking if player " +
// this.playerIndex + " is alive");
checkAlive();
}
sendMessagesOnQueue();
if (this.roundRunning) {
sendPositionInformation();
getMovementInformation();
}
}
} catch (InterruptedException e) {
return;
}
}
/**
* This method tell the client the other player disconnected asynchronously.
*/
public synchronized void otherDisconnectedWhilePlayingEvent() {
final Message m = new Message(Message.Type.OTHER_DISCONNECTED, null);
this.messagesQueue.add(m);
}
/**
* This method tell the client the match begins asynchronously.
*
* @param roundNumber
* the number of rounds for the match
*/
public synchronized void matchBeginsEvent(final int roundNumber) {
Debugger.printLine("ServerProtocolThread.matchBeginsEvent called for player "
+ this.playerIndex);
final Message m = new Message(Message.Type.MATCH_BEGINS, Integer.valueOf(roundNumber));
this.messagesQueue.add(m);
}
/**
* This method tell the client that the round ended and tells him if he won it.
*
* @param won
* true or false
*/
public synchronized void roundEndEvent(final Boolean won) {
final Message m = new Message(Message.Type.ROUND_END, won);
this.messagesQueue.add(m);
}
/**
* This method tell the client the round begins asynchronously.
*/
public synchronized void roundBeginsEvent() {
Debugger.printLine("ServerProtocolThread.roundBeginsEvent called for player "
+ this.playerIndex);
final Message m = new Message(Message.Type.ROUND_BEGINS, null);
this.messagesQueue.add(m);
}
/**
* This method interrupts the thread.
*/
@Override
public synchronized void interrupt() {
Debugger.printLine("ServerProtocolThread.interrupt");
super.interrupt();
try {
this.input.close();
this.output.close();
this.socket.close();
} catch (IOException e) {
// DO NOTHING
}
}
/**
* Tells the player that the match ended.
*/
public synchronized void matchEndsEvent() {
// CURRENT PROTOCOL IMPLEMENTATION DOES NOT NEED TO SEND A MESSAGE FOR
// THIS EVENT
}
/**
* This method sends messages that were to be sent asynchronously
*/
private void sendMessagesOnQueue() {
while (this.messagesQueue.size() != 0) {
final Message m = this.messagesQueue.remove();
sendMessage(m);
if (m.getMessageType() == Message.Type.OTHER_DISCONNECTED
|| m.getMessageType() == Message.Type.ROUND_END
|| m.getMessageType() == Message.Type.MATCH_BEGINS) {
this.roundRunning = false;
} else if (m.getMessageType() == Message.Type.ROUND_BEGINS) {
this.roundRunning = true;
}
}
}
/**
* This method sends the player index to the client.
*/
private void sendPlayerIndex() {
Debugger.printLine("ServerProtocolThread.sendPlayerIndex called for player "
+ this.playerIndex);
final Message m = new Message(Message.Type.YOUR_PLAYER_INDEX, playerIndex);
sendMessage(m);
}
/**
* This method sends where objects are to the client
*/
private void sendPositionInformation() {
final Position[] pos = server.getObjectsPositions();
// Debugger.printLine("ServerProtocolThread.sendPositions called");
// Debugger.printLine("Player 1 position " + pos[1].getY());
final Message m = new Message(Message.Type.POSITIONS, pos);
sendMessage(m);
}
/**
* This method gets how the player is moving from the client and updates the MovementInformation
* object inside the Player instance.
*/
private void getMovementInformation() {
if (this.isInterrupted()) {
return;
}
Debugger.printLine("ServerProtocolThread.getMovementInformation called for player"
+ this.playerIndex + " time " + System.currentTimeMillis());
Message message = null;
try {
message = (Message) this.input.readObject();
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
System.exit(1);
} catch (IOException e) {
onDisconnect();
return;
}
assert (message != null);
assert (message.getMessageType() == Message.Type.MOVEMENT);
this.player.setMovementInformation((PlayerMovementInformation) message.getContent());
Debugger.printLine("Movement information received for player" + this.playerIndex + " time "
+ System.currentTimeMillis());
}
/**
* Sends a message to the client checking for failure.
*
* @param message
* the message to be sent to the client
*/
private void sendMessage(final Message message) {
if (this.isInterrupted()) {
return;
}
Debugger.printLine("ServerProtocolThread.sendMessage called for player " + this.playerIndex
+ " " + message.getMessageType().name() + " time " + System.currentTimeMillis());
try {
this.output.writeObject(message);
this.output.flush();
this.output.reset();
} catch (IOException e) {
onDisconnect();
}
}
/**
* Player disconnects. This method tell the server and interrupt the thread.
*
*/
private void onDisconnect() {
Debugger.printLine("ServerProtocolThread.onDisconnect called");
this.player.setConnected(false);
if (!this.isInterrupted()) {
this.server.playerDisconnectedEvent(this);
interrupt();
}
}
/**
* Sends a message just to make an IOException be thrown in case the player disconnects
* unexpectedly or the connection fails.
*/
private void checkAlive() {
final Message m = new Message(Message.Type.ARE_YOU_ALIVE, null);
sendMessage(m);
}
}