diff --git a/src/main/java/seng302/gameServer/ClientConnectionDelegate.java b/src/main/java/seng302/gameServer/ClientConnectionDelegate.java index dc0827fe..35d839bc 100644 --- a/src/main/java/seng302/gameServer/ClientConnectionDelegate.java +++ b/src/main/java/seng302/gameServer/ClientConnectionDelegate.java @@ -3,5 +3,15 @@ package seng302.gameServer; import seng302.models.Player; public interface ClientConnectionDelegate { + /** + * A player has connected to the server + * @param player The player that has connected + */ void clientConnected(Player player); + + /** + * A player has disconnected from the server + * @param player The player that has disconnected + */ + void clientDisconnected(Player player); } diff --git a/src/main/java/seng302/gameServer/GameServerThread.java b/src/main/java/seng302/gameServer/GameServerThread.java index a7dbb3cc..91d17cc2 100644 --- a/src/main/java/seng302/gameServer/GameServerThread.java +++ b/src/main/java/seng302/gameServer/GameServerThread.java @@ -3,10 +3,14 @@ package seng302.gameServer; import seng302.models.Player; import seng302.models.Yacht; import seng302.server.messages.*; +import seng302.server.simulator.Boat; +import seng302.server.simulator.Simulator; import java.io.IOException; import java.io.InputStream; import java.net.InetSocketAddress; +import java.net.SocketOption; +import java.net.SocketOptions; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; import java.util.*; @@ -22,7 +26,6 @@ public class GameServerThread implements Runnable, Observer, ClientConnectionDel private long startTime; private short seqNum; - private final int HEARTBEAT_PERIOD = 5000; private final int RACE_STATUS_PERIOD = 1000/2; private final int RACE_START_STATUS_PERIOD = 1000; private final int BOAT_LOCATION_PERIOD = 1000/5; @@ -124,28 +127,6 @@ public class GameServerThread implements Runnable, Observer, ClientConnectionDel 100, GameState.getPlayers().size(), RaceType.MATCH_RACE, 1, boatSubMessages); } - - /** - * Starts sending heartbeat messages to the client - */ - private void startSendingHeartbeats() { - Timer t = new Timer(); - - t.schedule(new TimerTask() { - @Override - public void run() { - Message heartbeat = new Heartbeat(seqNum); - - try { - System.out.println("Sending heartbeat"); - broadcast(heartbeat); - } catch (IOException e) { - e.printStackTrace(); - } - } - }, 0, HEARTBEAT_PERIOD); - } - /** * Start sending race start status messages until race starts */ @@ -160,7 +141,6 @@ public class GameServerThread implements Runnable, Observer, ClientConnectionDel if (startTime < System.currentTimeMillis() && GameState.getCurrentStage() != GameStages.RACING){ } else{ - System.out.println("Sending race start status"); broadcast(raceStartStatusMessage); } @@ -200,15 +180,12 @@ public class GameServerThread implements Runnable, Observer, ClientConnectionDel Message regatta = getXmlMessage("/server_config/regatta.xml", XMLMessageSubType.REGATTA); if (raceData != null){ - System.out.println("Sending RaceXML"); broadcast(raceData); } if (boatData != null){ - System.out.println("Sending boatsXML"); broadcast(boatData); } if (regatta != null){ - System.out.println("Sending regattaXML"); broadcast(regatta); } } catch (IOException e) { @@ -227,7 +204,6 @@ public class GameServerThread implements Runnable, Observer, ClientConnectionDel try { Message raceData = getXmlMessage("/server_config/courseLimits.xml", XMLMessageSubType.RACE); if (raceData != null) { - System.out.println("Sending courseLimitsXML"); broadcast(raceData); } }catch (IOException e) { @@ -240,12 +216,17 @@ public class GameServerThread implements Runnable, Observer, ClientConnectionDel public void run() { ServerListenThread serverListenThread; + HeartbeatThread heartbeatThread; Boolean serverIsSendingMessages = false; try{ server = ServerSocketChannel.open(); server.socket().bind(new InetSocketAddress("localhost", PORT_NUMBER)); + serverListenThread = new ServerListenThread(server, this); + heartbeatThread = new HeartbeatThread(this); + + heartbeatThread.start(); serverListenThread.start(); } catch (IOException e){ @@ -262,7 +243,6 @@ public class GameServerThread implements Runnable, Observer, ClientConnectionDel if (GameState.getCurrentStage() == GameStages.RACING && !serverIsSendingMessages) { serverLog("Race Started", 0); - startSendingHeartbeats(); sendXml(); startSendingRaceStartStatusMessages(); //startSendingRaceStatusMessages(); @@ -275,8 +255,7 @@ public class GameServerThread implements Runnable, Observer, ClientConnectionDel } startTime = System.currentTimeMillis() + TIME_TILL_RACE_START; - - } + } } // /** @@ -310,44 +289,35 @@ public class GameServerThread implements Runnable, Observer, ClientConnectionDel @Override public void clientConnected(Player player) { if (GameState.getPlayers().size() < MAX_NUM_PLAYERS && GameState.getCurrentStage() == GameStages.LOBBYING) { - System.out.println(""); serverLog("Player Connected", 0); GameState.addPlayer(player); + sendXml(); } - sendXml(); } /** - * Listens for a connection and upon finding one, creates a Player object and adds it to the universal GameState + * A player has left the game, remove the player from the GameState + * @param player The player that left */ - private void acceptConnection() { - try { - SocketChannel thisClient = server.accept(); - if (thisClient.socket() != null){ - Player thisPlayer = new Player(thisClient); - GameState.addPlayer(thisPlayer); - } - } catch (IOException e) { - e.getMessage(); - } + @Override + public void clientDisconnected(Player player) { + serverLog("Player disconnected", 0); + GameState.removePlayer(player); + sendXml(); } - void unicast(Message message, SocketChannel client) throws IOException { - message.send(client); // TODO: 11/07/17 Do we incement seqNum for individual messages? + message.send(client); } - void broadcast(Message message) throws IOException{ for(Player player : GameState.getPlayers()) { - System.out.println("Sending message seqNo[" + seqNum + "] to Player: " + player.toString()); + //System.out.println("Sending message seqNo[" + seqNum + "] to Player: " + player.toString()); message.send(player.getSocketChannel()); } - seqNum++; // TODO: 11/07/17 Do we increment seqNum for every message or for the one message to everyone + seqNum++; } - - /** * Send a boat location message when they are updated by the simulator * @param o . @@ -356,42 +326,39 @@ public class GameServerThread implements Runnable, Observer, ClientConnectionDel @Override @SuppressWarnings("unchecked") public void update(Observable o, Object arg) { -// /* Only send if server started*/ -//// // TODO: I don't understand why i need to check server is null or not ... confused - haoming 2/5/17 -// if (server == null || !server.isStarted()) { -// return; -// } -// -// int numOfBoatsFinished = 0; -// for (Boat boat : (List) arg) { -// try { -// if (boat.isFinished()) { -// numOfBoatsFinished++; -// if (!boatsFinished.get(boat.getSourceID())) { -// boatsFinished.put(boat.getSourceID(), true); -// } -// } -// Message m = new BoatLocationMessage(boat.getSourceID(), 1, boat.getLat(), -// boat.getLng(), boat.getLastPassedCorner().getBearingToNextCorner(), -// ((long) boat.getSpeed())); -// broadcast(m); -// } catch (IOException e) { -// serverLog("Couldn't send a boat status message", 3); -// return; -// } catch (NullPointerException e) { -// e.printStackTrace(); -// } -// } + /* Only send if server started + // TODO: I don't understand why i need to check server is null or not ... confused - haoming 2/5/17 + if(server == null || !server.isStarted()){ + return; + } + + int numOfBoatsFinished = 0; + for (Boat boat : (List) arg){ + try { + if (boat.isFinished()) { + numOfBoatsFinished ++; + if (!boatsFinished.get(boat.getSourceID())) { + boatsFinished.put(boat.getSourceID(), true); + } + } + Message m = new BoatLocationMessage(boat.getSourceID(), 1, boat.getLat(), + boat.getLng(), boat.getLastPassedCorner().getBearingToNextCorner(), + ((long) boat.getSpeed())); + broadcast(m); + } catch (IOException e) { + serverLog("Couldn't send a boat status message", 3); + return; + } + catch (NullPointerException e){ + e.printStackTrace(); + }*/ + } // if (numOfBoatsFinished == ((List) arg).size()) { // startSendingRaceFinishedBoatPositions(); // } - } - - - - + //} public void terminateGame() { try { diff --git a/src/main/java/seng302/gameServer/HeartbeatThread.java b/src/main/java/seng302/gameServer/HeartbeatThread.java new file mode 100644 index 00000000..e10a85f5 --- /dev/null +++ b/src/main/java/seng302/gameServer/HeartbeatThread.java @@ -0,0 +1,81 @@ +package seng302.gameServer; + +import seng302.models.Player; +import seng302.server.messages.Heartbeat; +import seng302.server.messages.Message; + +import java.io.IOException; +import java.util.*; + +/** + * Send Heartbeat messages to connected player at a specified interval + * Will call .clientDisconnected on the delegate when a heartbeat message + * cannot be sent to a player + */ +public class HeartbeatThread extends Thread{ + private final int HEARTBEAT_PERIOD = 5000; + private ClientConnectionDelegate delegate; + private Integer seqNum; + private Stack disconnectedPlayers; + + HeartbeatThread(ClientConnectionDelegate delegate){ + this.delegate = delegate; + seqNum = 0; + disconnectedPlayers = new Stack<>(); + } + + /** + * A player has lost connection to the server + * The player is added to a stack so that the delegate + * can be notified + * + * @param player The player that has disconnected + */ + private void playerLostConnection(Player player){ + disconnectedPlayers.push(player); + } + + /** + * Sends a heartbeat message to each connected player + * The delegate is notified if a player has disconnected + */ + private void sendHeartbeatToAllPlayers(){ + Message heartbeat = new Heartbeat(seqNum); + + for (Player player : GameState.getPlayers()){ + if (!player.getSocketChannel().isConnected()){ + playerLostConnection(player); + } + + try { + heartbeat.send(player.getSocketChannel()); + } catch (IOException e) { + playerLostConnection(player); + } + } + + updateDelegate(); + seqNum++; + } + + /** + * Notifies the delegate about + * each disconnected player + */ + private void updateDelegate() { + while (!disconnectedPlayers.empty()){ + delegate.clientDisconnected(disconnectedPlayers.pop()); + } + } + + public void run(){ + Timer t = new Timer(); + + t.schedule(new TimerTask() { + @Override + public void run() { + sendHeartbeatToAllPlayers(); + } + }, 0, HEARTBEAT_PERIOD); + } +} diff --git a/src/main/java/seng302/gameServer/ServerListenThread.java b/src/main/java/seng302/gameServer/ServerListenThread.java index 67ab285c..cfcbdcaa 100644 --- a/src/main/java/seng302/gameServer/ServerListenThread.java +++ b/src/main/java/seng302/gameServer/ServerListenThread.java @@ -1,6 +1,5 @@ package seng302.gameServer; -import com.sun.corba.se.spi.activation.Server; import seng302.models.Player; import java.io.IOException; @@ -15,7 +14,7 @@ public class ServerListenThread extends Thread{ private ServerSocketChannel socketChannel; private ClientConnectionDelegate delegate; - public ServerListenThread(ServerSocketChannel socketChannel, ClientConnectionDelegate delegate){ + ServerListenThread(ServerSocketChannel socketChannel, ClientConnectionDelegate delegate){ this.socketChannel = socketChannel; this.delegate = delegate; } diff --git a/src/main/java/seng302/models/Player.java b/src/main/java/seng302/models/Player.java index 6768eefd..fb062d0d 100644 --- a/src/main/java/seng302/models/Player.java +++ b/src/main/java/seng302/models/Player.java @@ -24,7 +24,6 @@ public class Player { return socketChannel; } - public Integer getLastMarkPassed() { return lastMarkPassed; } @@ -40,6 +39,11 @@ public class Player { @Override public String toString() { String playerAddress = null; + + if (socketChannel == null){ + return "Disconnected Player"; + } + try { playerAddress = socketChannel.getRemoteAddress().toString(); } catch (IOException e) { @@ -48,4 +52,22 @@ public class Player { return playerAddress; } + + @Override + public boolean equals(Object obj) { + if (obj == null){ + return false; + } + + if (!(obj instanceof Player)){ + return false; + } + + return ((Player) obj).socketChannel.equals(socketChannel); + } + + @Override + public int hashCode(){ + return socketChannel.hashCode(); + } } diff --git a/src/main/java/seng302/server/messages/RaceStartStatusMessage.java b/src/main/java/seng302/server/messages/RaceStartStatusMessage.java index 368a18fd..9aa886ee 100644 --- a/src/main/java/seng302/server/messages/RaceStartStatusMessage.java +++ b/src/main/java/seng302/server/messages/RaceStartStatusMessage.java @@ -54,6 +54,15 @@ public class RaceStartStatusMessage extends Message { writeCRC(); rewind(); - outputStream.write(getBuffer()); + if (outputStream == null){ + return; + } + + try{ + outputStream.write(getBuffer()); + } + catch (IOException e){ + return; + } } }