Moved client side disconnection handling to this branch and reimplemented it with develop functionality.

#refactor #issue[47]
This commit is contained in:
Calum
2017-08-17 12:11:36 +12:00
parent 7b4a70817b
commit 769d1956b3
5 changed files with 155 additions and 134 deletions
@@ -5,7 +5,6 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.net.Socket; import java.net.Socket;
import java.time.LocalDateTime;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
@@ -15,10 +14,6 @@ import java.util.TimerTask;
import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.zip.CRC32; import java.util.zip.CRC32;
import java.util.zip.Checksum; 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.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import seng302.gameServer.messages.BoatAction; import seng302.gameServer.messages.BoatAction;
@@ -46,16 +41,20 @@ public class ClientToServerThread implements Runnable {
void newPacket(); void newPacket();
} }
@FunctionalInterface
public interface DisconnectedFromHostListener {
void notifYDisconnection (String message);
}
private class ByteReadException extends Exception { private class ByteReadException extends Exception {
private ByteReadException(String message) { private ByteReadException(String message) {
super(message); super(message);
} }
} }
private static final int LOG_LEVEL = 1;
private Queue<StreamPacket> streamPackets = new ConcurrentLinkedQueue<>(); private Queue<StreamPacket> streamPackets = new ConcurrentLinkedQueue<>();
private List<ClientSocketListener> listeners = new ArrayList<>(); private List<ClientSocketListener> listeners = new ArrayList<>();
private List<DisconnectedFromHostListener> disconnectionListeners = new ArrayList<>();
private Thread thread; private Thread thread;
private Socket socket; private Socket socket;
@@ -72,7 +71,6 @@ public class ClientToServerThread implements Runnable {
private int clientId = -1; private int clientId = -1;
// private Boolean updateClient = true;
private ByteArrayOutputStream crcBuffer; private ByteArrayOutputStream crcBuffer;
private boolean socketOpen = true; private boolean socketOpen = true;
@@ -99,20 +97,6 @@ public class ClientToServerThread implements Runnable {
thread.start(); 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 * Perform the thread loop. It exits the loop if ClientState connected to host
* variable is false. * variable is false.
@@ -121,7 +105,7 @@ public class ClientToServerThread implements Runnable {
int sync1; int sync1;
int sync2; int sync2;
// TODO: 14/07/17 wmu16 - Work out how to fix this while loop // TODO: 14/07/17 wmu16 - Work out how to fix this while loop
while(socketOpen) { while(!socket.isClosed() && socket.isConnected() && socketOpen) {
try { try {
crcBuffer = new ByteArrayOutputStream(); crcBuffer = new ByteArrayOutputStream();
sync1 = readByte(); sync1 = readByte();
@@ -153,26 +137,25 @@ public class ClientToServerThread implements Runnable {
} }
} }
} else { } else {
clientLog("Packet has been dropped", 1); logger.warn("Packet has been dropped", 1);
} }
} }
} catch (ByteReadException e) { } catch (ByteReadException e) {
e.printStackTrace(); logger.warn("Byte read exception on ClientToServerThread", 1);
closeSocket(); closeSocket();
Platform.runLater(() -> { notifyDisconnectListeners("Connection to server was interrupted");
Alert alert = new Alert(AlertType.ERROR);
alert.setHeaderText("Host has disconnected");
alert.setContentText("Cannot find Server");
alert.showAndWait();
});
clientLog(e.getMessage(), 1);
return;
} }
} }
logger.warn("Closed connection to server", 1);
closeSocket(); 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 * Sends a request to the server asking for a source ID
@@ -184,7 +167,8 @@ public class ClientToServerThread implements Runnable {
os.write(requestMessage.getBuffer()); os.write(requestMessage.getBuffer());
} catch (IOException e) { } catch (IOException e) {
logger.error("Could not send registration request. Exiting"); 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)){ if (status.equals(RegistrationResponseStatus.SUCCESS_PLAYING)){
clientId = sourceId; clientId = sourceId;
return; return;
} }
@@ -214,11 +197,8 @@ public class ClientToServerThread implements Runnable {
else{ else{
alertErrorText = "Could not connect to server"; alertErrorText = "Could not connect to server";
} }
closeSocket();
Platform.runLater(() -> { notifyDisconnectListeners(alertErrorText);
new Alert(AlertType.ERROR, alertErrorText, ButtonType.OK).showAndWait();
System.exit(1);
});
} }
/** /**
@@ -228,7 +208,7 @@ public class ClientToServerThread implements Runnable {
* - MAINTAIN_HEADING = DOWNWIND and UPWIND packets stop being sent. * - MAINTAIN_HEADING = DOWNWIND and UPWIND packets stop being sent.
* @param actionType The boat action that will dictate packets sent. * @param actionType The boat action that will dictate packets sent.
*/ */
public void sendBoatAction(BoatAction actionType) { public void sendBoatActionMessage(BoatAction actionType) {
switch (actionType) { switch (actionType) {
case MAINTAIN_HEADING: case MAINTAIN_HEADING:
if (upwindTimerFlag) { if (upwindTimerFlag) {
@@ -249,7 +229,7 @@ public class ClientToServerThread implements Runnable {
new TimerTask() { new TimerTask() {
@Override @Override
public void run() { public void run() {
sendBoatAction(new BoatActionMessage(BoatAction.DOWNWIND)); sendBoatActionMessage(new BoatActionMessage(BoatAction.DOWNWIND));
} }
}, 0, PACKET_SENDING_INTERVAL_MS }, 0, PACKET_SENDING_INTERVAL_MS
); );
@@ -262,14 +242,14 @@ public class ClientToServerThread implements Runnable {
new TimerTask() { new TimerTask() {
@Override @Override
public void run() { public void run() {
sendBoatAction(new BoatActionMessage(BoatAction.UPWIND)); sendBoatActionMessage(new BoatActionMessage(BoatAction.UPWIND));
} }
}, 0, PACKET_SENDING_INTERVAL_MS }, 0, PACKET_SENDING_INTERVAL_MS
); );
} }
break; break;
default: default:
sendBoatAction(new BoatActionMessage(actionType)); sendBoatActionMessage(new BoatActionMessage(actionType));
break; break;
} }
} }
@@ -287,12 +267,14 @@ public class ClientToServerThread implements Runnable {
* Sends a boat action of the given message type. * Sends a boat action of the given message type.
* @param message The given message type. * @param message The given message type.
*/ */
private void sendBoatAction(BoatActionMessage message) { private void sendBoatActionMessage(BoatActionMessage message) {
if (clientId != -1) { if (clientId != -1) {
try { try {
os.write(message.getBuffer()); os.write(message.getBuffer());
} catch (IOException e) { } 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 { try {
socket.close(); socket.close();
} catch (IOException e) { } 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); listeners.remove(streamListener);
} }
public void addDisconnectionListener (DisconnectedFromHostListener listener) {
disconnectionListeners.add(listener);
}
public void removeDisconnectionListener (DisconnectedFromHostListener listener) {
disconnectionListeners.remove(listener);
}
private int readByte() throws ByteReadException { private int readByte() throws ByteReadException {
int currentByte = -1; int currentByte = -1;
try { try {
currentByte = is.read(); currentByte = is.read();
crcBuffer.write(currentByte); crcBuffer.write(currentByte);
} catch (IOException e) { } 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) { if (currentByte == -1) {
throw new ByteReadException("InputStream reach end of stream"); throw new ByteReadException("InputStream reach end of stream");
@@ -349,8 +341,7 @@ public class ClientToServerThread implements Runnable {
} }
} }
public int getClientId () { int getClientId () {
return clientId; return clientId;
} }
} }
@@ -10,6 +10,8 @@ import javafx.collections.FXCollections;
import javafx.collections.ObservableList; import javafx.collections.ObservableList;
import javafx.fxml.FXMLLoader; import javafx.fxml.FXMLLoader;
import javafx.scene.Node; import javafx.scene.Node;
import javafx.scene.control.Alert;
import javafx.scene.control.Alert.AlertType;
import javafx.scene.input.KeyEvent; import javafx.scene.input.KeyEvent;
import javafx.scene.layout.Pane; import javafx.scene.layout.Pane;
import seng302.gameServer.GameState; import seng302.gameServer.GameState;
@@ -69,28 +71,31 @@ public class GameClient {
*/ */
public void runAsClient(String ipAddress, Integer portNumber) { public void runAsClient(String ipAddress, Integer portNumber) {
try { 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) { } catch (IOException ioe) {
ioe.printStackTrace(); showConnectionError("Unable to find server");
System.out.println("Unable to connect to host..."); 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. * @param portNumber Port to connect to.
*/ */
public void runAsHost(String ipAddress, Integer portNumber) { public void runAsHost(String ipAddress, Integer portNumber) {
server = new MainServerThread();
try { try {
socketThread = new ClientToServerThread(ipAddress, portNumber); startClientToServerThread(ipAddress, portNumber);
} catch (IOException ioe) { socketThread.addDisconnectionListener((cause) -> {
ioe.printStackTrace(); Platform.runLater(this::loadStartScreen);
System.out.println("Unable to make local connection to host..."); });
} LobbyController lobbyController = loadLobby();
socketThread.addStreamObserver(this::parsePackets); lobbyController.setPlayerListSource(clientLobbyList);
LobbyController lobbyController = loadLobby();
lobbyController.setPlayerListSource(clientLobbyList);
if (regattaData != null){ if (regattaData != null) {
lobbyController.setTitle("Hosting: " + regattaData.getRegattaName()); lobbyController.setTitle("Hosting: " + regattaData.getRegattaName());
lobbyController.setCourseName(regattaData.getCourseName()); lobbyController.setCourseName(regattaData.getCourseName());
} } else {
else{ lobbyController.setTitle("Hosting: " + ipAddress);
lobbyController.setTitle("Hosting: " + ipAddress); lobbyController.setCourseName("");
lobbyController.setCourseName("");
}
lobbyController.addCloseListener(exitCause -> {
if (exitCause == CloseStatus.READY) {
GameState.resetStartTime();
lobbyController.disableReadyButton();
server.startGame();
} else if (exitCause == CloseStatus.LEAVE) {
loadStartScreen();
} }
});
this.lobbyController = lobbyController; lobbyController.addCloseListener(exitCause -> {
server.setGameClient(this); 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() { private void loadStartScreen() {
@@ -145,10 +149,24 @@ public class GameClient {
holderPane.getChildren().clear(); holderPane.getChildren().clear();
holderPane.getChildren().add(fxmlLoader.load()); holderPane.getChildren().add(fxmlLoader.load());
} catch (IOException e) { } 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 * Loads a view of the lobby into the clients pane
* *
@@ -353,13 +371,13 @@ public class GameClient {
private void keyPressed(KeyEvent e) { private void keyPressed(KeyEvent e) {
switch (e.getCode()) { switch (e.getCode()) {
case SPACE: // align with vmg case SPACE: // align with vmg
socketThread.sendBoatAction(BoatAction.VMG); break; socketThread.sendBoatActionMessage(BoatAction.VMG); break;
case PAGE_UP: // upwind case PAGE_UP: // upwind
socketThread.sendBoatAction(BoatAction.UPWIND); break; socketThread.sendBoatActionMessage(BoatAction.UPWIND); break;
case PAGE_DOWN: // downwind case PAGE_DOWN: // downwind
socketThread.sendBoatAction(BoatAction.DOWNWIND); break; socketThread.sendBoatActionMessage(BoatAction.DOWNWIND); break;
case ENTER: // tack/gybe 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()) { 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) //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 case SHIFT: // sails in/sails out
socketThread.sendBoatAction(BoatAction.SAILS_IN); socketThread.sendBoatActionMessage(BoatAction.SAILS_IN);
allBoatsMap.get(socketThread.getClientId()).toggleSail(); allBoatsMap.get(socketThread.getClientId()).toggleSail();
break; break;
case PAGE_UP: case PAGE_UP:
case PAGE_DOWN: case PAGE_DOWN:
socketThread.sendBoatAction(BoatAction.MAINTAIN_HEADING); break; socketThread.sendBoatActionMessage(BoatAction.MAINTAIN_HEADING); break;
} }
} }
+36 -24
View File
@@ -277,7 +277,31 @@ public class GameView extends Pane {
colour = Color.BLACK; 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<Corner> sequence) {
for (int i=1; i < sequence.size()-1; i++) { //General case. for (int i=1; i < sequence.size()-1; i++) { //General case.
double averageLat = 0; double averageLat = 0;
double averageLng = 0; double averageLng = 0;
@@ -297,7 +321,7 @@ public class GameView extends Pane {
averageLng += mark.getLng(); averageLng += mark.getLng();
} }
GeoPoint nextMarkAv = new GeoPoint(averageLat / numMarks, averageLng / numMarks); 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()) { for (Mark mark : course.get(i).getMarks()) {
markerObjects.get(mark).addArrows( markerObjects.get(mark).addArrows(
mark.getRoundingSide() == RoundingSide.STARBOARD ? MarkArrowFactory.RoundingSide.STARBOARD : MarkArrowFactory.RoundingSide.PORT, 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 private void createStartLineArrows () {
//First mark case
double averageLat = 0; double averageLat = 0;
double averageLng = 0; double averageLng = 0;
int numMarks = 0; int numMarks = 0;
@@ -325,10 +351,12 @@ public class GameView extends Pane {
GeoUtility.getBearing(mark, firstMarkAv) GeoUtility.getBearing(mark, firstMarkAv)
); );
} }
//Last Mark case }
numMarks = 0;
averageLat = 0; private void createFinishLineArrows () {
averageLng = 0; double numMarks = 0;
double averageLat = 0;
double averageLng = 0;
for (Mark mark : course.get(course.size()-2).getMarks()) { for (Mark mark : course.get(course.size()-2).getMarks()) {
numMarks += 1; numMarks += 1;
averageLat += mark.getLat(); averageLat += mark.getLat();
@@ -342,22 +370,6 @@ public class GameView extends Pane {
GeoUtility.getBearing(mark, mark) 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());
});
} }
/** /**
@@ -30,12 +30,12 @@ public class RegularPacketsTest {
// ServerYacht yacht = new ArrayList<>(GameState.getYachts().values()).get(0); // ServerYacht yacht = new ArrayList<>(GameState.getYachts().values()).get(0);
// double startAngle = yacht.getHeading(); // double startAngle = yacht.getHeading();
// long startTime = System.currentTimeMillis(); // long startTime = System.currentTimeMillis();
// clientThread.sendBoatAction(BoatAction.UPWIND); // clientThread.sendBoatActionMessage(BoatAction.UPWIND);
// Thread.sleep(200); // Thread.sleep(200);
// while (Math.abs(yacht.getHeading() - startAngle) < TEST_DISTANCE) { // while (Math.abs(yacht.getHeading() - startAngle) < TEST_DISTANCE) {
// Thread.sleep(1); // Thread.sleep(1);
// } // }
// clientThread.sendBoatAction(BoatAction.MAINTAIN_HEADING); // clientThread.sendBoatActionMessage(BoatAction.MAINTAIN_HEADING);
// long endTime = System.currentTimeMillis(); // long endTime = System.currentTimeMillis();
// SleepThreadMaxDelay(); // SleepThreadMaxDelay();
// //Allowed to be two loops of delay due to loop delay and processing delay at client + server ends. // //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(); // SleepThreadMaxDelay();
// Yacht yacht = new ArrayList<>(GameState.getYachts().values()).get(0); // Yacht yacht = new ArrayList<>(GameState.getYachts().values()).get(0);
// boolean startState = yacht.getSailIn(); // boolean startState = yacht.getSailIn();
// clientThread.sendBoatAction(BoatAction.SAILS_IN); // clientThread.sendBoatActionMessage(BoatAction.SAILS_IN);
// SleepThreadMaxDelay(); // SleepThreadMaxDelay();
// Assert.assertEquals(startState, !yacht.getSailIn()); // Assert.assertEquals(startState, !yacht.getSailIn());
// } // }
+1 -1
View File
@@ -36,7 +36,7 @@ public class ToggleSailSteps {
public void the_user_has_pressed(String arg1) throws Throwable { public void the_user_has_pressed(String arg1) throws Throwable {
startTime = System.currentTimeMillis(); startTime = System.currentTimeMillis();
if (arg1 == "shift") { if (arg1 == "shift") {
client.sendBoatAction(BoatAction.SAILS_IN); client.sendBoatActionMessage(BoatAction.SAILS_IN);
} }
} }