mirror of
https://github.com/michaelrausch/Party-Parrots-At-Sea.git
synced 2026-05-09 14:28:43 +00:00
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:
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|
||||||
outputStream.write(getBuffer());
|
if (outputStream == null){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try{
|
||||||
|
outputStream.write(getBuffer());
|
||||||
|
}
|
||||||
|
catch (IOException e){
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user