Server discovery bug fixes & error handling improvements

- Fixed concurrency bug that prevented players from connecting to servers
- Discovery server can restart itself if it crashes
- Added nicer error handling for server discovery.
- Using AWS to get servers external IP address.

Tags: #story[1281]
This commit is contained in:
Michael Rausch
2017-09-27 12:32:17 +13:00
parent 735699dc85
commit daf3867433
12 changed files with 214 additions and 147 deletions
+9 -1
View File
@@ -80,6 +80,14 @@ public class App extends Application {
ViewManager.getInstance().initialStartView(primaryStage);
}
private static void runDiscoveryServer(){
try{
new DiscoveryServer();
}
catch (Exception e){
runDiscoveryServer();
}
}
public static void main(String[] args) throws Exception {
try {
@@ -92,7 +100,7 @@ public class App extends Application {
launch(args);
}
else{
new DiscoveryServer();
runDiscoveryServer();
}
}
}
@@ -2,15 +2,19 @@ package seng302.discoveryServer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import seng302.discoveryServer.util.ServerListing;
import seng302.discoveryServer.util.ServerRepoStreamParser;
import seng302.gameServer.messages.Message;
import seng302.gameServer.messages.RoomCodeRequest;
import seng302.gameServer.messages.ServerRegistrationMessage;
import seng302.model.stream.packets.PacketType;
import seng302.discoveryServer.util.ServerListing;
import seng302.discoveryServer.util.ServerRepoStreamParser;
import seng302.visualiser.controllers.ViewManager;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;
import java.net.URL;
import java.util.Timer;
import java.util.TimerTask;
@@ -20,9 +24,28 @@ public class DiscoveryServerClient {
private static String roomCode = null;
private Timer serverListingUpdateTimer;
private Logger logger = LoggerFactory.getLogger(DiscoveryServerClient.class);
private String ip = "";
private Boolean isInInvalidState = false;
public DiscoveryServerClient() {
try {
ip = getInetIpAddr();
} catch (Exception e) {
failError();
}
}
public String getInetIp(){
return ip;
}
private void failError() {
isInInvalidState = true;
ViewManager.getInstance().showErrorSnackBar("You do not appear to be able to connect to the internet. Matchmaking will be unavailable.");
}
public boolean didFail(){
return isInInvalidState;
}
/**
@@ -30,6 +53,8 @@ public class DiscoveryServerClient {
* @param serverListing The listing to register
*/
public void register(ServerListing serverListing){
if (isInInvalidState) return;
if (serverListingUpdateTimer != null){
serverListingUpdateTimer.cancel();
serverListingUpdateTimer = null;
@@ -53,6 +78,7 @@ public class DiscoveryServerClient {
* Stop updating the server registration updates
*/
public void unregister(){
if (serverListingUpdateTimer != null)
serverListingUpdateTimer.cancel();
}
@@ -143,5 +169,27 @@ public class DiscoveryServerClient {
public static String getRoomCode(){
return roomCode;
}
public static String getInetIpAddr() throws Exception {
URL myIp = new URL("http://checkip.amazonaws.com");
BufferedReader in = null;
try {
in = new BufferedReader(new InputStreamReader(
myIp.openStream()));
String ip = in.readLine();
return ip;
} finally {
if (in != null) {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
@@ -48,7 +48,7 @@ public class ServerTable {
updateTtlForServer(server);
return;
}
logger.debug("Added new server - " + server.getServerName());
logger.debug("Added new server - " + server.getServerName() + " at address: " + server.getAddress() + ":" + server.getPortNumber());
servers.add(server);
}
@@ -1,11 +1,5 @@
package seng302.gameServer;
import java.io.IOException;
import java.net.ServerSocket;
import java.util.ArrayList;
import java.util.Random;
import java.util.Timer;
import java.util.TimerTask;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import seng302.gameServer.messages.Message;
@@ -18,6 +12,13 @@ import seng302.model.stream.xml.parser.RaceXMLData;
import seng302.model.stream.xml.parser.RegattaXMLData;
import seng302.utilities.GeoUtility;
import java.io.IOException;
import java.net.ServerSocket;
import java.util.ArrayList;
import java.util.Random;
import java.util.Timer;
import java.util.TimerTask;
/**
* A class describing the overall server, which creates and collects server threads for each client
* Created by wmu16 on 13/07/17.
@@ -158,6 +159,8 @@ public class MainServerThread implements Runnable, ClientConnectionDelegate {
}
private void sendSetupMessages() {
MessageFactory.updateBoats(new ArrayList<>(GameState.getYachts().values()));
broadcastMessage(MessageFactory.getRaceXML());
broadcastMessage(MessageFactory.getRegattaXML());
broadcastMessage(MessageFactory.getBoatXML());
@@ -253,16 +256,26 @@ public class MainServerThread implements Runnable, ClientConnectionDelegate {
}
});
} else {
serverToClientThread.addConnectionListener(this::sendSetupMessages);
MessageFactory.updateBoats(new ArrayList<>(GameState.getYachts().values()));
//serverToClientThread.addConnectionListener(this::sendSetupMessages);
}
serverToClientThreads.add(serverToClientThread);
serverToClientThread.addDisconnectListener(this::clientDisconnected);
try {
ServerAdvertiser.getInstance().setNumberOfPlayers(GameState.getNumberOfPlayers());
} catch (IOException e) {
logger.warn("Couldn't update advertisement");
}
while (regattaXMLData == null && raceXMLData == null){
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
serverToClientThread.addConnectionListener(this::sendSetupMessages);
serverToClientThread.addDisconnectListener(this::clientDisconnected);
}
/**
@@ -283,6 +296,7 @@ public class MainServerThread implements Runnable, ClientConnectionDelegate {
serverToClientThread.sendSetupMessages();
}
}
serverToClientThreads.remove(closedConnection);
try {
@@ -1,18 +1,6 @@
package seng302.gameServer;
import java.util.ArrayList;
import java.util.List;
import seng302.gameServer.messages.BoatLocationMessage;
import seng302.gameServer.messages.BoatSubMessage;
import seng302.gameServer.messages.RaceStartNotificationType;
import seng302.gameServer.messages.RaceStartStatusMessage;
import seng302.gameServer.messages.RaceStatus;
import seng302.gameServer.messages.RaceStatusMessage;
import seng302.gameServer.messages.RaceType;
import seng302.gameServer.messages.XMLMessage;
import seng302.gameServer.messages.XMLMessageSubType;
import seng302.gameServer.messages.YachtEventCodeMessage;
import seng302.gameServer.messages.YachtEventType;
import seng302.gameServer.messages.*;
import seng302.model.Player;
import seng302.model.ServerYacht;
import seng302.model.stream.xml.generator.RaceXMLTemplate;
@@ -20,9 +8,11 @@ import seng302.model.stream.xml.generator.RegattaXMLTemplate;
import seng302.model.stream.xml.parser.RaceXMLData;
import seng302.model.stream.xml.parser.RegattaXMLData;
import seng302.model.token.Token;
import seng302.model.token.TokenType;
import seng302.utilities.XMLGenerator;
import java.util.ArrayList;
import java.util.List;
/**
* A Class for interfacing between the data we have in the GameState to the messages we need to send
* through the MainServerThread.
@@ -1,7 +1,7 @@
package seng302.gameServer;
import seng302.discoveryServer.util.ServerListing;
import seng302.discoveryServer.DiscoveryServerClient;
import seng302.discoveryServer.util.ServerListing;
import javax.jmdns.JmDNS;
import javax.jmdns.ServiceInfo;
@@ -133,7 +133,7 @@ public class ServerAdvertiser {
}
}, 0);
ServerListing serverListing = new ServerListing(serverName, props.get("map"), getLocalHostIp(), portNo, Integer.parseInt(props.get("capacity")));
ServerListing serverListing = new ServerListing(serverName, props.get("map"), new DiscoveryServerClient().getInetIp(), portNo, Integer.parseInt(props.get("capacity")));
repositoryClient.register(serverListing);
}
@@ -1,6 +1,21 @@
package seng302.gameServer;
import javafx.beans.property.SimpleObjectProperty;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import seng302.gameServer.messages.*;
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.parser.RaceXMLData;
import seng302.model.stream.xml.parser.RegattaXMLData;
import seng302.utilities.StreamParser;
import seng302.utilities.XMLGenerator;
import seng302.utilities.XMLParser;
import seng302.visualiser.fxObjects.assets_3D.BoatMeshType;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
@@ -12,26 +27,6 @@ import java.util.Arrays;
import java.util.List;
import java.util.zip.CRC32;
import java.util.zip.Checksum;
import javafx.beans.property.SimpleObjectProperty;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import seng302.gameServer.messages.BoatAction;
import seng302.gameServer.messages.ChatterMessage;
import seng302.gameServer.messages.ClientType;
import seng302.gameServer.messages.CustomizeRequestType;
import seng302.gameServer.messages.Message;
import seng302.gameServer.messages.RegistrationResponseMessage;
import seng302.gameServer.messages.RegistrationResponseStatus;
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.parser.RaceXMLData;
import seng302.model.stream.xml.parser.RegattaXMLData;
import seng302.utilities.StreamParser;
import seng302.utilities.XMLGenerator;
import seng302.visualiser.fxObjects.assets_3D.BoatMeshType;
import seng302.utilities.XMLParser;
/**
* A class describing a single connection to a Client for the purposes of sending and receiving on
@@ -196,6 +191,7 @@ public class ServerToClientThread implements Runnable {
// TODO: 17/08/2017 ajm412: Send a response packet here, not really necessary until we do shapes.
break;
case RACE_XML:
System.out.println("Got raceXML from client");
raceXMLProperty.set(
XMLParser.parseRace(
StreamParser.extractXmlMessage(packet)
@@ -1,36 +1,22 @@
package seng302.visualiser;
import javafx.util.Pair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import seng302.gameServer.messages.*;
import seng302.model.stream.packets.PacketType;
import seng302.model.stream.packets.StreamPacket;
import seng302.utilities.XMLParser;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Queue;
import java.util.Timer;
import java.util.TimerTask;
import java.util.*;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.zip.CRC32;
import java.util.zip.Checksum;
import javafx.util.Pair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import seng302.gameServer.messages.BoatAction;
import seng302.gameServer.messages.BoatActionMessage;
import seng302.gameServer.messages.ChatterMessage;
import seng302.gameServer.messages.ClientType;
import seng302.gameServer.messages.CustomizeRequestMessage;
import seng302.gameServer.messages.CustomizeRequestType;
import seng302.gameServer.messages.Message;
import seng302.gameServer.messages.RegistrationRequestMessage;
import seng302.gameServer.messages.RegistrationResponseStatus;
import seng302.gameServer.messages.XMLMessage;
import seng302.gameServer.messages.XMLMessageSubType;
import seng302.model.stream.packets.PacketType;
import seng302.model.stream.packets.StreamPacket;
import seng302.utilities.XMLParser;
/**
* A class describing a single connection to a Server for the purposes of sending and receiving on
@@ -137,10 +123,12 @@ public class ClientToServerThread implements Runnable {
else {
if (clientId == -1) continue; // Do not continue if not registered
streamPackets.add(new StreamPacket(type, payloadLength, timeStamp, payload));
synchronized (this) {
for (ClientSocketListener csl : listeners)
csl.newPacket();
}
}
}
} else {
logger.warn("Packet has been dropped", 1);
}
@@ -322,20 +310,26 @@ public class ClientToServerThread implements Runnable {
}
public void addStreamObserver (ClientSocketListener streamListener) {
synchronized (this){
listeners.add(streamListener);
}
}
public void removeStreamObserver (ClientSocketListener streamListener) {
listeners.remove(streamListener);
}
public void addDisconnectionListener (DisconnectedFromHostListener listener) {
synchronized (this){
disconnectionListeners.add(listener);
}
}
public void removeDisconnectionListener (DisconnectedFromHostListener listener) {
synchronized (this){
disconnectionListeners.remove(listener);
}
}
private int readByte() throws ByteReadException {
int currentByte = -1;
@@ -1,15 +1,5 @@
package seng302.visualiser;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.TimeZone;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
@@ -48,6 +38,12 @@ import seng302.visualiser.controllers.LobbyController;
import seng302.visualiser.controllers.RaceViewController;
import seng302.visualiser.controllers.ViewManager;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.util.*;
/**
* This class is a client side instance of a yacht racing game in JavaFX. The game is instantiated
* with a JavaFX Pane to insert itself into.
@@ -114,7 +110,7 @@ public class GameClient {
this.lobbyController = ViewManager.getInstance().goToLobby(true);
} catch (IOException ioe) {
showConnectionError("Unable to find server");
ViewManager.getInstance().showErrorSnackBar("Unable to find server");
}
}
@@ -2,12 +2,6 @@ package seng302.visualiser.controllers;
import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.JFXDialog;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.ResourceBundle;
import javafx.application.Platform;
import javafx.collections.ListChangeListener;
import javafx.fxml.FXML;
@@ -19,6 +13,7 @@ import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import seng302.discoveryServer.DiscoveryServerClient;
import seng302.gameServer.GameStages;
import seng302.gameServer.GameState;
import seng302.model.ClientYacht;
@@ -28,12 +23,17 @@ import seng302.model.RaceState;
import seng302.model.mark.CompoundMark;
import seng302.model.mark.Corner;
import seng302.model.stream.xml.parser.RaceXMLData;
import seng302.discoveryServer.DiscoveryServerClient;
import seng302.utilities.Sounds;
import seng302.visualiser.MapPreview;
import seng302.visualiser.controllers.cells.PlayerCell;
import seng302.visualiser.controllers.dialogs.BoatCustomizeController;
import seng302.visualiser.controllers.dialogs.DirectConnectController;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.ResourceBundle;
public class LobbyController implements Initializable {
@@ -91,6 +91,17 @@ public class LobbyController implements Initializable {
serverName.setText(ViewManager.getInstance().getProperty("serverName"));
mapName.setText(ViewManager.getInstance().getProperty("mapName"));
int tries = 0;
while (DiscoveryServerClient.getRoomCode() == null && tries <= 10){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
tries ++;
}
if (DiscoveryServerClient.getRoomCode() != null){
setRoomCode(DiscoveryServerClient.getRoomCode());
}
@@ -5,17 +5,11 @@ import com.jfoenix.controls.JFXDialog;
import com.jfoenix.controls.JFXDialog.DialogTransition;
import com.jfoenix.controls.JFXTextField;
import com.jfoenix.validation.RequiredFieldValidator;
import java.io.IOException;
import java.net.URL;
import java.util.Arrays;
import java.util.List;
import java.util.ResourceBundle;
import javafx.application.Platform;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable;
import javafx.geometry.Pos;
import javafx.scene.control.Alert;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.input.KeyCode;
@@ -23,19 +17,21 @@ import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import seng302.discoveryServer.DiscoveryServer;
import seng302.gameServer.ServerDescription;
import seng302.discoveryServer.util.ServerListing;
import seng302.discoveryServer.DiscoveryServerClient;
import seng302.gameServer.messages.RoomCodeRequest;
import seng302.discoveryServer.util.ServerListing;
import seng302.gameServer.ServerDescription;
import seng302.gameServer.messages.ServerRegistrationMessage;
import seng302.utilities.Sounds;
import seng302.visualiser.ServerListener;
import seng302.visualiser.ServerListenerDelegate;
import seng302.visualiser.controllers.cells.ServerCell;
import seng302.visualiser.controllers.dialogs.DirectConnectController;
import seng302.visualiser.validators.HostNameFieldValidator;
import seng302.visualiser.validators.NumberRangeValidator;
import seng302.visualiser.validators.ValidationTools;
import java.io.IOException;
import java.net.URL;
import java.util.Arrays;
import java.util.List;
import java.util.ResourceBundle;
public class ServerListController implements Initializable, ServerListenerDelegate {
@@ -90,6 +86,8 @@ public class ServerListController implements Initializable, ServerListenerDelega
Sounds.playButtonClick();
});
directConnectDialog = createDirectConnectDialog();
for (JFXTextField textField : Arrays.asList(roomNumber)) {
// Event for pressing enter to submit direct connection
textField.setOnKeyPressed(event -> {
@@ -105,26 +103,26 @@ public class ServerListController implements Initializable, ServerListenerDelega
}
autoSelectGame.setOnMouseReleased(e -> {
try {
ServerListing listing = new DiscoveryServerClient().getRandomServer();
ServerListing listing;
DiscoveryServerClient client = new DiscoveryServerClient();
if (listing == null){
Alert alert = new Alert(Alert.AlertType.ERROR);
alert.setHeaderText("Error finding game");
alert.setContentText("No servers are up");
alert.showAndWait();
}
else{
ViewManager.getInstance().getGameClient().runAsClient(listing.getAddress(), listing.getPortNumber());
}
try {
listing = client.getRandomServer();
} catch (Exception e1) {
e1.printStackTrace();
logger.error("Error getting listing");
Alert alert = new Alert(Alert.AlertType.ERROR);
alert.setHeaderText("Error finding game");
alert.setContentText("Couldn't contact matchmaking server");
alert.showAndWait();
ViewManager.getInstance().showErrorSnackBar("Unable to connect to matchmaking server. Are you connected to the internet?");
return;
}
if (client.didFail()){
return;
}
if (listing == null || listing.equals(ServerRegistrationMessage.getEmptyRegistration())) {
ViewManager.getInstance().showErrorSnackBar("There are currently no servers available for you to connect to.");
return;
}
ViewManager.getInstance().getGameClient().runAsClient(listing.getAddress(), listing.getPortNumber());
});
/*
@@ -186,9 +184,6 @@ public class ServerListController implements Initializable, ServerListenerDelega
e.printStackTrace();
logger.warn("Could not create Server Creation Dialog.");
}
directConnectDialog = createDirectConnectDialog();
});
}
@@ -235,20 +230,30 @@ public class ServerListController implements Initializable, ServerListenerDelega
}
private void connectToRoomCode(String roomCode){
DiscoveryServerClient client = new DiscoveryServerClient();
ServerListing serverListing;
if (client.didFail()){
return;
}
try {
serverListing = client.getServerForRoomCode(roomCode);
} catch (Exception e) {
ViewManager.getInstance().showErrorSnackBar("Error connecting to matchmaking server. Please try again later.");
return;
}
if (serverListing == null || serverListing.equals(new ServerListing("","","", 0, 0))){
ViewManager.getInstance().showErrorSnackBar("No servers could be found with that room code.");
return;
}
try {
ServerListing serverListing = new DiscoveryServerClient().getServerForRoomCode(roomCode);
ViewManager.getInstance().getGameClient().runAsClient(serverListing.getAddress(), serverListing.getPortNumber());
}
catch (java.net.ConnectException e){
//TODO Add proper dialog
logger.warn("Couldn't connect to discovery server");
Alert alert = new Alert(Alert.AlertType.ERROR);
alert.setHeaderText("Couldn't connect to discovery server");
alert.setContentText("Couldn't connect to " + DiscoveryServer.DISCOVERY_SERVER);
alert.showAndWait();
}
catch (Exception e) {
logger.warn("Error discovering room code");
ViewManager.getInstance().showErrorSnackBar("Error connecting to matchmaking service.");
}
}
@@ -6,16 +6,10 @@ import com.jfoenix.controls.JFXDialog;
import com.jfoenix.controls.JFXDialog.DialogTransition;
import com.jfoenix.controls.JFXSnackbar;
import com.jfoenix.svg.SVGGlyph;
import java.io.IOException;
import java.util.HashMap;
import javafx.application.Platform;
import javafx.collections.ObservableList;
import javafx.fxml.FXMLLoader;
import javafx.scene.Cursor;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.SceneAntialiasing;
import javafx.scene.*;
import javafx.scene.image.Image;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
@@ -28,6 +22,9 @@ import seng302.utilities.Sounds;
import seng302.visualiser.GameClient;
import seng302.visualiser.controllers.dialogs.KeyBindingDialogController;
import java.io.IOException;
import java.util.HashMap;
public class ViewManager {
private static ViewManager instance;
@@ -369,6 +366,14 @@ public class ViewManager {
return loader.getController();
}
public void showErrorSnackBar(String msg){
decorator.getStylesheets()
.add(getClass().getResource("/css/dialogs/Snackbar.css").toExternalForm());
JFXSnackbar bar = new JFXSnackbar(decorator);
bar.enqueue(new JFXSnackbar.SnackbarEvent(msg));
}
public Stage getStage() {
return stage;
}