Made discovery more reliable & added docs/tests

- Added unit tests
- Added documentation for discovery classes
- Improved error handling

Tags: #story[1281]
This commit is contained in:
Michael Rausch
2017-09-22 00:01:13 +12:00
parent 95ad7a4840
commit 5e3ae40d03
15 changed files with 360 additions and 155 deletions
+33 -26
View File
@@ -10,7 +10,7 @@ import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import seng302.serverRepository.ServerRepository;
import seng302.discoveryServer.DiscoveryServer;
import seng302.visualiser.controllers.ViewManager;
public class App extends Application {
@@ -27,45 +27,52 @@ public class App extends Application {
.getLogger(Logger.ROOT_LOGGER_NAME);
options.addOption("debugLevel", true, "Set the application debug level");
options.addOption("runAsCache", false, "Run as a server cache for server discovery");
options.addOption("runAsDiscoveryServer", false, "Run as a discovery server");
options.addOption("discoveryDevMode", false, "Use a local discovery server");
cmd = parser.parse(options, args);
if (cmd.hasOption("runAsCache")){
if (cmd.hasOption("runAsDiscoveryServer")){
isRunningAsCache = true;
rootLogger.setLevel(Level.ALL);
return;
}
if (cmd.hasOption("discoveryDevMode")) {
DiscoveryServer.DISCOVERY_SERVER = "localhost";
}
if (cmd.hasOption("debugLevel")) {
switch (cmd.getOptionValue("debugLevel")) {
case "DEBUG":
rootLogger.setLevel(Level.DEBUG);
break;
switch (cmd.getOptionValue("debugLevel")) {
case "DEBUG":
rootLogger.setLevel(Level.DEBUG);
break;
case "ALL":
rootLogger.setLevel(Level.ALL);
break;
case "ALL":
rootLogger.setLevel(Level.ALL);
break;
case "WARNING":
rootLogger.setLevel(Level.WARN);
break;
case "WARNING":
rootLogger.setLevel(Level.WARN);
break;
case "ERROR":
rootLogger.setLevel(Level.ERROR);
break;
case "ERROR":
rootLogger.setLevel(Level.ERROR);
break;
case "INFO":
rootLogger.setLevel(Level.INFO);
case "INFO":
rootLogger.setLevel(Level.INFO);
case "TRACE":
rootLogger.setLevel(Level.TRACE);
case "TRACE":
rootLogger.setLevel(Level.TRACE);
default:
rootLogger.setLevel(Level.ALL);
}
} else {
rootLogger.setLevel(Level.WARN);
default:
rootLogger.setLevel(Level.ALL);
}
} else {
rootLogger.setLevel(Level.WARN);
}
}
@Override
@@ -85,7 +92,7 @@ public class App extends Application {
launch(args);
}
else{
ServerRepository serverRepository = new ServerRepository();
new DiscoveryServer();
}
}
}
@@ -0,0 +1,117 @@
package seng302.discoveryServer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.discoveryServer.util.ServerTable;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Arrays;
import java.util.Random;
public class DiscoveryServer {
public static final String ANSI_GREEN = "\u001B[32m";
public static final String ANSI_YELLOW = "\u001B[33m";
public static final String ANSI_BLUE = "\u001B[34m";
public static final String ANSI_RESET = "\u001B[0m";
public static String DISCOVERY_SERVER = "party.sydney.srv.michaelrausch.nz";
private ServerTable serverTable;
public static final Integer PORT_NUMBER = 9969;
private final Logger logger = LoggerFactory.getLogger(DiscoveryServer.class);
private void displayHeader(){
String selectedColor = Arrays.asList(ANSI_BLUE, ANSI_GREEN, ANSI_YELLOW).get(new Random().nextInt(2));
System.out.println(selectedColor);
System.out.println(" .ccccc. \n" +
" .cc;'coooxkl;. \n" +
" .:c:::c:,,,,,;c;;,.'. \n" +
" .clc,',:,..:xxocc;'..c; \n" +
" .c:,';:ox:..:c,,,,,,...cd, \n" +
" .c:'.,oxxxxl::l:.,loll;..;ol. \n" +
" ;Oc..:xxxxxxxxx:.,llll,....oc \n" +
" .,;,',:loxxxxxxxxx:.,llll;.,,.'ld, \n" +
" .lo;..:xxxxxxxxxxxx:.'cllc,.:l:'cO; \n" +
" .:;...'cxxxxxxxxxxxxoc;,::,..cdl;;l' \n" +
" .cl;':,'';oxxxxxxdxxxxxx:....,cooc,cO; \n" +
" .,,,::;,lxoc:,,:lxxxxxxxxxxxo:,,;lxxl;'oNc \n" +
" .cdxo;':lxxxxxxc'';cccccoxxxxxxxxxxxxo,.;lc. " + ANSI_YELLOW + "Party-Parrots-At-Sea Discovery Server v0.1 " + selectedColor +"\n" +
" .loc'.'lxxxxxxxxocc;''''';ccoxxxxxxxxx:..oc \n" +
"olc,..',:cccccccccccc:;;;;;;;;:ccccccccc,.'c, \n" +
"Ol;......................................;l' ");
System.out.println(ANSI_RESET);
}
public DiscoveryServer() throws Exception {
displayHeader();
serverTable = new ServerTable();
ServerSocket serverSocket;
try{
serverSocket = new ServerSocket(PORT_NUMBER);
}
catch(java.net.BindException e){
logger.error("FATAL - Could not bind socket, are you sure there isn't already an instance running?");
System.exit(1);
return;
}
logger.info("Started successfully - Now accepting connections");
while (true){
Socket clientSocket = serverSocket.accept();
parseRequest(clientSocket);
clientSocket.close();
}
}
private void parseRequest(Socket clientSocket) throws Exception {
ServerRepoStreamParser parser = new ServerRepoStreamParser(clientSocket.getInputStream());
if (clientSocket.isConnected() && !clientSocket.isClosed()){
PacketType parsePacketResult = parser.parse();
switch (parsePacketResult){
case SERVER_REGISTRATION:
ServerListing listing = parser.getServerListing();
if (!serverTable.getAllServers().contains(listing)){
listing.setRoomCode(serverTable.getNextRoomCode().toString());
}
serverTable.addServer(listing);
Message serverRegMessage = new RoomCodeRequest(listing.getRoomCode());
clientSocket.getOutputStream().write(serverRegMessage.getBuffer());
break;
case ROOM_CODE_REQUEST:
String desiredRoomCode = parser.getRoomCode();
ServerListing serverListing = serverTable.getServerByRoomCode(desiredRoomCode);
Message response;
if (serverListing != null){
response = new ServerRegistrationMessage(serverListing.getServerName(), serverListing.getMapName(), serverListing.getAddress(), serverListing.getPortNumber(), 0, 0, desiredRoomCode);
}
else{
response = new ServerRegistrationMessage("", "", "", 0, 0, 0, "");
}
clientSocket.getOutputStream().write(response.getBuffer());
break;
}
}
}
}
@@ -1,24 +1,39 @@
package seng302.serverRepository;
package seng302.discoveryServer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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 java.net.Socket;
import java.util.Timer;
import java.util.TimerTask;
public class ServerRepositoryClient {
public class DiscoveryServerClient {
private final Integer UPDATE_INTERVAL_MS = 5000;
private static String roomCode = null;
private Timer serverListingUpdateTimer;
private Logger logger = LoggerFactory.getLogger(DiscoveryServerClient.class);
public ServerRepositoryClient() {
public DiscoveryServerClient() {
}
/**
* Register the server with the discovery server
* @param serverListing The listing to register
*/
public void register(ServerListing serverListing){
if (serverListingUpdateTimer != null){
serverListingUpdateTimer.cancel();
serverListingUpdateTimer = null;
}
serverListingUpdateTimer = new Timer();
serverListingUpdateTimer.schedule(new TimerTask() {
@@ -27,19 +42,28 @@ public class ServerRepositoryClient {
try {
sendRegistrationUpdate(serverListing);
} catch (Exception e) {
e.printStackTrace();//todo proper error handling
logger.debug("Could not update server listing");
}
}
}, 0, 5000);
}, 0, UPDATE_INTERVAL_MS);
}
/**
* Stop updating the server registration updates
*/
public void unregister(){
serverListingUpdateTimer.cancel();
}
/**
* Gets the connection information for a server given a room code
*
* @param roomCode The room code to search for
* @return The ServerListing, or null if there was an error
* @throws Exception .
*/
public ServerListing getServerForRoomCode(String roomCode) throws Exception {
// TODO replace localhost with server
Socket socket = new Socket("localhost", 9999);
Socket socket = new Socket(DiscoveryServer.DISCOVERY_SERVER, DiscoveryServer.PORT_NUMBER);
ServerRepoStreamParser parser = new ServerRepoStreamParser(socket.getInputStream());
Message request = new RoomCodeRequest(roomCode); //roomCode);
@@ -48,7 +72,7 @@ public class ServerRepositoryClient {
PacketType packetType = parser.parse();
if (packetType != PacketType.SERVER_REGISTRATION){
System.out.println("Wrong packet type");
logger.debug("Wrong packet received in response to a room code request");
return null;
}
@@ -57,8 +81,14 @@ public class ServerRepositoryClient {
return parser.getServerListing();
}
/**
* Sends a registration update to the discovery server.
*
* @param serverListing The server listing to send
* @throws Exception IF there was an error sending the update
*/
private void sendRegistrationUpdate(ServerListing serverListing) throws Exception {
Socket socket = new Socket("localhost", 9999);
Socket socket = new Socket(DiscoveryServer.DISCOVERY_SERVER, DiscoveryServer.PORT_NUMBER);
ServerRepoStreamParser parser = new ServerRepoStreamParser(socket.getInputStream());
Message req = new ServerRegistrationMessage(serverListing);
@@ -68,18 +98,22 @@ public class ServerRepositoryClient {
PacketType packetType = parser.parse();
if (packetType != PacketType.ROOM_CODE_REQUEST){
socket.close();
return;
}
String roomCode = parser.getRoomCode();
if (roomCode.length() != 0){
ServerRepositoryClient.roomCode = roomCode;
DiscoveryServerClient.roomCode = roomCode;
}
socket.close();
}
/**
* @return The last room code received by the client
*/
public static String getRoomCode(){
return roomCode;
}
@@ -1,4 +1,4 @@
package seng302.serverRepository;
package seng302.discoveryServer.util;
import java.io.InputStream;
@@ -9,6 +9,12 @@ public class ReadableByteInputStream {
this.is = is;
}
/**
* Get n bytes from the input stream
* @param n number of bytes
* @return the bytes read
* @throws Exception .
*/
public byte[] getBytes(int n) throws Exception {
byte[] bytes = new byte[n];
for (int i = 0; i < n; i++) {
@@ -17,12 +23,22 @@ public class ReadableByteInputStream {
return bytes;
}
/**
* Skip n bytes
* @param n number of bytes to skip
* @throws Exception
*/
public void skipBytes(long n) throws Exception {
for (int i = 0; i < n; i++) {
readByte();
}
}
/**
* Read the next byte from the stream
* @return The byte that was read
* @throws Exception .
*/
public int readByte() throws Exception {
int currentByte = is.read();
@@ -1,7 +1,7 @@
package seng302.serverRepository;
package seng302.discoveryServer.util;
public class ServerListing {
private static final int SERVER_TTL_DEFAULT = 10;
public final static int SERVER_TTL_DEFAULT = 10;
private String serverName = "";
private String mapName = "";
@@ -106,4 +106,8 @@ public class ServerListing {
public String getAddress() {
return address;
}
public void setTtl(Integer ttl){
this.ttl = ttl;
}
}
@@ -1,4 +1,4 @@
package seng302.serverRepository;
package seng302.discoveryServer.util;
import seng302.gameServer.messages.Message;
@@ -1,12 +1,14 @@
package seng302.serverRepository;
package seng302.discoveryServer.util;
import com.sun.corba.se.spi.activation.Server;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.*;
public class ServerTable {
private List<ServerListing> servers;
private int lastRoomCode = 4020;
private Logger logger = LoggerFactory.getLogger(ServerTable.class);
public ServerTable(){
servers = new ArrayList<>();
@@ -17,9 +19,11 @@ public class ServerTable {
updateServers();
}
}, 0, 1000);
}
/**
* Update the servers TTL values, and then remove expired servers
*/
private void updateServers() {
List<ServerListing> serversToRemove = new ArrayList<>();
@@ -27,38 +31,52 @@ public class ServerTable {
server.decrementTtl();
if (server.hasTtlExpired()){
logger.debug("Removed expired server - " + server.getServerName());
serversToRemove.add(server);
}
}
for (ServerListing server : serversToRemove){
System.out.println("Removing " + server.getServerName());
servers.remove(server);
}
servers.removeAll(serversToRemove);
}
/**
* Add a server to the table
* @param server The server to add
*/
public void addServer(ServerListing server){
if (servers.contains(server)){
updateTtlForServer(server);
return;
}
logger.debug("Added new server - " + server.getServerName());
servers.add(server);
}
/**
* Update the TTL for a given server to the default TTL value
* @param server The server to update
*/
private void updateTtlForServer(ServerListing server) {
for (ServerListing serverListing : servers){
if (server.equals(serverListing)){
System.out.println("Refreshing TTL For " + server.getServerName());
serverListing.refreshTtl();
}
}
}
/**
* @return All the servers in the table
*/
public List<ServerListing> getAllServers(){
return Collections.unmodifiableList(servers);
}
/**
* Get a server from the table given its room code
* @param roomCode The room code to search for
* @return The ServerListing of the found server, or null
* the server wasn't found
*/
public ServerListing getServerByRoomCode(String roomCode){
for (ServerListing serverListing : servers){
if (serverListing.getRoomCode().equals(roomCode)){
@@ -69,11 +87,11 @@ public class ServerTable {
return null;
}
/**
* @return The next available room code
*/
public Integer getNextRoomCode(){
System.out.println(lastRoomCode);
lastRoomCode += 1;
return lastRoomCode;
}
}
@@ -1,10 +1,7 @@
package seng302.gameServer;
import com.sun.org.apache.xpath.internal.operations.Bool;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import seng302.serverRepository.ServerListing;
import seng302.serverRepository.ServerRepositoryClient;
import seng302.discoveryServer.util.ServerListing;
import seng302.discoveryServer.DiscoveryServerClient;
import javax.jmdns.JmDNS;
import javax.jmdns.ServiceInfo;
@@ -38,14 +35,14 @@ public class ServerAdvertiser {
private static ServerAdvertiser instance = null;
private static JmDNS jmdnsInstance = null;
private ServiceInfo serviceInfo; // Note: Whenever this is changed, our service will be re-registered on the network.
private ServerRepositoryClient repositoryClient;
private DiscoveryServerClient repositoryClient;
private Hashtable<String ,String> props;
private ServerAdvertiser() throws IOException{
jmdnsInstance = JmDNS.create(InetAddress.getByName(getLocalHostIp()));
repositoryClient = new ServerRepositoryClient();
repositoryClient = new DiscoveryServerClient();
props = new Hashtable<>();
props.put("map", "");
@@ -1,6 +1,6 @@
package seng302.gameServer.messages;
import seng302.serverRepository.ServerListing;
import seng302.discoveryServer.util.ServerListing;
public class ServerRegistrationMessage extends Message {
private int size;
@@ -1,86 +0,0 @@
package seng302.serverRepository;
import seng302.gameServer.messages.Message;
import seng302.gameServer.messages.RoomCodeRequest;
import seng302.gameServer.messages.ServerRegistrationMessage;
import seng302.model.stream.packets.PacketType;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Timer;
import java.util.TimerTask;
public class ServerRepository {
private ServerTable serverTable;
public ServerRepository() throws Exception {
System.out.println(" -- Starting Server Repository -- ");
serverTable = new ServerTable();
ServerSocket serverSocket = new ServerSocket(9999);
// // TODO Remove later, this is for testing
// new Timer().schedule(new TimerTask() {
// @Override
// public void run() {
// try {
// System.out.println("Starting repo client");
// new ServerRepositoryClient();
// } catch (Exception e) {
// e.printStackTrace();
// }
// }
// }, 5000);
while (true){
Socket clientSocket = serverSocket.accept();
parseRequest(clientSocket);
clientSocket.close();
}
}
private void parseRequest(Socket clientSocket) throws Exception {
ServerRepoStreamParser parser = new ServerRepoStreamParser(clientSocket.getInputStream());
if (clientSocket.isConnected() && !clientSocket.isClosed()){
PacketType parsePacketResult = parser.parse();
switch (parsePacketResult){
case SERVER_REGISTRATION:
ServerListing listing = parser.getServerListing();
if (!serverTable.getAllServers().contains(listing)){
listing.setRoomCode(serverTable.getNextRoomCode().toString());
}
serverTable.addServer(listing);
Message serverRegMessage = new RoomCodeRequest(listing.getRoomCode());
clientSocket.getOutputStream().write(serverRegMessage.getBuffer());
break;
case ROOM_CODE_REQUEST:
String desiredRoomCode = parser.getRoomCode();
ServerListing serverListing = serverTable.getServerByRoomCode(desiredRoomCode);
Message response;
if (serverListing != null){
response = new ServerRegistrationMessage(serverListing.getServerName(), serverListing.getMapName(), serverListing.getAddress(), serverListing.getPortNumber(), 0, 0, desiredRoomCode);
}
else{
response = new ServerRegistrationMessage("", "", "", 0, 0, 0, "");
}
clientSocket.getOutputStream().write(response.getBuffer());
break;
}
}
}
}
@@ -22,7 +22,7 @@ import seng302.model.RaceState;
import seng302.model.mark.CompoundMark;
import seng302.model.mark.Corner;
import seng302.model.stream.xml.parser.RaceXMLData;
import seng302.serverRepository.ServerRepositoryClient;
import seng302.discoveryServer.DiscoveryServerClient;
import seng302.utilities.Sounds;
import seng302.visualiser.GameView;
import seng302.visualiser.controllers.cells.PlayerCell;
@@ -83,8 +83,8 @@ public class LobbyController implements Initializable {
serverName.setText(ViewManager.getInstance().getProperty("serverName"));
mapName.setText(ViewManager.getInstance().getProperty("mapName"));
if (ServerRepositoryClient.getRoomCode() != null){
setRoomCode(ServerRepositoryClient.getRoomCode());
if (DiscoveryServerClient.getRoomCode() != null){
setRoomCode(DiscoveryServerClient.getRoomCode());
}
ViewManager.getInstance().getPlayerList().addListener((ListChangeListener<String>) c -> Platform.runLater(this::refreshPlayerList));
@@ -15,6 +15,7 @@ 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;
@@ -22,9 +23,10 @@ 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.serverRepository.ServerListing;
import seng302.serverRepository.ServerRepositoryClient;
import seng302.discoveryServer.util.ServerListing;
import seng302.discoveryServer.DiscoveryServerClient;
import seng302.utilities.Sounds;
import seng302.visualiser.ServerListener;
import seng302.visualiser.ServerListenerDelegate;
@@ -169,10 +171,19 @@ public class ServerListController implements Initializable, ServerListenerDelega
private void connectToRoomCode(String roomCode){
try {
ServerListing serverListing = new ServerRepositoryClient().getServerForRoomCode(roomCode);
ServerListing serverListing = new DiscoveryServerClient().getServerForRoomCode(roomCode);
ViewManager.getInstance().getGameClient().runAsClient(serverListing.getAddress(), serverListing.getPortNumber());
} catch (Exception e) {
e.printStackTrace();
}
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");
}
}
@@ -20,7 +20,6 @@ import javafx.stage.Stage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import seng302.gameServer.ServerAdvertiser;
import seng302.serverRepository.ServerRepositoryClient;
import seng302.utilities.BonjourInstallChecker;
import seng302.utilities.Sounds;
import seng302.visualiser.GameClient;