Merge remote-tracking branch 'origin/1047_Hosting_Game' into 1047_Hosting_Game

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