From 769d1956b398d062b2cd6d76302fb412b44ac857 Mon Sep 17 00:00:00 2001 From: Calum Date: Thu, 17 Aug 2017 12:11:36 +1200 Subject: [PATCH 1/6] Moved client side disconnection handling to this branch and reimplemented it with develop functionality. #refactor #issue[47] --- .../visualiser/ClientToServerThread.java | 93 ++++++------- .../java/seng302/visualiser/GameClient.java | 128 ++++++++++-------- .../java/seng302/visualiser/GameView.java | 60 ++++---- .../RegularPacketsTest.java | 6 +- src/test/java/steps/ToggleSailSteps.java | 2 +- 5 files changed, 155 insertions(+), 134 deletions(-) diff --git a/src/main/java/seng302/visualiser/ClientToServerThread.java b/src/main/java/seng302/visualiser/ClientToServerThread.java index dbf93693..10001869 100644 --- a/src/main/java/seng302/visualiser/ClientToServerThread.java +++ b/src/main/java/seng302/visualiser/ClientToServerThread.java @@ -5,7 +5,6 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.Socket; -import java.time.LocalDateTime; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -15,10 +14,6 @@ import java.util.TimerTask; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.zip.CRC32; import java.util.zip.Checksum; -import javafx.application.Platform; -import javafx.scene.control.Alert; -import javafx.scene.control.Alert.AlertType; -import javafx.scene.control.ButtonType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import seng302.gameServer.messages.BoatAction; @@ -46,16 +41,20 @@ public class ClientToServerThread implements Runnable { void newPacket(); } + @FunctionalInterface + public interface DisconnectedFromHostListener { + void notifYDisconnection (String message); + } + private class ByteReadException extends Exception { private ByteReadException(String message) { super(message); } } - private static final int LOG_LEVEL = 1; - private Queue streamPackets = new ConcurrentLinkedQueue<>(); private List listeners = new ArrayList<>(); + private List disconnectionListeners = new ArrayList<>(); private Thread thread; private Socket socket; @@ -72,7 +71,6 @@ public class ClientToServerThread implements Runnable { private int clientId = -1; -// private Boolean updateClient = true; private ByteArrayOutputStream crcBuffer; private boolean socketOpen = true; @@ -99,20 +97,6 @@ public class ClientToServerThread implements Runnable { thread.start(); } - /** - * Prints out log messages and the time happened. - * Only perform task if log level is below LOG_LEVEL variable. - * - * @param message a string of message to be printed out - * @param logLevel an int for log level - */ - static void clientLog(String message, int logLevel) { - if (logLevel <= LOG_LEVEL) { - System.out.println( - "[CLIENT " + LocalDateTime.now().toLocalTime().toString() + "] " + message); - } - } - /** * Perform the thread loop. It exits the loop if ClientState connected to host * variable is false. @@ -121,7 +105,7 @@ public class ClientToServerThread implements Runnable { int sync1; int sync2; // TODO: 14/07/17 wmu16 - Work out how to fix this while loop - while(socketOpen) { + while(!socket.isClosed() && socket.isConnected() && socketOpen) { try { crcBuffer = new ByteArrayOutputStream(); sync1 = readByte(); @@ -153,26 +137,25 @@ public class ClientToServerThread implements Runnable { } } } else { - clientLog("Packet has been dropped", 1); + logger.warn("Packet has been dropped", 1); } } } catch (ByteReadException e) { - e.printStackTrace(); + logger.warn("Byte read exception on ClientToServerThread", 1); closeSocket(); - Platform.runLater(() -> { - Alert alert = new Alert(AlertType.ERROR); - alert.setHeaderText("Host has disconnected"); - alert.setContentText("Cannot find Server"); - alert.showAndWait(); - }); - clientLog(e.getMessage(), 1); - return; + notifyDisconnectListeners("Connection to server was interrupted"); } } + logger.warn("Closed connection to server", 1); closeSocket(); - clientLog("Closed connection to Server", 0); + notifyDisconnectListeners("Connection to server was terminated"); } + private void notifyDisconnectListeners (String message) { + for (DisconnectedFromHostListener listener : disconnectionListeners) { + listener.notifYDisconnection(message); + } + } /** * Sends a request to the server asking for a source ID @@ -184,7 +167,8 @@ public class ClientToServerThread implements Runnable { os.write(requestMessage.getBuffer()); } catch (IOException e) { logger.error("Could not send registration request. Exiting"); - System.exit(1); + closeSocket(); + notifyDisconnectListeners("Failed to register with server"); } } @@ -200,7 +184,6 @@ public class ClientToServerThread implements Runnable { if (status.equals(RegistrationResponseStatus.SUCCESS_PLAYING)){ clientId = sourceId; - return; } @@ -214,11 +197,8 @@ public class ClientToServerThread implements Runnable { else{ alertErrorText = "Could not connect to server"; } - - Platform.runLater(() -> { - new Alert(AlertType.ERROR, alertErrorText, ButtonType.OK).showAndWait(); - System.exit(1); - }); + closeSocket(); + notifyDisconnectListeners(alertErrorText); } /** @@ -228,7 +208,7 @@ public class ClientToServerThread implements Runnable { * - MAINTAIN_HEADING = DOWNWIND and UPWIND packets stop being sent. * @param actionType The boat action that will dictate packets sent. */ - public void sendBoatAction(BoatAction actionType) { + public void sendBoatActionMessage(BoatAction actionType) { switch (actionType) { case MAINTAIN_HEADING: if (upwindTimerFlag) { @@ -249,7 +229,7 @@ public class ClientToServerThread implements Runnable { new TimerTask() { @Override public void run() { - sendBoatAction(new BoatActionMessage(BoatAction.DOWNWIND)); + sendBoatActionMessage(new BoatActionMessage(BoatAction.DOWNWIND)); } }, 0, PACKET_SENDING_INTERVAL_MS ); @@ -262,14 +242,14 @@ public class ClientToServerThread implements Runnable { new TimerTask() { @Override public void run() { - sendBoatAction(new BoatActionMessage(BoatAction.UPWIND)); + sendBoatActionMessage(new BoatActionMessage(BoatAction.UPWIND)); } }, 0, PACKET_SENDING_INTERVAL_MS ); } break; default: - sendBoatAction(new BoatActionMessage(actionType)); + sendBoatActionMessage(new BoatActionMessage(actionType)); break; } } @@ -287,12 +267,14 @@ public class ClientToServerThread implements Runnable { * Sends a boat action of the given message type. * @param message The given message type. */ - private void sendBoatAction(BoatActionMessage message) { + private void sendBoatActionMessage(BoatActionMessage message) { if (clientId != -1) { try { os.write(message.getBuffer()); } catch (IOException e) { - clientLog("Could not write to server", 1); + logger.warn("IOException on attempting to sendBoatActionMessage from Client"); + closeSocket(); + notifyDisconnectListeners("Cannot communicate with server"); } } } @@ -301,7 +283,7 @@ public class ClientToServerThread implements Runnable { try { socket.close(); } catch (IOException e) { - clientLog("Failed to close the socket", 1); + logger.warn("IOException on attempting to close ClientToServerSocket"); } } @@ -321,13 +303,23 @@ public class ClientToServerThread implements Runnable { listeners.remove(streamListener); } + public void addDisconnectionListener (DisconnectedFromHostListener listener) { + disconnectionListeners.add(listener); + } + + public void removeDisconnectionListener (DisconnectedFromHostListener listener) { + disconnectionListeners.remove(listener); + } + private int readByte() throws ByteReadException { int currentByte = -1; try { currentByte = is.read(); crcBuffer.write(currentByte); } catch (IOException e) { - clientLog("Read byte failed", 1); + logger.warn("IOException on readByte Client side", 1); + closeSocket(); + notifyDisconnectListeners("Cannot read from server."); } if (currentByte == -1) { throw new ByteReadException("InputStream reach end of stream"); @@ -349,8 +341,7 @@ public class ClientToServerThread implements Runnable { } } - public int getClientId () { + int getClientId () { return clientId; } - } diff --git a/src/main/java/seng302/visualiser/GameClient.java b/src/main/java/seng302/visualiser/GameClient.java index 82129046..8b8c7b79 100644 --- a/src/main/java/seng302/visualiser/GameClient.java +++ b/src/main/java/seng302/visualiser/GameClient.java @@ -10,6 +10,8 @@ import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.fxml.FXMLLoader; import javafx.scene.Node; +import javafx.scene.control.Alert; +import javafx.scene.control.Alert.AlertType; import javafx.scene.input.KeyEvent; import javafx.scene.layout.Pane; import seng302.gameServer.GameState; @@ -69,28 +71,31 @@ public class GameClient { */ public void runAsClient(String ipAddress, Integer portNumber) { try { - socketThread = new ClientToServerThread(ipAddress, portNumber); + startClientToServerThread(ipAddress, portNumber); + socketThread.addDisconnectionListener((cause) -> { + showConnectionError(cause); + Platform.runLater(this::loadStartScreen); + }); + socketThread.addStreamObserver(this::parsePackets); + LobbyController lobbyController = loadLobby(); + lobbyController.setPlayerListSource(clientLobbyList); + lobbyController.disableReadyButton(); + + if (regattaData != null){ + lobbyController.setTitle(regattaData.getRegattaName()); + lobbyController.setCourseName(regattaData.getCourseName()); + } + else{ + lobbyController.setTitle(ipAddress); + lobbyController.setCourseName(""); + } + + lobbyController.addCloseListener((exitCause) -> this.loadStartScreen()); + this.lobbyController = lobbyController; } catch (IOException ioe) { - ioe.printStackTrace(); - System.out.println("Unable to connect to host..."); + showConnectionError("Unable to find server"); + Platform.runLater(this::loadStartScreen); } - - socketThread.addStreamObserver(this::parsePackets); - LobbyController lobbyController = loadLobby(); - lobbyController.setPlayerListSource(clientLobbyList); - lobbyController.disableReadyButton(); - - if (regattaData != null){ - lobbyController.setTitle(regattaData.getRegattaName()); - lobbyController.setCourseName(regattaData.getCourseName()); - } - else{ - lobbyController.setTitle(ipAddress); - lobbyController.setCourseName(""); - } - - lobbyController.addCloseListener((exitCause) -> this.loadStartScreen()); - this.lobbyController = lobbyController; } /** @@ -99,38 +104,37 @@ public class GameClient { * @param portNumber Port to connect to. */ public void runAsHost(String ipAddress, Integer portNumber) { - server = new MainServerThread(); try { - socketThread = new ClientToServerThread(ipAddress, portNumber); - } catch (IOException ioe) { - ioe.printStackTrace(); - System.out.println("Unable to make local connection to host..."); - } - socketThread.addStreamObserver(this::parsePackets); - LobbyController lobbyController = loadLobby(); - lobbyController.setPlayerListSource(clientLobbyList); + startClientToServerThread(ipAddress, portNumber); + socketThread.addDisconnectionListener((cause) -> { + Platform.runLater(this::loadStartScreen); + }); + LobbyController lobbyController = loadLobby(); + lobbyController.setPlayerListSource(clientLobbyList); - if (regattaData != null){ - lobbyController.setTitle("Hosting: " + regattaData.getRegattaName()); - lobbyController.setCourseName(regattaData.getCourseName()); - } - else{ - lobbyController.setTitle("Hosting: " + ipAddress); - lobbyController.setCourseName(""); - } - - lobbyController.addCloseListener(exitCause -> { - if (exitCause == CloseStatus.READY) { - GameState.resetStartTime(); - lobbyController.disableReadyButton(); - server.startGame(); - } else if (exitCause == CloseStatus.LEAVE) { - loadStartScreen(); + if (regattaData != null) { + lobbyController.setTitle("Hosting: " + regattaData.getRegattaName()); + lobbyController.setCourseName(regattaData.getCourseName()); + } else { + lobbyController.setTitle("Hosting: " + ipAddress); + lobbyController.setCourseName(""); } - }); - this.lobbyController = lobbyController; - server.setGameClient(this); + lobbyController.addCloseListener(exitCause -> { + if (exitCause == CloseStatus.READY) { + GameState.resetStartTime(); + lobbyController.disableReadyButton(); + server.startGame(); + } else if (exitCause == CloseStatus.LEAVE) { + loadStartScreen(); + } + }); + this.lobbyController = lobbyController; + server.setGameClient(this); + } catch (IOException ioe) { + showConnectionError("Cannot connect to server as host"); + Platform.runLater(this::loadStartScreen); + } } private void loadStartScreen() { @@ -145,10 +149,24 @@ public class GameClient { holderPane.getChildren().clear(); holderPane.getChildren().add(fxmlLoader.load()); } catch (IOException e) { - e.printStackTrace(); + showConnectionError("JavaFX crashed. Please restart the app"); } } + private void showConnectionError (String message) { + Platform.runLater(() -> { + Alert alert = new Alert(AlertType.ERROR); + alert.setHeaderText("Connection Error"); + alert.setContentText(message); + alert.showAndWait(); + }); + } + + private void startClientToServerThread (String ipAddress, int portNumber) throws IOException { + socketThread = new ClientToServerThread(ipAddress, portNumber); + socketThread.addStreamObserver(this::parsePackets); + } + /** * Loads a view of the lobby into the clients pane * @@ -353,13 +371,13 @@ public class GameClient { private void keyPressed(KeyEvent e) { switch (e.getCode()) { case SPACE: // align with vmg - socketThread.sendBoatAction(BoatAction.VMG); break; + socketThread.sendBoatActionMessage(BoatAction.VMG); break; case PAGE_UP: // upwind - socketThread.sendBoatAction(BoatAction.UPWIND); break; + socketThread.sendBoatActionMessage(BoatAction.UPWIND); break; case PAGE_DOWN: // downwind - socketThread.sendBoatAction(BoatAction.DOWNWIND); break; + socketThread.sendBoatActionMessage(BoatAction.DOWNWIND); break; case ENTER: // tack/gybe - socketThread.sendBoatAction(BoatAction.TACK_GYBE); break; + socketThread.sendBoatActionMessage(BoatAction.TACK_GYBE); break; } } @@ -368,12 +386,12 @@ public class GameClient { switch (e.getCode()) { //TODO 12/07/17 Determine the sail state and send the appropriate packet (eg. if sails are in, send a sail out packet) case SHIFT: // sails in/sails out - socketThread.sendBoatAction(BoatAction.SAILS_IN); + socketThread.sendBoatActionMessage(BoatAction.SAILS_IN); allBoatsMap.get(socketThread.getClientId()).toggleSail(); break; case PAGE_UP: case PAGE_DOWN: - socketThread.sendBoatAction(BoatAction.MAINTAIN_HEADING); break; + socketThread.sendBoatActionMessage(BoatAction.MAINTAIN_HEADING); break; } } diff --git a/src/main/java/seng302/visualiser/GameView.java b/src/main/java/seng302/visualiser/GameView.java index 9268f3c1..3bd0ea14 100644 --- a/src/main/java/seng302/visualiser/GameView.java +++ b/src/main/java/seng302/visualiser/GameView.java @@ -277,7 +277,31 @@ public class GameView extends Pane { colour = Color.BLACK; } - //Creating mark arrows. + createMarkArrows(sequence); + + //Scale race to markers if there is no border. + if (borderPoints == null) { + rescaleRace(new ArrayList<>(markerObjects.keySet())); + } + //Move the Markers to initial position. + markerObjects.forEach(((mark, marker) -> { + Point2D p2d = findScaledXY(mark.getLat(), mark.getLng()); + marker.setLayoutX(p2d.getX()); + marker.setLayoutY(p2d.getY()); + })); + Platform.runLater(() -> { + markers.getChildren().clear(); + markers.getChildren().addAll(gates); + markers.getChildren().addAll(markerObjects.values()); + }); + } + + /** + * Calculates all the data needed for to create mark arrows. Requires that a course has been + * added to the gameview. + * @param sequence The order in which marks are traversed. + */ + private void createMarkArrows (List sequence) { for (int i=1; i < sequence.size()-1; i++) { //General case. double averageLat = 0; double averageLng = 0; @@ -297,7 +321,7 @@ public class GameView extends Pane { averageLng += mark.getLng(); } GeoPoint nextMarkAv = new GeoPoint(averageLat / numMarks, averageLng / numMarks); - // TODO: 16/08/17 This comparison is cancer and deserves to die. + // TODO: 16/08/17 This comparison doesn't need to exist but the alternative is to user server enum client side. for (Mark mark : course.get(i).getMarks()) { markerObjects.get(mark).addArrows( mark.getRoundingSide() == RoundingSide.STARBOARD ? MarkArrowFactory.RoundingSide.STARBOARD : MarkArrowFactory.RoundingSide.PORT, @@ -306,9 +330,11 @@ public class GameView extends Pane { ); } } + createStartLineArrows(); + createFinishLineArrows(); + } - // TODO: 16/08/17 Make this cleaner - //First mark case + private void createStartLineArrows () { double averageLat = 0; double averageLng = 0; int numMarks = 0; @@ -325,10 +351,12 @@ public class GameView extends Pane { GeoUtility.getBearing(mark, firstMarkAv) ); } - //Last Mark case - numMarks = 0; - averageLat = 0; - averageLng = 0; + } + + private void createFinishLineArrows () { + double numMarks = 0; + double averageLat = 0; + double averageLng = 0; for (Mark mark : course.get(course.size()-2).getMarks()) { numMarks += 1; averageLat += mark.getLat(); @@ -342,22 +370,6 @@ public class GameView extends Pane { GeoUtility.getBearing(mark, mark) ); } - - //Scale race to markers if there is no border. - if (borderPoints == null) { - rescaleRace(new ArrayList<>(markerObjects.keySet())); - } - //Move the Markers to initial position. - markerObjects.forEach(((mark, marker) -> { - Point2D p2d = findScaledXY(mark.getLat(), mark.getLng()); - marker.setLayoutX(p2d.getX()); - marker.setLayoutY(p2d.getY()); - })); - Platform.runLater(() -> { - markers.getChildren().clear(); - markers.getChildren().addAll(gates); - markers.getChildren().addAll(markerObjects.values()); - }); } /** diff --git a/src/test/java/seng302/visualiser/ClientToServerTests/RegularPacketsTest.java b/src/test/java/seng302/visualiser/ClientToServerTests/RegularPacketsTest.java index d7e2610e..68ff7b11 100644 --- a/src/test/java/seng302/visualiser/ClientToServerTests/RegularPacketsTest.java +++ b/src/test/java/seng302/visualiser/ClientToServerTests/RegularPacketsTest.java @@ -30,12 +30,12 @@ public class RegularPacketsTest { // ServerYacht yacht = new ArrayList<>(GameState.getYachts().values()).get(0); // double startAngle = yacht.getHeading(); // long startTime = System.currentTimeMillis(); -// clientThread.sendBoatAction(BoatAction.UPWIND); +// clientThread.sendBoatActionMessage(BoatAction.UPWIND); // Thread.sleep(200); // while (Math.abs(yacht.getHeading() - startAngle) < TEST_DISTANCE) { // Thread.sleep(1); // } -// clientThread.sendBoatAction(BoatAction.MAINTAIN_HEADING); +// clientThread.sendBoatActionMessage(BoatAction.MAINTAIN_HEADING); // long endTime = System.currentTimeMillis(); // SleepThreadMaxDelay(); // //Allowed to be two loops of delay due to loop delay and processing delay at client + server ends. @@ -50,7 +50,7 @@ public class RegularPacketsTest { // SleepThreadMaxDelay(); // Yacht yacht = new ArrayList<>(GameState.getYachts().values()).get(0); // boolean startState = yacht.getSailIn(); -// clientThread.sendBoatAction(BoatAction.SAILS_IN); +// clientThread.sendBoatActionMessage(BoatAction.SAILS_IN); // SleepThreadMaxDelay(); // Assert.assertEquals(startState, !yacht.getSailIn()); // } diff --git a/src/test/java/steps/ToggleSailSteps.java b/src/test/java/steps/ToggleSailSteps.java index e4c6abed..cd4caeea 100644 --- a/src/test/java/steps/ToggleSailSteps.java +++ b/src/test/java/steps/ToggleSailSteps.java @@ -36,7 +36,7 @@ public class ToggleSailSteps { public void the_user_has_pressed(String arg1) throws Throwable { startTime = System.currentTimeMillis(); if (arg1 == "shift") { - client.sendBoatAction(BoatAction.SAILS_IN); + client.sendBoatActionMessage(BoatAction.SAILS_IN); } } From 0276911b88d99501b5ceb05d44a9938d08b81400 Mon Sep 17 00:00:00 2001 From: Calum Date: Thu, 17 Aug 2017 14:02:18 +1200 Subject: [PATCH 2/6] Fixed disconnection issues. Fix is only temporary until a consistent interface for disconnections exists. #bug #implement --- .../seng302/gameServer/MainServerThread.java | 17 +++--- .../gameServer/ServerListenThread.java | 2 +- .../gameServer/ServerToClientThread.java | 54 ++++++++++++------- .../visualiser/ClientToServerThread.java | 23 ++++---- .../java/seng302/visualiser/GameClient.java | 15 +++--- .../DisconnectionTest.java | 20 +++++++ .../RegularPacketsTest.java | 2 +- src/test/java/steps/ToggleSailSteps.java | 2 +- 8 files changed, 92 insertions(+), 43 deletions(-) create mode 100644 src/test/java/seng302/visualiser/ClientToServerTests/DisconnectionTest.java diff --git a/src/main/java/seng302/gameServer/MainServerThread.java b/src/main/java/seng302/gameServer/MainServerThread.java index f4dc387d..9cbcb8ca 100644 --- a/src/main/java/seng302/gameServer/MainServerThread.java +++ b/src/main/java/seng302/gameServer/MainServerThread.java @@ -93,6 +93,9 @@ public class MainServerThread implements Runnable, ClientConnectionDelegate { // TODO: 14/07/17 wmu16 - Send out disconnect packet to clients try { + for (ServerToClientThread serverToClientThread : serverToClientThreads) { + serverToClientThread.terminate(); + } serverSocket.close(); return; } catch (IOException e) { @@ -173,6 +176,7 @@ public class MainServerThread implements Runnable, ClientConnectionDelegate { thread.sendSetupMessages(); } }); + serverToClientThread.addDisconnectListener(this::clientDisconnected); } /** @@ -182,11 +186,11 @@ public class MainServerThread implements Runnable, ClientConnectionDelegate { */ @Override public void clientDisconnected(Player player) { - try { - player.getSocket().close(); - } catch (Exception e) { - serverLog("Cannot disconnect the socket for the disconnected player.", 0); - } +// try { +// player.getSocket().close(); +// } catch (Exception e) { +// serverLog("Cannot disconnect the socket for the disconnected player.", 0); +// } serverLog("Player " + player.getYacht().getSourceId() + "'s socket disconnected", 0); GameState.removeYacht(player.getYacht().getSourceId()); GameState.removePlayer(player); @@ -194,11 +198,12 @@ public class MainServerThread implements Runnable, ClientConnectionDelegate { for (ServerToClientThread serverToClientThread : serverToClientThreads) { if (serverToClientThread.getSocket() == player.getSocket()) { closedConnection = serverToClientThread; - } else { + } else if (GameState.getCurrentStage() != GameStages.RACING){ serverToClientThread.sendSetupMessages(); } } serverToClientThreads.remove(closedConnection); + closedConnection.terminate(); } public void startGame() { diff --git a/src/main/java/seng302/gameServer/ServerListenThread.java b/src/main/java/seng302/gameServer/ServerListenThread.java index e05b76a9..2c220ca2 100644 --- a/src/main/java/seng302/gameServer/ServerListenThread.java +++ b/src/main/java/seng302/gameServer/ServerListenThread.java @@ -38,7 +38,7 @@ public class ServerListenThread implements Runnable { } public void run(){ - while (true){ + while (serverSocket != null && !serverSocket.isClosed()){ acceptConnection(); } } diff --git a/src/main/java/seng302/gameServer/ServerToClientThread.java b/src/main/java/seng302/gameServer/ServerToClientThread.java index 479333bd..088fbd14 100644 --- a/src/main/java/seng302/gameServer/ServerToClientThread.java +++ b/src/main/java/seng302/gameServer/ServerToClientThread.java @@ -19,26 +19,22 @@ import java.util.zip.CRC32; import java.util.zip.Checksum; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import seng302.gameServer.messages.BoatAction; +import seng302.gameServer.messages.BoatLocationMessage; +import seng302.gameServer.messages.ClientType; +import seng302.gameServer.messages.Message; +import seng302.gameServer.messages.RegistrationResponseMessage; +import seng302.gameServer.messages.RegistrationResponseStatus; +import seng302.gameServer.messages.XMLMessage; +import seng302.gameServer.messages.XMLMessageSubType; import seng302.gameServer.messages.YachtEventCodeMessage; import seng302.model.Player; +import seng302.model.ServerYacht; import seng302.model.stream.packets.PacketType; import seng302.model.stream.packets.StreamPacket; import seng302.model.stream.xml.generator.Race; import seng302.model.stream.xml.generator.Regatta; import seng302.utilities.XMLGenerator; -import seng302.gameServer.messages.BoatAction; -import seng302.gameServer.messages.BoatLocationMessage; -import seng302.gameServer.messages.BoatSubMessage; -import seng302.gameServer.messages.ClientType; -import seng302.gameServer.messages.Message; -import seng302.gameServer.messages.RaceStatus; -import seng302.gameServer.messages.RaceStatusMessage; -import seng302.gameServer.messages.RaceType; -import seng302.gameServer.messages.RegistrationResponseMessage; -import seng302.gameServer.messages.RegistrationResponseStatus; -import seng302.gameServer.messages.XMLMessage; -import seng302.gameServer.messages.XMLMessageSubType; -import seng302.model.ServerYacht; /** * A class describing a single connection to a Client for the purposes of sending and receiving on @@ -55,6 +51,12 @@ public class ServerToClientThread implements Runnable, Observer { void notifyConnection (); } + // TODO: 17/08/17 this is only temporary disconnects should be handled consistently + @FunctionalInterface + interface DisconnectListener { + void notifyDisconnect (Player player); + } + private Logger logger = LoggerFactory.getLogger(ServerToClientThread.class); private Thread thread; @@ -74,8 +76,10 @@ public class ServerToClientThread implements Runnable, Observer { private XMLGenerator xml; private List connectionListeners = new ArrayList<>(); + private DisconnectListener disconnectListener; private ServerYacht yacht; + private Player player; public ServerToClientThread(Socket socket) { this.socket = socket; @@ -122,8 +126,9 @@ public class ServerToClientThread implements Runnable, Observer { ); yacht.addObserver(this); // TODO: yacht can notify mark rounding message hyi25 13/8/17 + player = new Player(socket, yacht); GameState.addYacht(sourceId, yacht); - GameState.addPlayer(new Player(socket, yacht)); + GameState.addPlayer(player); } @Override @@ -169,8 +174,7 @@ public class ServerToClientThread implements Runnable, Observer { int sync2; // TODO: 14/07/17 wmu16 - Work out how to fix this while loop - while (socket.isConnected()) { - + while (socket.isConnected() && !socket.isClosed()) { try { crcBuffer = new ByteArrayOutputStream(); sync1 = readByte(); @@ -213,6 +217,7 @@ public class ServerToClientThread implements Runnable, Observer { return; } } + logger.warn("Closed serverToClientThread" + thread, 1); } public void sendSetupMessages() { @@ -252,11 +257,12 @@ public class ServerToClientThread implements Runnable, Observer { private int readByte() throws Exception { int currentByte = -1; try { - // @TODO @FIX ConnectionReset Exception when a client disconnects before it is garbage collected currentByte = is.read(); crcBuffer.write(currentByte); + } catch (SocketException se) { + disconnectListener.notifyDisconnect(this.player); } catch (IOException e) { - e.printStackTrace(); + disconnectListener.notifyDisconnect(this.player); logger.warn("Socket read failed", 1); } if (currentByte == -1) { @@ -335,4 +341,16 @@ public class ServerToClientThread implements Runnable, Observer { public void removeConnectionListener(ConnectionListener listener) { connectionListeners.remove(listener); } + + public void terminate () { + try { + socket.close(); + } catch (IOException ioe) { + logger.warn("IOException attempting to terminate serverToClientThread " + this.thread); + } + } + + public void addDisconnectListener(DisconnectListener disconnectListener) { + this.disconnectListener = disconnectListener; + } } diff --git a/src/main/java/seng302/visualiser/ClientToServerThread.java b/src/main/java/seng302/visualiser/ClientToServerThread.java index 10001869..3655f080 100644 --- a/src/main/java/seng302/visualiser/ClientToServerThread.java +++ b/src/main/java/seng302/visualiser/ClientToServerThread.java @@ -142,18 +142,20 @@ public class ClientToServerThread implements Runnable { } } catch (ByteReadException e) { logger.warn("Byte read exception on ClientToServerThread", 1); - closeSocket(); notifyDisconnectListeners("Connection to server was interrupted"); + closeSocket(); } } logger.warn("Closed connection to server", 1); - closeSocket(); notifyDisconnectListeners("Connection to server was terminated"); + closeSocket(); } private void notifyDisconnectListeners (String message) { - for (DisconnectedFromHostListener listener : disconnectionListeners) { - listener.notifYDisconnection(message); + if (socketOpen) { + for (DisconnectedFromHostListener listener : disconnectionListeners) { + listener.notifYDisconnection(message); + } } } @@ -167,8 +169,8 @@ public class ClientToServerThread implements Runnable { os.write(requestMessage.getBuffer()); } catch (IOException e) { logger.error("Could not send registration request. Exiting"); - closeSocket(); notifyDisconnectListeners("Failed to register with server"); + closeSocket(); } } @@ -197,8 +199,8 @@ public class ClientToServerThread implements Runnable { else{ alertErrorText = "Could not connect to server"; } - closeSocket(); notifyDisconnectListeners(alertErrorText); + closeSocket(); } /** @@ -208,7 +210,7 @@ public class ClientToServerThread implements Runnable { * - MAINTAIN_HEADING = DOWNWIND and UPWIND packets stop being sent. * @param actionType The boat action that will dictate packets sent. */ - public void sendBoatActionMessage(BoatAction actionType) { + public void sendBoatAction(BoatAction actionType) { switch (actionType) { case MAINTAIN_HEADING: if (upwindTimerFlag) { @@ -272,9 +274,9 @@ public class ClientToServerThread implements Runnable { try { os.write(message.getBuffer()); } catch (IOException e) { - logger.warn("IOException on attempting to sendBoatActionMessage from Client"); - closeSocket(); + logger.warn("IOException on attempting to sendBoatAction from Client"); notifyDisconnectListeners("Cannot communicate with server"); + closeSocket(); } } } @@ -282,6 +284,7 @@ public class ClientToServerThread implements Runnable { private void closeSocket() { try { socket.close(); + socketOpen = false; } catch (IOException e) { logger.warn("IOException on attempting to close ClientToServerSocket"); } @@ -318,8 +321,8 @@ public class ClientToServerThread implements Runnable { crcBuffer.write(currentByte); } catch (IOException e) { logger.warn("IOException on readByte Client side", 1); - closeSocket(); notifyDisconnectListeners("Cannot read from server."); + closeSocket(); } if (currentByte == -1) { throw new ByteReadException("InputStream reach end of stream"); diff --git a/src/main/java/seng302/visualiser/GameClient.java b/src/main/java/seng302/visualiser/GameClient.java index 68cd120c..e55fa180 100644 --- a/src/main/java/seng302/visualiser/GameClient.java +++ b/src/main/java/seng302/visualiser/GameClient.java @@ -104,6 +104,7 @@ public class GameClient { * @param portNumber Port to connect to. */ public void runAsHost(String ipAddress, Integer portNumber) { + server = new MainServerThread(); try { startClientToServerThread(ipAddress, portNumber); socketThread.addDisconnectionListener((cause) -> { @@ -126,6 +127,8 @@ public class GameClient { lobbyController.disableReadyButton(); server.startGame(); } else if (exitCause == CloseStatus.LEAVE) { + server.terminate(); + server = null; loadStartScreen(); } }); @@ -370,13 +373,13 @@ public class GameClient { private void keyPressed(KeyEvent e) { switch (e.getCode()) { case SPACE: // align with vmg - socketThread.sendBoatActionMessage(BoatAction.VMG); break; + socketThread.sendBoatAction(BoatAction.VMG); break; case PAGE_UP: // upwind - socketThread.sendBoatActionMessage(BoatAction.UPWIND); break; + socketThread.sendBoatAction(BoatAction.UPWIND); break; case PAGE_DOWN: // downwind - socketThread.sendBoatActionMessage(BoatAction.DOWNWIND); break; + socketThread.sendBoatAction(BoatAction.DOWNWIND); break; case ENTER: // tack/gybe - socketThread.sendBoatActionMessage(BoatAction.TACK_GYBE); break; + socketThread.sendBoatAction(BoatAction.TACK_GYBE); break; } } @@ -385,12 +388,12 @@ public class GameClient { switch (e.getCode()) { //TODO 12/07/17 Determine the sail state and send the appropriate packet (eg. if sails are in, send a sail out packet) case SHIFT: // sails in/sails out - socketThread.sendBoatActionMessage(BoatAction.SAILS_IN); + socketThread.sendBoatAction(BoatAction.SAILS_IN); allBoatsMap.get(socketThread.getClientId()).toggleSail(); break; case PAGE_UP: case PAGE_DOWN: - socketThread.sendBoatActionMessage(BoatAction.MAINTAIN_HEADING); break; + socketThread.sendBoatAction(BoatAction.MAINTAIN_HEADING); break; } } diff --git a/src/test/java/seng302/visualiser/ClientToServerTests/DisconnectionTest.java b/src/test/java/seng302/visualiser/ClientToServerTests/DisconnectionTest.java new file mode 100644 index 00000000..6bc3e9ad --- /dev/null +++ b/src/test/java/seng302/visualiser/ClientToServerTests/DisconnectionTest.java @@ -0,0 +1,20 @@ +package seng302.visualiser.ClientToServerTests; + +import org.junit.Assert; +import org.junit.Test; +import seng302.gameServer.MainServerThread; +import seng302.visualiser.ClientToServerThread; + +/** + * Created by cir27 on 17/08/17. + */ +public class DisconnectionTest { + @Test + public void testServerDisconnection () throws Exception { + MainServerThread serverThread = new MainServerThread(); + ClientToServerThread clientThread = new ClientToServerThread("localhost", 4942); + Thread.sleep(1000); + clientThread.addDisconnectionListener(message -> Assert.assertTrue(message != null)); + serverThread.terminate(); + } +} diff --git a/src/test/java/seng302/visualiser/ClientToServerTests/RegularPacketsTest.java b/src/test/java/seng302/visualiser/ClientToServerTests/RegularPacketsTest.java index 70e47e79..f3293606 100644 --- a/src/test/java/seng302/visualiser/ClientToServerTests/RegularPacketsTest.java +++ b/src/test/java/seng302/visualiser/ClientToServerTests/RegularPacketsTest.java @@ -61,7 +61,7 @@ public class RegularPacketsTest { // SleepThreadMaxDelay(); // ServerYacht yacht = new ArrayList<>(GameState.getYachts().values()).get(0); // boolean startState = yacht.getSailIn(); -// clientThread.sendBoatActionMessage(BoatAction.SAILS_IN); +// clientThread.sendBoatAction(BoatAction.SAILS_IN); // SleepThreadMaxDelay(); // Assert.assertEquals(startState, !yacht.getSailIn()); // } diff --git a/src/test/java/steps/ToggleSailSteps.java b/src/test/java/steps/ToggleSailSteps.java index cd4caeea..e4c6abed 100644 --- a/src/test/java/steps/ToggleSailSteps.java +++ b/src/test/java/steps/ToggleSailSteps.java @@ -36,7 +36,7 @@ public class ToggleSailSteps { public void the_user_has_pressed(String arg1) throws Throwable { startTime = System.currentTimeMillis(); if (arg1 == "shift") { - client.sendBoatActionMessage(BoatAction.SAILS_IN); + client.sendBoatAction(BoatAction.SAILS_IN); } } From 3639e0d3ce428d6ce8481f9580679b71d00c7249 Mon Sep 17 00:00:00 2001 From: Calum Date: Thu, 17 Aug 2017 14:10:38 +1200 Subject: [PATCH 3/6] Fixed disconnection issues. Fix is only temporary until a consistent interface for disconnections exists. #bug #implement --- src/main/java/seng302/gameServer/GameState.java | 1 - .../java/seng302/gameServer/ServerToClientThread.java | 10 ++++++---- .../visualiser/controllers/RaceViewController.java | 1 - src/main/java/seng302/visualiser/fxObjects/Marker.java | 10 +++------- src/test/java/seng302/utilities/GeoUtilityTest.java | 1 - 5 files changed, 9 insertions(+), 14 deletions(-) diff --git a/src/main/java/seng302/gameServer/GameState.java b/src/main/java/seng302/gameServer/GameState.java index 604694e1..3e88b7f2 100644 --- a/src/main/java/seng302/gameServer/GameState.java +++ b/src/main/java/seng302/gameServer/GameState.java @@ -423,7 +423,6 @@ public class GameState implements Runnable { private void checkForLegProgression(ServerYacht yacht) { Integer currentMarkSeqID = yacht.getCurrentMarkSeqID(); CompoundMark currentMark = markOrder.getCurrentMark(currentMarkSeqID); -// System.out.println(yacht.getCurrentMarkSeqID()); Boolean hasProgressed; if (currentMarkSeqID == 0) { diff --git a/src/main/java/seng302/gameServer/ServerToClientThread.java b/src/main/java/seng302/gameServer/ServerToClientThread.java index 088fbd14..75907275 100644 --- a/src/main/java/seng302/gameServer/ServerToClientThread.java +++ b/src/main/java/seng302/gameServer/ServerToClientThread.java @@ -192,7 +192,6 @@ public class ServerToClientThread implements Runnable, Observer { long computedCrc = checksum.getValue(); long packetCrc = Message.bytesToLong(getBytes(4)); if (computedCrc == packetCrc) { - //System.out.println("RECEIVED A PACKET"); switch (PacketType.assignPacketType(type, payload)) { case BOAT_ACTION: BoatAction actionType = ServerPacketParser @@ -229,7 +228,11 @@ public class ServerToClientThread implements Runnable, Observer { } //@TODO calculate lat/lng values - xml.setRegatta(new Regatta("Party Parrot Test Server", "Bermuda Test Course", 57.6679590, 11.8503233)); + xml.setRegatta( + new Regatta( + "Party Parrot Test Server", "Bermuda Test Course", + 57.6679590, 11.8503233) + ); xml.setRace(race); XMLMessage xmlMessage; @@ -289,8 +292,7 @@ public class ServerToClientThread implements Runnable, Observer { try { os.write(message.getBuffer()); } catch (SocketException e) { - //serverLog("Player " + sourceId + " side socket disconnected", 1); - return; + logger.warn("Player " + sourceId + " side socket disconnected", 1); } catch (IOException e) { logger.warn("Message send failed", 1); } diff --git a/src/main/java/seng302/visualiser/controllers/RaceViewController.java b/src/main/java/seng302/visualiser/controllers/RaceViewController.java index b4c690c8..7c762c85 100644 --- a/src/main/java/seng302/visualiser/controllers/RaceViewController.java +++ b/src/main/java/seng302/visualiser/controllers/RaceViewController.java @@ -410,7 +410,6 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel List vboxEntries = new ArrayList<>(); for (int i = 0; i < yachts.size(); i++) { -// System.out.println("yacht == null " + String.valueOf(yacht == null)); if (yachts.get(i).getBoatStatus() == 3) { // 3 is finish status Text textToAdd = new Text(i + 1 + ". " + yachts.get(i).getShortName() + " (Finished)"); diff --git a/src/main/java/seng302/visualiser/fxObjects/Marker.java b/src/main/java/seng302/visualiser/fxObjects/Marker.java index 64923b51..297a9c34 100644 --- a/src/main/java/seng302/visualiser/fxObjects/Marker.java +++ b/src/main/java/seng302/visualiser/fxObjects/Marker.java @@ -49,17 +49,13 @@ public class Marker extends Group { */ public void addArrows(MarkArrowFactory.RoundingSide roundingSide, double entryAngle, double exitAngle) { - + //Change Color.GRAY to this.colour to revert all gray arrows. enterArrows.add( - MarkArrowFactory.constructEntryArrow(roundingSide, entryAngle, exitAngle, colour) + MarkArrowFactory.constructEntryArrow(roundingSide, entryAngle, exitAngle, Color.GRAY) ); exitArrows.add( - MarkArrowFactory.constructExitArrow(roundingSide, exitAngle, colour) + MarkArrowFactory.constructExitArrow(roundingSide, exitAngle, Color.GRAY) ); -// Platform.runLater(() -> { -// this.getChildren().add(enterArrows.get(enterArrows.size()-1)); -// this.getChildren().add(exitArrows.get(exitArrows.size()-1)); -// }); } /** diff --git a/src/test/java/seng302/utilities/GeoUtilityTest.java b/src/test/java/seng302/utilities/GeoUtilityTest.java index a9a60148..936b5983 100644 --- a/src/test/java/seng302/utilities/GeoUtilityTest.java +++ b/src/test/java/seng302/utilities/GeoUtilityTest.java @@ -140,7 +140,6 @@ public class GeoUtilityTest { // for (int i = 0; i < 100000; i++) { // assertTrue(GeoUtility.isPointInTriangle(v1, v2, v3, p1)); // } -// System.out.println((System.nanoTime() - startTime) / 1000000000.0); // test for different orders of vertices, which should not affect the result assertTrue(GeoUtility.isPointInTriangle(v2, v1, v3, p1)); From d41bdfebbcdd3ed5d1815d45422be1dc76b59a32 Mon Sep 17 00:00:00 2001 From: Calum Date: Thu, 17 Aug 2017 14:21:06 +1200 Subject: [PATCH 4/6] Merged Boat customization onto develop. #bug #merge #refactor --- src/main/java/seng302/visualiser/GameClient.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main/java/seng302/visualiser/GameClient.java b/src/main/java/seng302/visualiser/GameClient.java index f3240f4f..eabe0b8d 100644 --- a/src/main/java/seng302/visualiser/GameClient.java +++ b/src/main/java/seng302/visualiser/GameClient.java @@ -18,7 +18,6 @@ import seng302.gameServer.GameState; import seng302.gameServer.MainServerThread; import seng302.gameServer.messages.BoatAction; import seng302.gameServer.messages.BoatStatus; -import seng302.gameServer.messages.BoatAction; import seng302.model.ClientYacht; import seng302.model.RaceState; import seng302.model.stream.packets.StreamPacket; @@ -79,9 +78,10 @@ public class GameClient { }); socketThread.addStreamObserver(this::parsePackets); LobbyController lobbyController = loadLobby(); + lobbyController.setSocketThread(socketThread); + lobbyController.setPlayerID(socketThread.getClientId()); lobbyController.setPlayerListSource(clientLobbyList); lobbyController.disableReadyButton(); - if (regattaData != null){ lobbyController.setTitle(regattaData.getRegattaName()); lobbyController.setCourseName(regattaData.getCourseName()); @@ -112,8 +112,9 @@ public class GameClient { Platform.runLater(this::loadStartScreen); }); LobbyController lobbyController = loadLobby(); + lobbyController.setSocketThread(socketThread); + lobbyController.setPlayerID(socketThread.getClientId()); lobbyController.setPlayerListSource(clientLobbyList); - if (regattaData != null) { lobbyController.setTitle("Hosting: " + regattaData.getRegattaName()); lobbyController.setCourseName(regattaData.getCourseName()); From 72a390b484e653e6f8e0abc088166924a7b51d32 Mon Sep 17 00:00:00 2001 From: Alistair McIntyre Date: Thu, 17 Aug 2017 14:47:20 +1200 Subject: [PATCH 5/6] Fixed build bug. tags : #issue[47] --- src/main/java/seng302/visualiser/ClientToServerThread.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/seng302/visualiser/ClientToServerThread.java b/src/main/java/seng302/visualiser/ClientToServerThread.java index 5b16e21a..3d6152d8 100644 --- a/src/main/java/seng302/visualiser/ClientToServerThread.java +++ b/src/main/java/seng302/visualiser/ClientToServerThread.java @@ -338,7 +338,7 @@ public class ClientToServerThread implements Runnable { closeSocket(); } if (currentByte == -1) { - clientLog("InputStream reach end of stream", 1); + logger.warn("InputStream reach end of stream", 1); } return currentByte; } From d867a4b7a229826e60bc53c251032bc6dc7cac4e Mon Sep 17 00:00:00 2001 From: Alistair McIntyre Date: Thu, 17 Aug 2017 14:53:54 +1200 Subject: [PATCH 6/6] Fixed bug, should fix disconnection issues. tags : #issue[47] --- src/main/java/seng302/visualiser/ClientToServerThread.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/seng302/visualiser/ClientToServerThread.java b/src/main/java/seng302/visualiser/ClientToServerThread.java index 3d6152d8..57256760 100644 --- a/src/main/java/seng302/visualiser/ClientToServerThread.java +++ b/src/main/java/seng302/visualiser/ClientToServerThread.java @@ -338,6 +338,8 @@ public class ClientToServerThread implements Runnable { closeSocket(); } if (currentByte == -1) { + notifyDisconnectListeners("Cannot read from server."); + closeSocket(); logger.warn("InputStream reach end of stream", 1); } return currentByte;