WIP: Adapted the old server thread class to the GameServerThread class to allow multiple clients to connect

tags: #story[1047]  #pair[wmu16, mra106]
This commit is contained in:
William Muir
2017-07-11 17:03:32 +12:00
parent 752863a0d3
commit 035841f221
11 changed files with 515 additions and 132 deletions
+5 -5
View File
@@ -70,13 +70,13 @@ public class App extends Application {
else{ else{
// sr = new StreamReceiver("localhost", 4949, "RaceStream"); // sr = new StreamReceiver("localhost", 4949, "RaceStream");
// sr = new StreamReceiver("csse-s302staff.canterbury.ac.nz", 4941, "RaceStream"); // sr = new StreamReceiver("csse-s302staff.canterbury.ac.nz", 4941, "RaceStream");
// sr = new StreamReceiver("csse-s302staff.canterbury.ac.nz", 4942, "RaceStream"); sr = new StreamReceiver("csse-s302staff.canterbury.ac.nz", 4942, "RaceStream");
sr = new StreamReceiver("livedata.americascup.com", 4941, "RaceStream"); // sr = new StreamReceiver("livedata.americascup.com", 4941, "RaceStream");
} }
sr.start(); // sr.start();
StreamParser streamParser = new StreamParser("StreamParser"); // StreamParser streamParser = new StreamParser("StreamParser");
streamParser.start(); // streamParser.start();
launch(args); launch(args);
@@ -8,6 +8,8 @@ import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.GridPane; import javafx.scene.layout.GridPane;
import javafx.scene.layout.Pane; import javafx.scene.layout.Pane;
import javafx.scene.text.Text; import javafx.scene.text.Text;
import seng302.gameServer.GameStages;
import seng302.gameServer.GameState;
import java.io.IOException; import java.io.IOException;
@@ -58,7 +60,6 @@ public class LobbyController {
@FXML @FXML
public void readyButtonPressed() { public void readyButtonPressed() {
// TODO: 10/07/17 wmu16 - Finish function GameState.setCurrentStage(GameStages.RACING);
System.out.println("LEts play!!");
} }
} }
@@ -6,12 +6,12 @@ import javafx.scene.control.TextField;
import javafx.scene.layout.AnchorPane; import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.GridPane; import javafx.scene.layout.GridPane;
import javafx.scene.layout.Pane; import javafx.scene.layout.Pane;
import seng302.gameServer.GameConnectionListener; import seng302.gameServer.GameServerThread;
import seng302.gameServer.GameState; import seng302.gameServer.GameState;
import seng302.models.stream.StreamReceiver;
import java.io.IOException; import java.io.IOException;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException; import java.net.UnknownHostException;
/** /**
@@ -53,15 +53,11 @@ public class StartScreen2Controller {
try { try {
String ipAddress = InetAddress.getLocalHost().getHostAddress(); String ipAddress = InetAddress.getLocalHost().getHostAddress();
new GameState(ipAddress); new GameState(ipAddress);
GameConnectionListener gameConnectionListener = new GameConnectionListener(); GameServerThread gameServerThread = new GameServerThread("Game Server");
gameConnectionListener.start();
setContentPane("/views/LobbyView.fxml"); setContentPane("/views/LobbyView.fxml");
} catch (UnknownHostException e) { } catch (UnknownHostException e) {
System.err.println("COULD NOT FIND YOUR IP ADDRESS!"); System.err.println("COULD NOT FIND YOUR IP ADDRESS!");
e.printStackTrace(); e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
System.err.println("COULD NOT OPEN CONNECTION!");
} }
} }
@@ -71,13 +67,7 @@ public class StartScreen2Controller {
public void connectButtonPressed() { public void connectButtonPressed() {
// TODO: 10/07/17 wmu16 - Finish function // TODO: 10/07/17 wmu16 - Finish function
String ipAddress = ipTextField.getText().trim(); String ipAddress = ipTextField.getText().trim();
Socket host = null; StreamReceiver sr = new StreamReceiver(ipAddress, GameServerThread.PORT_NUMBER, "HostStream");
try { sr.start();
host = new Socket(ipAddress, GameConnectionListener.GAME_HOST_PORT);
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("connecting to: " + ipTextField.getText());
} }
} }
@@ -0,0 +1,7 @@
package seng302.gameServer;
import seng302.models.Player;
public interface ClientConnectionDelegate {
void clientConnected(Player player);
}
@@ -1,53 +0,0 @@
package seng302.gameServer;
import seng302.models.Player;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.ServerSocketChannel;
/**
* A class defining the lobby host that will wait for connections and update the GameState appropriately
* Created by wmu16 on 10/07/17.
*/
public class GameConnectionListener extends Thread{
public static final Integer GAME_HOST_PORT = 4950;
private Thread t;
private ServerSocketChannel serverSocketChannel;
public GameConnectionListener() throws IOException {
serverSocketChannel = ServerSocketChannel.open();
// TODO: 10/07/17 wmu16 - If you pres host, leave lobby, host, - an error is thrown as this port is already bound.
serverSocketChannel.socket().bind(new InetSocketAddress("localhost", GAME_HOST_PORT));
}
/**
* Starts the listening thread
*/
public void start() {
if (t == null) {
t = new Thread(this, "GameConnectionListener");
t.start();
}
}
/**
* This listens for players connecting and adds them to the GameState object
* WHILE - max plaers is not exceeded AND the race has not started
*/
public void run() {
while(GameState.getPlayers().size() < GameState.MAX_NUM_PLAYERS && !GameState.getIsRaceStarted()) {
try {
GameState.addPlayer(new Player(serverSocketChannel.accept()));
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
@@ -0,0 +1,370 @@
package seng302.gameServer;
import seng302.models.Player;
import seng302.models.Yacht;
import seng302.server.messages.*;
import seng302.server.simulator.Boat;
import seng302.server.simulator.Simulator;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetSocketAddress;
import java.net.SocketOption;
import java.net.SocketOptions;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.*;
public class GameServerThread implements Runnable, Observer, ClientConnectionDelegate{
private static final Integer MAX_NUM_PLAYERS = 10;
public static final int PORT_NUMBER = 4950;
private Boolean hosting = true;
private ServerSocketChannel server;
private long startTime;
private short seqNum;
private final int HEARTBEAT_PERIOD = 5000;
private final int RACE_STATUS_PERIOD = 1000/2;
private final int RACE_START_STATUS_PERIOD = 1000;
private final int BOAT_LOCATION_PERIOD = 1000/5;
private final int TIME_TILL_RACE_START = 20*1000;
private static final int LOG_LEVEL = 1;
public GameServerThread(String threadName){
Thread runner = new Thread(this, threadName);
runner.setDaemon(true);
seqNum = 0;
runner.start();
}
static void serverLog(String message, int logLevel){
if(logLevel <= LOG_LEVEL){
System.out.println("[SERVER] " + message);
}
}
/**
* Creates and returns an XML Message from the file specified
* @param fileName The source XML file
* @param type The XML Message type
* @return The XML Message
*/
private Message getXmlMessage(String fileName, XMLMessageSubType type){
String fileContents = null;
try {
InputStream thisStream = this.getClass().getResourceAsStream(fileName);
fileContents = new String(org.apache.commons.io.IOUtils.toByteArray(thisStream));
} catch (IOException e) {
e.printStackTrace();
} catch (NullPointerException e){
return null;
}
if (fileContents != null){
return new XMLMessage(fileContents, type, seqNum);
}
return null;
}
/**
* @return Get a race status message for the current race
*/
private Message getRaceStatusMessage(){
List<BoatSubMessage> boatSubMessages = new ArrayList<>();
BoatStatus boatStatus;
RaceStatus raceStatus;
boolean thereAreBoatsNotFinished = false;
for (Player player : GameState.getPlayers()){
Yacht y = player.getYacht();
if (GameState.getCurrentStage() == GameStages.PRE_RACE){
boatStatus = BoatStatus.PRESTART;
thereAreBoatsNotFinished = true;
}
else if(false){ //@TODO if boat has finished
boatStatus = BoatStatus.FINISHED;
}
else{
boatStatus = BoatStatus.PRESTART;
thereAreBoatsNotFinished = true;
}
BoatSubMessage m = new BoatSubMessage(y.getSourceID(), boatStatus, y.getLastMarkRounded().getId(), 0, 0, 1234l, 1234l);
boatSubMessages.add(m);
}
if (thereAreBoatsNotFinished){
if (GameState.getCurrentStage() == GameStages.RACING){
raceStatus = RaceStatus.STARTED;
}
else{
long currentTime = System.currentTimeMillis();
long timeDifference = startTime - currentTime;
if (timeDifference > 60*3){
raceStatus = RaceStatus.PRESTART;
}
else if (timeDifference > 60){
raceStatus = RaceStatus.WARNING;
}
else{
raceStatus = RaceStatus.PREPARATORY;
}
}
}
else{
raceStatus = RaceStatus.TERMINATED;
}
return new RaceStatusMessage(1, raceStatus, startTime, WindDirection.SOUTH,
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 {
broadcast(heartbeat);
} catch (IOException e) {
e.printStackTrace();
}
}
}, 0, HEARTBEAT_PERIOD);
}
/**
* Start sending race start status messages until race starts
*/
private void startSendingRaceStartStatusMessages(){
Timer t = new Timer();
t.schedule(new TimerTask() {
@Override
public void run() {
Message raceStartStatusMessage = new RaceStartStatusMessage(seqNum, startTime , 1,
RaceStartNotificationType.SET_RACE_START_TIME);
try {
if (startTime < System.currentTimeMillis() && GameState.getCurrentStage() != GameStages.RACING){
}
else{
broadcast(raceStartStatusMessage);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}, 0, RACE_START_STATUS_PERIOD);
}
/**
* Start sending race start status messages until race starts
*/
private void startSendingRaceStatusMessages(){
Timer t = new Timer();
t.schedule(new TimerTask() {
@Override
public void run() {
Message raceStatusMessage = getRaceStatusMessage();
try {
broadcast(raceStatusMessage);
} catch (IOException e) {
e.printStackTrace();
}
}
}, 0, RACE_STATUS_PERIOD);
}
/**
* Sends the race, boat, and regatta XML files to the client
*/
private void sendXml(){
try{
Message raceData = getXmlMessage("/server_config/race.xml", XMLMessageSubType.RACE);
Message boatData = getXmlMessage("/server_config/boats.xml", XMLMessageSubType.BOAT);
Message regatta = getXmlMessage("/server_config/regatta.xml", XMLMessageSubType.REGATTA);
if (raceData != null){
broadcast(raceData);
}
if (boatData != null){
broadcast(boatData);
}
if (regatta != null){
broadcast(regatta);
}
} catch (IOException e) {
serverLog("Couldn't send an XML Message: " + e.getMessage(), 0);
}
}
/**
* Send the post-start race course information
*/
private void sendPostStartCourseXml(){
Timer t = new Timer();
t.schedule(new TimerTask() {
@Override
public void run() {
try {
Message raceData = getXmlMessage("/server_config/courseLimits.xml", XMLMessageSubType.RACE);
if (raceData != null) {
broadcast(raceData);
}
}catch (IOException e) {
serverLog("Couldn't send an XML Message: " + e.getMessage(), 0);
}
}
},25000);
//Delays the new course xml data for 25 seconds so the boats are able to pass the starting line
}
public void run() {
ServerListenThread serverListenThread;
try{
server = ServerSocketChannel.open();
server.socket().bind(new InetSocketAddress("localhost", PORT_NUMBER));
serverListenThread = new ServerListenThread(server, this);
serverListenThread.start();
}
catch (IOException e){
serverLog("Failed to bind socket: " + e.getMessage(), 0);
}
while (hosting) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (GameState.getCurrentStage() == GameStages.RACING) {
System.out.println("Racing");
//startSendingHeartbeats();
sendXml();
//startSendingRaceStartStatusMessages();
//startSendingRaceStatusMessages();
//sendPostStartCourseXml();
}
else if (GameState.getCurrentStage() == GameStages.FINISHED) {
}
startTime = System.currentTimeMillis() + TIME_TILL_RACE_START;
}
startSendingHeartbeats();
sendXml();
startSendingRaceStartStatusMessages();
//startSendingRaceStatusMessages();
sendPostStartCourseXml();
}
// /**
// * Start sending static boat position updates when race has finished
// */
// private void startSendingRaceFinishedBoatPositions(){
// Timer t = new Timer();
// t.schedule(new TimerTask() {
// @Override
// public void run() {
// try {
// for (Boat b : raceSimulator.getBoats()){
// Message m = new BoatLocationMessage(b.getSourceID(), seqNum, b.getLat(),
// b.getLng(), b.getLastPassedCorner().getBearingToNextCorner(),
// ((long) 0));
//
// server.send(m);
// }
//
// } catch (IOException e) {
// e.printStackTrace();
// }
// }
// }, 0, BOAT_LOCATION_PERIOD);
// }
void unicast(Message message, SocketChannel client) throws IOException {
message.send(client); // TODO: 11/07/17 Do we incement seqNum for individual messages?
}
void broadcast(Message message) throws IOException{
for(Player player : GameState.getPlayers()) {
message.send(player.getSocketChannel());
}
seqNum++; // TODO: 11/07/17 Do we increment seqNum for every message or for the one message to everyone
}
@Override
public void clientConnected(Player player) {
if (GameState.getPlayers().size() < MAX_NUM_PLAYERS && GameState.getCurrentStage() == GameStages.LOBBYING) {
System.out.println("Hi");
GameState.addPlayer(player);
}
}
/**
* Send a boat location message when they are updated by the simulator
* @param o .
* @param arg .
*/
@Override
@SuppressWarnings("unchecked")
public void update(Observable o, Object arg) {
/* 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
if(server == null || !server.isStarted()){
return;
}
int numOfBoatsFinished = 0;
for (Boat boat : (List<Boat>) arg){
try {
if (boat.isFinished()) {
numOfBoatsFinished ++;
if (!boatsFinished.get(boat.getSourceID())) {
boatsFinished.put(boat.getSourceID(), true);
}
}
Message m = new BoatLocationMessage(boat.getSourceID(), 1, boat.getLat(),
boat.getLng(), boat.getLastPassedCorner().getBearingToNextCorner(),
((long) boat.getSpeed()));
broadcast(m);
} catch (IOException e) {
serverLog("Couldn't send a boat status message", 3);
return;
}
catch (NullPointerException e){
e.printStackTrace();
}*/
}
// if (numOfBoatsFinished == ((List<Boat>) arg).size()) {
// startSendingRaceFinishedBoatPositions();
// }
//}
}
@@ -0,0 +1,23 @@
package seng302.gameServer;
/**
* An enum describing the states of the game
* Created by wmu16 on 11/07/17.
*/
public enum GameStages {
LOBBYING(0),
PRE_RACE(1),
RACING(2),
FINISHED(3);
private long code;
GameStages(long code) {
this.code = code;
}
public long getCode(){
return code;
}
}
@@ -10,14 +10,15 @@ import java.util.ArrayList;
*/ */
public class GameState { public class GameState {
public static final Integer MAX_NUM_PLAYERS = 10;
private static String hostIpAddress; private static String hostIpAddress;
private static ArrayList<Player> players; private static ArrayList<Player> players;
private static Boolean isRaceStarted; private static Boolean isRaceStarted;
private static GameStages currentStage;
public GameState(String hostIpAddress) { public GameState(String hostIpAddress) {
GameState.hostIpAddress = hostIpAddress; GameState.hostIpAddress = hostIpAddress;
players = new ArrayList<>(); players = new ArrayList<>();
currentStage = GameStages.LOBBYING;
isRaceStarted = false; isRaceStarted = false;
} }
@@ -41,6 +42,13 @@ public class GameState {
return isRaceStarted; return isRaceStarted;
} }
public static GameStages getCurrentStage() {
return currentStage;
}
public static void setCurrentStage(GameStages currentStage) {
GameState.currentStage = currentStage;
}
/** /**
* This iterates through all players and updates each players info to its new state based on its current data * This iterates through all players and updates each players info to its new state based on its current data
@@ -50,8 +58,8 @@ public class GameState {
// TODO: 10/07/17 wmu16 - Update all player info // TODO: 10/07/17 wmu16 - Update all player info
} }
} }
} }
@@ -0,0 +1,45 @@
package seng302.gameServer;
import com.sun.corba.se.spi.activation.Server;
import seng302.models.Player;
import java.io.IOException;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
/**
* A class for a thread to listen to connections
* Created by wmu16 on 11/07/17.
*/
public class ServerListenThread extends Thread{
private ServerSocketChannel socketChannel;
private ClientConnectionDelegate delegate;
public ServerListenThread(ServerSocketChannel socketChannel, ClientConnectionDelegate delegate){
this.socketChannel = socketChannel;
this.delegate = delegate;
}
/**
* Listens for a connection and upon finding one, creates a Player object and adds it to the universal GameState
*/
private void acceptConnection() {
try {
SocketChannel thisClient = socketChannel.accept();
if (thisClient.socket() != null){
Player thisPlayer = new Player(thisClient);
GameState.addPlayer(thisPlayer);
delegate.clientConnected(thisPlayer);
}
} catch (IOException e) {
e.getMessage();
}
}
public void run(){
while (true){
acceptConnection();
}
}
}
+5 -44
View File
@@ -11,61 +11,19 @@ import java.nio.channels.SocketChannel;
public class Player { public class Player {
private SocketChannel socketChannel; private SocketChannel socketChannel;
private Color color; private Yacht yacht;
private Float xPos;
private Float yPos;
private Float heading;
private Float velocity;
private Integer lastMarkPassed; private Integer lastMarkPassed;
public Player(SocketChannel socketChannel) { public Player(SocketChannel socketChannel) {
this.socketChannel = socketChannel; this.socketChannel = socketChannel;
} }
public SocketChannel getSocketChannel() { public SocketChannel getSocketChannel() {
return socketChannel; return socketChannel;
} }
public Color getColor() {
return color;
}
public void setColor(Color color) {
this.color = color;
}
public Float getxPos() {
return xPos;
}
public void setxPos(Float xPos) {
this.xPos = xPos;
}
public Float getyPos() {
return yPos;
}
public void setyPos(Float yPos) {
this.yPos = yPos;
}
public Float getHeading() {
return heading;
}
public void setHeading(Float heading) {
this.heading = heading;
}
public Float getVelocity() {
return velocity;
}
public void setVelocity(Float velocity) {
this.velocity = velocity;
}
public Integer getLastMarkPassed() { public Integer getLastMarkPassed() {
return lastMarkPassed; return lastMarkPassed;
@@ -75,4 +33,7 @@ public class Player {
this.lastMarkPassed = lastMarkPassed; this.lastMarkPassed = lastMarkPassed;
} }
public Yacht getYacht() {
return yacht;
}
} }
+39 -8
View File
@@ -24,6 +24,10 @@ public class Yacht {
private String shortName; private String shortName;
private String boatName; private String boatName;
private String country; private String country;
// Situational data
// Boat status // Boat status
private Integer boatStatus; private Integer boatStatus;
private Integer legNumber; private Integer legNumber;
@@ -31,6 +35,9 @@ public class Yacht {
private Integer penaltiesServed; private Integer penaltiesServed;
private Long estimateTimeAtFinish; private Long estimateTimeAtFinish;
private String position; private String position;
private Double lat;
private Double lon;
private Float heading;
private double velocity; private double velocity;
private Long timeTillNext; private Long timeTillNext;
private Long markRoundTime; private Long markRoundTime;
@@ -191,17 +198,41 @@ public class Yacht {
this.lastMarkRounded = lastMarkRounded; this.lastMarkRounded = lastMarkRounded;
} }
public void setNextMark(Mark nextMark) {
this.nextMark = nextMark;
}
public Mark getNextMark(){
return nextMark;
}
public Double getLat() {
return lat;
}
public void setLat(Double lat) {
this.lat = lat;
}
public Double getLon() {
return lon;
}
public void setLon(Double lon) {
this.lon = lon;
}
public Float getHeading() {
return heading;
}
public void setHeading(Float heading) {
this.heading = heading;
}
@Override @Override
public String toString() { public String toString() {
return boatName; return boatName;
} }
public void setNextMark(Mark nextMark) {
this.nextMark = nextMark;
}
public Mark getNextMark(){
return nextMark;
}
} }