Added garbage collection for disconnected players

- Heartbeat messages are sent out from their own thread to each player
- If a heartbeat message can't be sent to a player, they are removed from the list of players
- Added equals method for players
Tags: #story[1047]
This commit is contained in:
Michael Rausch
2017-07-13 22:07:03 +12:00
parent 1e6fd1af09
commit c19f66a6a4
6 changed files with 141 additions and 56 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);
} }
@@ -26,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;
@@ -128,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
*/ */
@@ -164,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);
} }
@@ -204,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) {
@@ -231,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) {
@@ -244,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){
@@ -266,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();
@@ -279,7 +255,6 @@ public class GameServerThread implements Runnable, Observer, ClientConnectionDel
} }
startTime = System.currentTimeMillis() + TIME_TILL_RACE_START; startTime = System.currentTimeMillis() + TIME_TILL_RACE_START;
} }
} }
@@ -314,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 .
@@ -388,8 +354,6 @@ public class GameServerThread implements Runnable, Observer, ClientConnectionDel
}*/ }*/
} }
// if (numOfBoatsFinished == ((List<Boat>) arg).size()) { // if (numOfBoatsFinished == ((List<Boat>) arg).size()) {
// startSendingRaceFinishedBoatPositions(); // startSendingRaceFinishedBoatPositions();
// } // }
@@ -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;
}
}
} }