Added ConnectionToServerCommandFactory, and JoinSuccessfulCommand, RaceParticipantsFullCOmmand, ServerFullCommand. Added IncomingHeartBeatCommandFactory, and IncomingHeartBeatCommand. Added ConnectionToServerState, which represents the client's connection state to server. Renamed VisualiserInput to VisualiserRaceController. Added ConnectionToServer, which tracks the client's connection to server. Added ConnectionToServerController, which accepts JoinAcceptance messages, turns them into commands, and passes them to ConnectionToServer. Added IncomingHeartBeatService, which tracks the heart beat status of the connection. Added IncomingHeartBeatController, which accepts HeartBeat messages, turns them into commands, and passes them to IncomingHeartBeatService. Refactored ServerConnection a bit. #story[1095]main
parent
6e5fb62880
commit
89b0aa8b77
@ -1,11 +1,127 @@
|
||||
package network.MessageRouters;
|
||||
|
||||
|
||||
import network.Messages.AC35Data;
|
||||
import network.Messages.Enums.MessageType;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import shared.model.RunnableWithFramePeriod;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* This class routes {@link network.Messages.AC35Data} messages to an appropriate message controller.
|
||||
*/
|
||||
public class MessageRouter {
|
||||
public class MessageRouter implements RunnableWithFramePeriod {
|
||||
|
||||
|
||||
/**
|
||||
* Incoming queue of messages.
|
||||
*/
|
||||
private BlockingQueue<AC35Data> incomingMessages;
|
||||
|
||||
|
||||
/**
|
||||
* The routing map, which maps from a {@link MessageType} to a message queue.
|
||||
*/
|
||||
private Map<MessageType, BlockingQueue<AC35Data>> routeMap = new HashMap<>();
|
||||
|
||||
|
||||
/**
|
||||
* The default routing queue.
|
||||
* Messages without routes are sent here.
|
||||
* Nothing by default, which means unrouted messages are discarded
|
||||
*/
|
||||
private Optional<BlockingQueue<AC35Data>> defaultRoute = Optional.empty();
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Constructs a {@link MessageRouter} with a given incoming message queue.
|
||||
* @param incomingMessages Incoming message queue to read from.
|
||||
*/
|
||||
public MessageRouter(BlockingQueue<AC35Data> incomingMessages) {
|
||||
this.incomingMessages = incomingMessages;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the queue the message router reads from.
|
||||
* Place messages onto this queue to pass them to the router.
|
||||
* @return Queue the message router reads from.
|
||||
*/
|
||||
public BlockingQueue<AC35Data> getIncomingMessageQueue() {
|
||||
return incomingMessages;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adds a route, which routes a given type of message to a given queue.
|
||||
* @param messageType The message type to route.
|
||||
* @param queue The queue to route messages to.
|
||||
*/
|
||||
public void addRoute(MessageType messageType, BlockingQueue<AC35Data> queue) {
|
||||
routeMap.put(messageType, queue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the route for a given {@link MessageType}.
|
||||
* @param messageType MessageType to remove route for.
|
||||
*/
|
||||
public void removeRoute(MessageType messageType) {
|
||||
routeMap.remove(messageType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a given queue as the default route for any unrouted message types.
|
||||
* @param queue Queue to use as default route.
|
||||
*/
|
||||
public void addDefaultRoute(@NotNull BlockingQueue<AC35Data> queue) {
|
||||
defaultRoute = Optional.of(queue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the current default route, if it exists.
|
||||
*/
|
||||
public void removeDefaultRoute() {
|
||||
defaultRoute = Optional.empty();
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
while (!Thread.interrupted()) {
|
||||
|
||||
try {
|
||||
|
||||
AC35Data message = incomingMessages.take();
|
||||
|
||||
|
||||
if (routeMap.containsKey(message.getType())) {
|
||||
//We have a route.
|
||||
routeMap.get(message.getType()).put(message);
|
||||
|
||||
} else {
|
||||
//No route. Use default.
|
||||
if (defaultRoute.isPresent()) {
|
||||
defaultRoute.get().put(message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} catch (InterruptedException e) {
|
||||
Logger.getGlobal().log(Level.SEVERE, "MessageRouter: " + this + " was interrupted on thread: " + Thread.currentThread() + " while reading message.", e);
|
||||
Thread.currentThread().interrupt();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,41 @@
|
||||
package visualiser.Commands.ConnectionToServerCommands;
|
||||
|
||||
|
||||
import mock.exceptions.CommandConstructionException;
|
||||
import mock.model.commandFactory.Command;
|
||||
import network.Messages.AC35Data;
|
||||
import network.Messages.JoinAcceptance;
|
||||
import visualiser.network.ConnectionToServer;
|
||||
|
||||
/**
|
||||
* Factory to create ConnectionToServer commands.
|
||||
*/
|
||||
public class ConnectionToServerCommandFactory {
|
||||
|
||||
/**
|
||||
* Generates a command to execute on server connection based on the type of {@link network.Messages.Enums.JoinAcceptanceEnum}.
|
||||
* @param message The message to turn into a command.
|
||||
* @param connectionToServer The connection for the command to operate on.
|
||||
* @return The command to execute the given action.
|
||||
* @throws CommandConstructionException Thrown if the command cannot be constructed.
|
||||
*/
|
||||
public static Command create(AC35Data message, ConnectionToServer connectionToServer) throws CommandConstructionException {
|
||||
|
||||
if (!(message instanceof JoinAcceptance)) {
|
||||
throw new CommandConstructionException("Message: " + message + " is not a JoinAcceptance message.");
|
||||
}
|
||||
|
||||
JoinAcceptance joinAcceptance = (JoinAcceptance) message;
|
||||
|
||||
|
||||
switch(joinAcceptance.getAcceptanceType()) {
|
||||
|
||||
case JOIN_SUCCESSFUL: return new JoinSuccessfulCommand(joinAcceptance, connectionToServer);
|
||||
case RACE_PARTICIPANTS_FULL: return new RaceParticipantsFullCommand(joinAcceptance, connectionToServer);
|
||||
case SERVER_FULL: return new ServerFullCommand(joinAcceptance, connectionToServer);
|
||||
|
||||
default: throw new CommandConstructionException("Could not create command for JoinAcceptance: " + joinAcceptance + ". Unknown JoinAcceptanceEnum.");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,47 @@
|
||||
package visualiser.Commands.ConnectionToServerCommands;
|
||||
|
||||
import mock.model.commandFactory.Command;
|
||||
import network.Messages.JoinAcceptance;
|
||||
import visualiser.enums.ConnectionToServerState;
|
||||
import visualiser.network.ConnectionToServer;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
|
||||
/**
|
||||
* Command created when a {@link network.Messages.Enums.JoinAcceptanceEnum#JOIN_SUCCESSFUL} {@link network.Messages.JoinAcceptance} message is received.
|
||||
*/
|
||||
public class JoinSuccessfulCommand implements Command {
|
||||
|
||||
/**
|
||||
* The message to operate on.
|
||||
*/
|
||||
private JoinAcceptance joinAcceptance;
|
||||
|
||||
/**
|
||||
* The context to operate on.
|
||||
*/
|
||||
private ConnectionToServer connectionToServer;
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new {@link JoinSuccessfulCommand}, which operates on a given {@link ConnectionToServer}.
|
||||
* @param joinAcceptance The message to operate on.
|
||||
* @param connectionToServer The context to operate on.
|
||||
*/
|
||||
public JoinSuccessfulCommand(JoinAcceptance joinAcceptance, ConnectionToServer connectionToServer) {
|
||||
this.joinAcceptance = joinAcceptance;
|
||||
this.connectionToServer = connectionToServer;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public void execute() {
|
||||
|
||||
connectionToServer.setJoinAcceptance(joinAcceptance);
|
||||
|
||||
connectionToServer.setConnectionState(ConnectionToServerState.CONNECTED);
|
||||
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,47 @@
|
||||
package visualiser.Commands.ConnectionToServerCommands;
|
||||
|
||||
import mock.model.commandFactory.Command;
|
||||
import network.Messages.JoinAcceptance;
|
||||
import visualiser.enums.ConnectionToServerState;
|
||||
import visualiser.network.ConnectionToServer;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
|
||||
/**
|
||||
* Command created when a {@link network.Messages.Enums.JoinAcceptanceEnum#RACE_PARTICIPANTS_FULL} {@link JoinAcceptance} message is received.
|
||||
*/
|
||||
public class RaceParticipantsFullCommand implements Command {
|
||||
|
||||
/**
|
||||
* The message to operate on.
|
||||
*/
|
||||
private JoinAcceptance joinAcceptance;
|
||||
|
||||
/**
|
||||
* The context to operate on.
|
||||
*/
|
||||
private ConnectionToServer connectionToServer;
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new {@link RaceParticipantsFullCommand}, which operates on a given {@link ConnectionToServer}.
|
||||
* @param joinAcceptance The message to operate on.
|
||||
* @param connectionToServer The context to operate on.
|
||||
*/
|
||||
public RaceParticipantsFullCommand(JoinAcceptance joinAcceptance, ConnectionToServer connectionToServer) {
|
||||
this.joinAcceptance = joinAcceptance;
|
||||
this.connectionToServer = connectionToServer;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public void execute() {
|
||||
|
||||
connectionToServer.setJoinAcceptance(joinAcceptance);
|
||||
|
||||
connectionToServer.setConnectionState(ConnectionToServerState.DECLINED);
|
||||
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,47 @@
|
||||
package visualiser.Commands.ConnectionToServerCommands;
|
||||
|
||||
import mock.model.commandFactory.Command;
|
||||
import network.Messages.JoinAcceptance;
|
||||
import visualiser.enums.ConnectionToServerState;
|
||||
import visualiser.network.ConnectionToServer;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
|
||||
/**
|
||||
* Command created when a {@link network.Messages.Enums.JoinAcceptanceEnum#SERVER_FULL} {@link JoinAcceptance} message is received.
|
||||
*/
|
||||
public class ServerFullCommand implements Command {
|
||||
|
||||
/**
|
||||
* The message to operate on.
|
||||
*/
|
||||
private JoinAcceptance joinAcceptance;
|
||||
|
||||
/**
|
||||
* The context to operate on.
|
||||
*/
|
||||
private ConnectionToServer connectionToServer;
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new {@link ServerFullCommand}, which operates on a given {@link ConnectionToServer}.
|
||||
* @param joinAcceptance The message to operate on.
|
||||
* @param connectionToServer The context to operate on.
|
||||
*/
|
||||
public ServerFullCommand(JoinAcceptance joinAcceptance, ConnectionToServer connectionToServer) {
|
||||
this.joinAcceptance = joinAcceptance;
|
||||
this.connectionToServer = connectionToServer;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public void execute() {
|
||||
|
||||
connectionToServer.setJoinAcceptance(joinAcceptance);
|
||||
|
||||
connectionToServer.setConnectionState(ConnectionToServerState.DECLINED);
|
||||
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,47 @@
|
||||
package visualiser.Commands.IncomingHeartBeatCommands;
|
||||
|
||||
import mock.model.commandFactory.Command;
|
||||
import network.Messages.HeartBeat;
|
||||
import network.Messages.JoinAcceptance;
|
||||
import visualiser.enums.ConnectionToServerState;
|
||||
import visualiser.network.ConnectionToServer;
|
||||
import visualiser.network.IncomingHeartBeatService;
|
||||
|
||||
|
||||
/**
|
||||
* Command created when a {@link HeartBeat} message is received.
|
||||
*/
|
||||
public class IncomingHeartBeatCommand implements Command {
|
||||
|
||||
/**
|
||||
* The message to operate on.
|
||||
*/
|
||||
private HeartBeat heartBeat;
|
||||
|
||||
/**
|
||||
* The context to operate on.
|
||||
*/
|
||||
private IncomingHeartBeatService incomingHeartBeatService;
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new {@link IncomingHeartBeatCommand}, which operates on a given {@link IncomingHeartBeatService}.
|
||||
* @param heartBeat The message to operate on.
|
||||
* @param incomingHeartBeatService The context to operate on.
|
||||
*/
|
||||
public IncomingHeartBeatCommand(HeartBeat heartBeat, IncomingHeartBeatService incomingHeartBeatService) {
|
||||
this.heartBeat = heartBeat;
|
||||
this.incomingHeartBeatService = incomingHeartBeatService;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public void execute() {
|
||||
|
||||
incomingHeartBeatService.setLastHeartBeatSeqNum(heartBeat.getSequenceNumber());
|
||||
|
||||
incomingHeartBeatService.setLastHeartbeatTime(System.currentTimeMillis());
|
||||
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,33 @@
|
||||
package visualiser.Commands.IncomingHeartBeatCommands;
|
||||
|
||||
|
||||
import mock.exceptions.CommandConstructionException;
|
||||
import mock.model.commandFactory.Command;
|
||||
import network.Messages.AC35Data;
|
||||
import network.Messages.HeartBeat;
|
||||
import visualiser.network.IncomingHeartBeatService;
|
||||
|
||||
/**
|
||||
* Factory to create IncomingHeartBeatService commands.
|
||||
*/
|
||||
public class IncomingHeartBeatCommandFactory {
|
||||
|
||||
/**
|
||||
* Generates a command on an IncomingHeartBeatService.
|
||||
* @param message The message to turn into a command.
|
||||
* @param incomingHeartBeatService The context for the command to operate on.
|
||||
* @return The command to execute the given action.
|
||||
* @throws CommandConstructionException Thrown if the command cannot be constructed.
|
||||
*/
|
||||
public static Command create(AC35Data message, IncomingHeartBeatService incomingHeartBeatService) throws CommandConstructionException {
|
||||
|
||||
if (!(message instanceof HeartBeat)) {
|
||||
throw new CommandConstructionException("Message: " + message + " is not a HeartBeat message.");
|
||||
}
|
||||
|
||||
HeartBeat heartBeat = (HeartBeat) message;
|
||||
|
||||
return new IncomingHeartBeatCommand(heartBeat, incomingHeartBeatService);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,96 @@
|
||||
package visualiser.enums;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* The states in which a connection from a client to a server may have.
|
||||
*/
|
||||
public enum ConnectionToServerState {
|
||||
|
||||
UNKNOWN(0),
|
||||
|
||||
/**
|
||||
* We're waiting for the server to complete the joining handshake.
|
||||
* See {@link network.Messages.RequestToJoin} and {@link network.Messages.JoinAcceptance}.
|
||||
*/
|
||||
REQUEST_SENT(1),
|
||||
|
||||
/**
|
||||
* The client has receved a {@link network.Messages.JoinAcceptance} from the server.
|
||||
*/
|
||||
RESPONSE_RECEIVED(2),
|
||||
|
||||
/**
|
||||
* The server has completed the handshake, and is connected.
|
||||
* That is, the client sent a {@link network.Messages.RequestToJoin}, which was successful, and the server responded with a {@link network.Messages.JoinAcceptance}.
|
||||
*/
|
||||
CONNECTED(3),
|
||||
|
||||
/**
|
||||
* The server has timed out, or the connection has been interrupted.
|
||||
*/
|
||||
TIMED_OUT(4),
|
||||
|
||||
/**
|
||||
* The client's connection has been declined.
|
||||
*/
|
||||
DECLINED(5);
|
||||
|
||||
|
||||
|
||||
|
||||
private byte value;
|
||||
|
||||
/**
|
||||
* Ctor. Creates a ConnectionToServerState from a given primitive integer value, cast to a byte.
|
||||
* @param value Integer, which is cast to byte, to construct from.
|
||||
*/
|
||||
private ConnectionToServerState(int value) {
|
||||
this.value = (byte) value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the primitive value of the enum.
|
||||
* @return Primitive value of the enum.
|
||||
*/
|
||||
public byte getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Stores a mapping between Byte values and ConnectionToServerState values.
|
||||
*/
|
||||
private static final Map<Byte, ConnectionToServerState> byteToStateMap = new HashMap<>();
|
||||
|
||||
|
||||
/*
|
||||
Static initialization block. Initializes the byteToStateMap.
|
||||
*/
|
||||
static {
|
||||
for (ConnectionToServerState type : ConnectionToServerState.values()) {
|
||||
ConnectionToServerState.byteToStateMap.put(type.value, type);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the enumeration value which corresponds to a given byte value.
|
||||
* @param connectionState Byte value to convert to a ConnectionToServerState value.
|
||||
* @return The ConnectionToServerState value which corresponds to the given byte value.
|
||||
*/
|
||||
public static ConnectionToServerState fromByte(byte connectionState) {
|
||||
//Gets the corresponding ConnectionToServerState from the map.
|
||||
ConnectionToServerState type = ConnectionToServerState.byteToStateMap.get(connectionState);
|
||||
|
||||
if (type == null) {
|
||||
//If the byte value wasn't found, return the UNKNOWN ConnectionToServerState.
|
||||
return ConnectionToServerState.UNKNOWN;
|
||||
} else {
|
||||
//Otherwise, return the ConnectionToServerState.
|
||||
return type;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@ -1,243 +0,0 @@
|
||||
package visualiser.model;
|
||||
|
||||
|
||||
import mock.enums.ConnectionStateEnum;
|
||||
import network.Messages.AC35Data;
|
||||
import network.Messages.Enums.JoinAcceptanceEnum;
|
||||
import network.Messages.Enums.MessageType;
|
||||
import network.Messages.Enums.RequestToJoinEnum;
|
||||
import network.Messages.JoinAcceptance;
|
||||
import network.Messages.LatestMessages;
|
||||
import network.Messages.RequestToJoin;
|
||||
import network.StreamRelated.MessageDeserialiser;
|
||||
import network.StreamRelated.MessageSerialiser;
|
||||
import shared.exceptions.HandshakeException;
|
||||
import visualiser.app.VisualiserInput;
|
||||
import visualiser.gameController.ControllerClient;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.Socket;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* This class handles the client-server connection handshake, and creation of VisualiserInput and ControllerClient.
|
||||
*/
|
||||
public class ServerConnection implements Runnable {
|
||||
|
||||
/**
|
||||
* The socket for the connection to server.
|
||||
*/
|
||||
private Socket socket;
|
||||
|
||||
/**
|
||||
* The source ID that has been allocated to the client.
|
||||
*/
|
||||
private int allocatedSourceID = 0;
|
||||
|
||||
|
||||
/**
|
||||
* Latest snapshot of the race, received from the server.
|
||||
*/
|
||||
private LatestMessages latestMessages;
|
||||
|
||||
|
||||
/**
|
||||
* Used to convert incoming messages into a race snapshot.
|
||||
*/
|
||||
private VisualiserInput visualiserInput;
|
||||
|
||||
/**
|
||||
* Used to send client input to server.
|
||||
*/
|
||||
private ControllerClient controllerClient;
|
||||
|
||||
|
||||
/**
|
||||
* Used to write messages to socket.
|
||||
*/
|
||||
private MessageSerialiser messageSerialiser;
|
||||
|
||||
/**
|
||||
* Stores messages to write to socket.
|
||||
*/
|
||||
private BlockingQueue<AC35Data> outputQueue;
|
||||
|
||||
/**
|
||||
* Used to read messages from socket.
|
||||
*/
|
||||
private MessageDeserialiser messageDeserialiser;
|
||||
|
||||
/**
|
||||
* Stores messages read from socket.
|
||||
*/
|
||||
private BlockingQueue<AC35Data> inputQueue;
|
||||
|
||||
/**
|
||||
* The state of the connection to the client.
|
||||
*/
|
||||
private ConnectionStateEnum connectionState = ConnectionStateEnum.UNKNOWN;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Creates a server connection, using a given socket.
|
||||
* @param socket The socket which connects to the client.
|
||||
* @param latestMessages Latest race snapshot to send to client.
|
||||
* @throws IOException Thrown if there is a problem with the client socket.
|
||||
*/
|
||||
public ServerConnection(Socket socket, LatestMessages latestMessages) throws IOException {
|
||||
this.socket = socket;
|
||||
this.latestMessages = latestMessages;
|
||||
|
||||
this.outputQueue = new LinkedBlockingQueue<>();
|
||||
this.inputQueue = new LinkedBlockingQueue<>();
|
||||
|
||||
|
||||
this.messageSerialiser = new MessageSerialiser(socket.getOutputStream(), outputQueue);
|
||||
this.messageDeserialiser = new MessageDeserialiser(socket.getInputStream(), inputQueue);
|
||||
|
||||
new Thread(messageSerialiser, "ServerConnection()->MessageSerialiser thread " + messageSerialiser).start();
|
||||
new Thread(messageDeserialiser, "ServerConnection()->MessageDeserialiser thread " + messageDeserialiser).start();
|
||||
|
||||
|
||||
this.controllerClient = new ControllerClient(outputQueue);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
handshake();
|
||||
|
||||
} catch (HandshakeException e) {
|
||||
Logger.getGlobal().log(Level.WARNING, "Server handshake failed.", e);
|
||||
Thread.currentThread().interrupt();
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Initiates the handshake with the server.
|
||||
* @throws HandshakeException Thrown if something goes wrong with the handshake.
|
||||
*/
|
||||
private void handshake() throws HandshakeException {
|
||||
|
||||
//This function is a bit messy, and could probably be refactored a bit.
|
||||
|
||||
connectionState = ConnectionStateEnum.WAITING_FOR_HANDSHAKE;
|
||||
|
||||
|
||||
sendRequestToJoinMessage(RequestToJoinEnum.PARTICIPANT);
|
||||
|
||||
|
||||
JoinAcceptance joinAcceptance = waitForJoinAcceptance();
|
||||
|
||||
|
||||
//If we join successfully...
|
||||
if (joinAcceptance.getAcceptanceType() == JoinAcceptanceEnum.JOIN_SUCCESSFUL) {
|
||||
|
||||
this.allocatedSourceID = joinAcceptance.getSourceID();
|
||||
|
||||
|
||||
//new Thread(controllerClient, "ServerConnection.run()->ControllerClient thread " + controllerClient).start();
|
||||
|
||||
}
|
||||
|
||||
this.visualiserInput = new VisualiserInput(latestMessages, inputQueue);
|
||||
new Thread(visualiserInput, "ServerConnection.run()->VisualiserInput thread " + visualiserInput).start();
|
||||
|
||||
|
||||
connectionState = ConnectionStateEnum.CONNECTED;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Waits until the server sends a {@link JoinAcceptance} message, and returns it.
|
||||
* @return The {@link JoinAcceptance} message.
|
||||
* @throws HandshakeException Thrown if we get interrupted while waiting.
|
||||
*/
|
||||
private JoinAcceptance waitForJoinAcceptance() throws HandshakeException {
|
||||
|
||||
try {
|
||||
|
||||
|
||||
while (connectionState == ConnectionStateEnum.WAITING_FOR_HANDSHAKE) {
|
||||
|
||||
AC35Data message = inputQueue.take();
|
||||
|
||||
//We need to wait until they actually send a join request.
|
||||
if (message.getType() == MessageType.JOIN_ACCEPTANCE) {
|
||||
return (JoinAcceptance) message;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
throw new HandshakeException("Handshake failed. Thread: " + Thread.currentThread() + " was interrupted while waiting on the incoming message queue.", e);
|
||||
|
||||
}
|
||||
|
||||
|
||||
throw new HandshakeException("Handshake was cancelled. Connection state is now: " + connectionState);
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sends the server a {@link RequestToJoin} message.
|
||||
* @param requestType The type of request to send
|
||||
* @throws HandshakeException Thrown if the thread is interrupted while placing message on the outgoing message queue.
|
||||
*/
|
||||
private void sendRequestToJoinMessage(RequestToJoinEnum requestType) throws HandshakeException {
|
||||
|
||||
//Send them the source ID.
|
||||
RequestToJoin requestToJoin = new RequestToJoin(requestType);
|
||||
|
||||
try {
|
||||
outputQueue.put(requestToJoin);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
throw new HandshakeException("Handshake failed. Thread: " + Thread.currentThread() + " interrupted while placing RequestToJoin message on outgoing message queue.", e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Determines whether or not this connection is still alive.
|
||||
* This is based off whether the {@link MessageDeserialiser} is still alive.
|
||||
* @return True if it is alive, false otherwise.
|
||||
*/
|
||||
public boolean isAlive() {
|
||||
return messageDeserialiser.isRunning();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the controller client, which writes BoatAction messages to the outgoing queue.
|
||||
* @return The ControllerClient.
|
||||
*/
|
||||
public ControllerClient getControllerClient() {
|
||||
return controllerClient;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the source ID that has been allocated to the client.
|
||||
* @return Source ID allocated to the client. 0 if it hasn't been allocated.
|
||||
*/
|
||||
public int getAllocatedSourceID() {
|
||||
return allocatedSourceID;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,157 @@
|
||||
package visualiser.network;
|
||||
|
||||
|
||||
import mock.model.commandFactory.Command;
|
||||
import network.Messages.AC35Data;
|
||||
import network.Messages.Enums.RequestToJoinEnum;
|
||||
import network.Messages.JoinAcceptance;
|
||||
import network.Messages.RequestToJoin;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import shared.model.RunnableWithFramePeriod;
|
||||
import visualiser.enums.ConnectionToServerState;
|
||||
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* This class tracks the state of the connection to a server.
|
||||
*/
|
||||
public class ConnectionToServer implements RunnableWithFramePeriod {
|
||||
|
||||
|
||||
/**
|
||||
* The state of the connection to the client.
|
||||
*/
|
||||
private ConnectionToServerState connectionState = ConnectionToServerState.UNKNOWN;
|
||||
|
||||
|
||||
/**
|
||||
* The type of join request to make to server.
|
||||
*/
|
||||
private RequestToJoinEnum requestType;
|
||||
|
||||
|
||||
/**
|
||||
* The queue to place outgoing messages on.
|
||||
*/
|
||||
private BlockingQueue<AC35Data> outgoingMessages;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* The {@link JoinAcceptance} message that has been received, if any.
|
||||
*/
|
||||
@Nullable
|
||||
private JoinAcceptance joinAcceptance;
|
||||
|
||||
|
||||
/**
|
||||
* The incoming commands to execute.
|
||||
*/
|
||||
private BlockingQueue<Command> incomingCommands;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Constructs a ConnectionToServer with a given state.
|
||||
* @param connectionState The state of the connection.
|
||||
* @param requestType The type of join request to make to server.
|
||||
* @param incomingCommands The queue of commands to execute.
|
||||
* @param outgoingMessages The queue to place outgoing messages on.
|
||||
*/
|
||||
public ConnectionToServer(ConnectionToServerState connectionState, RequestToJoinEnum requestType, BlockingQueue<Command> incomingCommands, BlockingQueue<AC35Data> outgoingMessages) {
|
||||
this.connectionState = connectionState;
|
||||
this.requestType = requestType;
|
||||
this.incomingCommands = incomingCommands;
|
||||
this.outgoingMessages = outgoingMessages;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the state of this connection.
|
||||
* @return The state of this connection.
|
||||
*/
|
||||
public ConnectionToServerState getConnectionState() {
|
||||
return connectionState;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the state of this connection.
|
||||
* @param connectionState The new state of this connection.
|
||||
*/
|
||||
public void setConnectionState(ConnectionToServerState connectionState) {
|
||||
this.connectionState = connectionState;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the {@link JoinAcceptance} message received from the server, if any.
|
||||
* @return The JoinAcceptance message from server. Null if no response from server.
|
||||
*/
|
||||
@Nullable
|
||||
public JoinAcceptance getJoinAcceptance() {
|
||||
return joinAcceptance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link JoinAcceptance} message received from the server, if any.
|
||||
* @param joinAcceptance The new JoinAcceptance message from server.
|
||||
*/
|
||||
public void setJoinAcceptance(@NotNull JoinAcceptance joinAcceptance) {
|
||||
this.joinAcceptance = joinAcceptance;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
try {
|
||||
sendRequestToJoinMessage(requestType);
|
||||
|
||||
} catch (InterruptedException e) {
|
||||
Logger.getGlobal().log(Level.SEVERE, "ConnectionToServer: " + this + " was interrupted on thread: " + Thread.currentThread() + " while sending RequestToJoin.", e);
|
||||
Thread.currentThread().interrupt();
|
||||
|
||||
}
|
||||
|
||||
while (!Thread.interrupted()) {
|
||||
|
||||
try {
|
||||
Command command = incomingCommands.take();
|
||||
command.execute();
|
||||
|
||||
} catch (InterruptedException e) {
|
||||
Logger.getGlobal().log(Level.SEVERE, "ConnectionToServer: " + this + " was interrupted on thread: " + Thread.currentThread() + " while reading command.", e);
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//If we get interrupted, we consider the connection to have timed-out.
|
||||
connectionState = ConnectionToServerState.TIMED_OUT;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Sends the server a {@link RequestToJoin} message.
|
||||
* @param requestType The type of request to send
|
||||
* @throws InterruptedException Thrown if the thread is interrupted while placing message on the outgoing message queue.
|
||||
*/
|
||||
private void sendRequestToJoinMessage(RequestToJoinEnum requestType) throws InterruptedException {
|
||||
|
||||
//Send them the source ID.
|
||||
RequestToJoin requestToJoin = new RequestToJoin(requestType);
|
||||
|
||||
outgoingMessages.put(requestToJoin);
|
||||
|
||||
connectionState = ConnectionToServerState.REQUEST_SENT;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@ -0,0 +1,78 @@
|
||||
package visualiser.network;
|
||||
|
||||
|
||||
import mock.exceptions.CommandConstructionException;
|
||||
import mock.model.commandFactory.Command;
|
||||
import network.Messages.AC35Data;
|
||||
import shared.model.RunnableWithFramePeriod;
|
||||
import visualiser.Commands.ConnectionToServerCommands.ConnectionToServerCommandFactory;
|
||||
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
|
||||
/**
|
||||
* The controller for connection related messages, coming from the server to the client.
|
||||
*/
|
||||
public class ConnectionToServerController implements RunnableWithFramePeriod {
|
||||
|
||||
|
||||
/**
|
||||
* The incoming queue of messages to act on.
|
||||
*/
|
||||
private BlockingQueue<AC35Data> incomingMessages;
|
||||
|
||||
|
||||
/**
|
||||
* The connection we are acting on.
|
||||
*/
|
||||
private ConnectionToServer connectionToServer;
|
||||
|
||||
|
||||
/**
|
||||
* The queue to place commands on.
|
||||
*/
|
||||
private BlockingQueue<Command> outgoingCommands;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Constructs a {@link ConnectionToServer} controller with the given parameters.
|
||||
* This accepts connection related messages, converts them to commands, and passes them to an outgoing command queue.
|
||||
* @param incomingMessages The message queue to read from.
|
||||
* @param connectionToServer The ConnectionToServer (context) to act on.
|
||||
* @param outgoingCommands The queue to place outgoing commands on.
|
||||
*/
|
||||
public ConnectionToServerController(BlockingQueue<AC35Data> incomingMessages, ConnectionToServer connectionToServer, BlockingQueue<Command> outgoingCommands) {
|
||||
this.incomingMessages = incomingMessages;
|
||||
this.connectionToServer = connectionToServer;
|
||||
this.outgoingCommands = outgoingCommands;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
while (!Thread.interrupted()) {
|
||||
|
||||
try {
|
||||
AC35Data message = incomingMessages.take();
|
||||
Command command = ConnectionToServerCommandFactory.create(message, connectionToServer);
|
||||
outgoingCommands.put(command);
|
||||
|
||||
} catch (CommandConstructionException e) {
|
||||
Logger.getGlobal().log(Level.WARNING, "ConnectionToServerController: " + this + " could not create command from message.", e);
|
||||
|
||||
} catch (InterruptedException e) {
|
||||
Logger.getGlobal().log(Level.SEVERE, "ConnectionToServerController: " + this + " was interrupted on thread: " + Thread.currentThread(), e);
|
||||
Thread.currentThread().interrupt();
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,78 @@
|
||||
package visualiser.network;
|
||||
|
||||
import mock.exceptions.CommandConstructionException;
|
||||
import mock.model.commandFactory.Command;
|
||||
import network.Messages.AC35Data;
|
||||
import shared.model.RunnableWithFramePeriod;
|
||||
import visualiser.Commands.IncomingHeartBeatCommands.IncomingHeartBeatCommandFactory;
|
||||
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
|
||||
/**
|
||||
* The controller for heartbeat related messages, coming from the server to the client.
|
||||
*/
|
||||
public class IncomingHeartBeatController implements RunnableWithFramePeriod {
|
||||
|
||||
|
||||
/**
|
||||
* The incoming queue of messages to act on.
|
||||
*/
|
||||
private BlockingQueue<AC35Data> incomingMessages;
|
||||
|
||||
|
||||
/**
|
||||
* The heart beat service we are acting on.
|
||||
*/
|
||||
private IncomingHeartBeatService incomingHeartBeatService;
|
||||
|
||||
|
||||
/**
|
||||
* The queue to place commands on.
|
||||
*/
|
||||
private BlockingQueue<Command> outgoingCommands;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Constructs a {@link IncomingHeartBeatService} controller with the given parameters.
|
||||
* This accepts connection related messages, converts them to commands, and passes them to an outgoing command queue.
|
||||
* @param incomingMessages The message queue to read from.
|
||||
* @param incomingHeartBeatService The IncomingHeartBeatService (context) to act on.
|
||||
* @param outgoingCommands The queue to place outgoing commands on.
|
||||
*/
|
||||
public IncomingHeartBeatController(BlockingQueue<AC35Data> incomingMessages, IncomingHeartBeatService incomingHeartBeatService, BlockingQueue<Command> outgoingCommands) {
|
||||
this.incomingMessages = incomingMessages;
|
||||
this.incomingHeartBeatService = incomingHeartBeatService;
|
||||
this.outgoingCommands = outgoingCommands;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
while (!Thread.interrupted()) {
|
||||
|
||||
try {
|
||||
AC35Data message = incomingMessages.take();
|
||||
Command command = IncomingHeartBeatCommandFactory.create(message, incomingHeartBeatService);
|
||||
outgoingCommands.put(command);
|
||||
|
||||
} catch (CommandConstructionException e) {
|
||||
Logger.getGlobal().log(Level.WARNING, "IncomingHeartBeatController: " + this + " could not create command from message.", e);
|
||||
|
||||
} catch (InterruptedException e) {
|
||||
Logger.getGlobal().log(Level.SEVERE, "IncomingHeartBeatController: " + this + " was interrupted on thread: " + Thread.currentThread(), e);
|
||||
Thread.currentThread().interrupt();
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,109 @@
|
||||
package visualiser.network;
|
||||
|
||||
import mock.model.commandFactory.Command;
|
||||
import shared.model.RunnableWithFramePeriod;
|
||||
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
|
||||
/**
|
||||
* Tracks the heart beat status of a connection.
|
||||
*/
|
||||
public class IncomingHeartBeatService implements RunnableWithFramePeriod {
|
||||
|
||||
|
||||
/**
|
||||
* Timestamp of the last sent heartbeat message.
|
||||
*/
|
||||
private long lastHeartbeatTime;
|
||||
|
||||
|
||||
/**
|
||||
* Sequence number for heartbeat messages.
|
||||
*/
|
||||
private long lastHeartBeatSeqNum;
|
||||
|
||||
|
||||
/**
|
||||
* The incoming commands to execute.
|
||||
*/
|
||||
private BlockingQueue<Command> incomingCommands;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Creates an {@link IncomingHeartBeatService} which executes commands from a given queue.
|
||||
* @param incomingCommands Queue to read and execute commands from.
|
||||
*/
|
||||
public IncomingHeartBeatService(BlockingQueue<Command> incomingCommands) {
|
||||
this.incomingCommands = incomingCommands;
|
||||
|
||||
this.lastHeartbeatTime = System.currentTimeMillis();
|
||||
this.lastHeartBeatSeqNum = -1;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the last heart beat time to a given value.
|
||||
* @param lastHeartbeatTime Timestamp of heartbeat.
|
||||
*/
|
||||
public void setLastHeartbeatTime(long lastHeartbeatTime) {
|
||||
this.lastHeartbeatTime = lastHeartbeatTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the last heart beat sequence number to a given value.
|
||||
* @param lastHeartBeatSeqNum Sequence number of heartbeat.
|
||||
*/
|
||||
public void setLastHeartBeatSeqNum(long lastHeartBeatSeqNum) {
|
||||
this.lastHeartBeatSeqNum = lastHeartBeatSeqNum;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Calculates the time since last heartbeat, in milliseconds.
|
||||
*
|
||||
* @return Time since last heartbeat, in milliseconds..
|
||||
*/
|
||||
private long timeSinceHeartbeat() {
|
||||
long now = System.currentTimeMillis();
|
||||
return (now - lastHeartbeatTime);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether or not the heartBeat service considers the connection "alive".
|
||||
* Going 10,000ms without receiving a heartBeat means that the connection is "dead".
|
||||
* @return True if alive, false if dead.
|
||||
*/
|
||||
public boolean isAlive() {
|
||||
long heartBeatPeriod = 10000;
|
||||
|
||||
return (timeSinceHeartbeat() < heartBeatPeriod);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
|
||||
while (!Thread.interrupted()) {
|
||||
|
||||
try {
|
||||
Command command = incomingCommands.take();
|
||||
command.execute();
|
||||
|
||||
} catch (InterruptedException e) {
|
||||
Logger.getGlobal().log(Level.SEVERE, "IncomingHeartBeatService: " + this + " was interrupted on thread: " + Thread.currentThread() + " while reading command.", e);
|
||||
Thread.currentThread().interrupt();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,459 @@
|
||||
package visualiser.network;
|
||||
|
||||
|
||||
import mock.model.commandFactory.Command;
|
||||
import network.MessageRouters.MessageRouter;
|
||||
import network.Messages.AC35Data;
|
||||
import network.Messages.Enums.MessageType;
|
||||
import network.Messages.Enums.RequestToJoinEnum;
|
||||
import network.Messages.JoinAcceptance;
|
||||
import network.Messages.LatestMessages;
|
||||
import network.StreamRelated.MessageDeserialiser;
|
||||
import network.StreamRelated.MessageSerialiser;
|
||||
import shared.model.RunnableWithFramePeriod;
|
||||
import visualiser.model.VisualiserRaceController;
|
||||
import visualiser.enums.ConnectionToServerState;
|
||||
import visualiser.gameController.ControllerClient;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.Socket;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* This class handles the client-server connection handshake, and creation of VisualiserInput and ControllerClient.
|
||||
*/
|
||||
public class ServerConnection implements RunnableWithFramePeriod {
|
||||
|
||||
/**
|
||||
* The socket for the connection to server.
|
||||
*/
|
||||
private Socket socket;
|
||||
|
||||
/**
|
||||
* The source ID that has been allocated to the client.
|
||||
*/
|
||||
private int allocatedSourceID = 0;
|
||||
|
||||
|
||||
/**
|
||||
* Latest snapshot of the race, received from the server.
|
||||
*/
|
||||
private LatestMessages latestMessages;
|
||||
|
||||
|
||||
/**
|
||||
* Used to send client input to server.
|
||||
*/
|
||||
private ControllerClient controllerClient;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Used to write messages to socket.
|
||||
*/
|
||||
private MessageSerialiser messageSerialiser;
|
||||
/**
|
||||
* The thread {@link #messageSerialiser} runs on.
|
||||
*/
|
||||
private Thread messageSerialiserThread;
|
||||
|
||||
/**
|
||||
* Used to read messages from socket.
|
||||
*/
|
||||
private MessageDeserialiser messageDeserialiser;
|
||||
/**
|
||||
* The thread {@link #messageDeserialiser} runs on.
|
||||
*/
|
||||
private Thread messageDeserialiserThread;
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Router to route messages to correct queue.
|
||||
*/
|
||||
private MessageRouter messageRouter;
|
||||
/**
|
||||
* The thread {@link #messageRouter} runs on.
|
||||
*/
|
||||
private Thread messageRouterThread;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* The state of the connection to the client.
|
||||
*/
|
||||
private ConnectionToServer connectionToServer;
|
||||
/**
|
||||
* The thread {@link #connectionToServer} runs on.
|
||||
*/
|
||||
private Thread connectionToServerThread;
|
||||
|
||||
/**
|
||||
* The controller which handles JoinAcceptance messages.
|
||||
*/
|
||||
private ConnectionToServerController connectionToServerController;
|
||||
/**
|
||||
* The thread {@link #connectionToServerController} runs on.
|
||||
*/
|
||||
private Thread connectionToServerControllerThread;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Tracks the heartBeat status of the connection.
|
||||
*/
|
||||
private IncomingHeartBeatService heartBeatService;
|
||||
/**
|
||||
* The thread {@link #heartBeatService} runs on.
|
||||
*/
|
||||
private Thread heartBeatServiceThread;
|
||||
|
||||
/**
|
||||
* Tracks the heartBeat status of the connection.
|
||||
*/
|
||||
private IncomingHeartBeatController heartBeatController;
|
||||
/**
|
||||
* The thread {@link #heartBeatController} runs on.
|
||||
*/
|
||||
private Thread heartBeatControllerThread;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Used to convert incoming messages into a race snapshot.
|
||||
*/
|
||||
private VisualiserRaceController visualiserRaceController;
|
||||
/**
|
||||
* The thread {@link #visualiserRaceController} runs on.
|
||||
*/
|
||||
private Thread visualiserInputThread;
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Creates a server connection, using a given socket.
|
||||
* @param socket The socket which connects to the client.
|
||||
* @param latestMessages Latest race snapshot to send to client.
|
||||
* @param requestType The type of join request to make.
|
||||
* @throws IOException Thrown if there is a problem with the client socket.
|
||||
*/
|
||||
public ServerConnection(Socket socket, LatestMessages latestMessages, RequestToJoinEnum requestType) throws IOException {
|
||||
this.socket = socket;
|
||||
this.latestMessages = latestMessages;
|
||||
|
||||
createMessageSerialiser(socket);
|
||||
createMessageDeserialiser(socket);
|
||||
|
||||
createRouter(messageDeserialiser.getMessagesRead());
|
||||
|
||||
createConnectionToServer(requestType);
|
||||
|
||||
|
||||
messageRouterThread.start();
|
||||
|
||||
|
||||
this.controllerClient = new ControllerClient(messageRouter.getIncomingMessageQueue());
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Creates this connection's {@link MessageRouter}, and gives it a queue to read from.
|
||||
* Does not start {@link #messageRouterThread}. Start it after setting up any initial routes.
|
||||
* @param inputQueue Queue for the MessageRouter to read from.
|
||||
*/
|
||||
private void createRouter(BlockingQueue<AC35Data> inputQueue) {
|
||||
this.messageRouter = new MessageRouter(inputQueue);
|
||||
|
||||
this.messageRouterThread = new Thread(messageRouter, "ServerConnection()->MessageRouter thread " + messageRouter);
|
||||
|
||||
//Unrouted messages get sent back to the router. Kind of ugly, but we do this to ensure that no messages are lost while initializing (e.g., XML message being received before setting up the route for it).
|
||||
messageRouter.addDefaultRoute(messageRouter.getIncomingMessageQueue());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates the {@link #connectionToServer} and {@link #connectionToServerController}, and starts their threads.
|
||||
* @param requestType The type of join request to make to server.
|
||||
*/
|
||||
private void createConnectionToServer(RequestToJoinEnum requestType) {
|
||||
|
||||
//ConnectionToServer executes these commands.
|
||||
BlockingQueue<Command> commands = new LinkedBlockingQueue<>();
|
||||
this.connectionToServer = new ConnectionToServer(ConnectionToServerState.UNKNOWN, requestType, commands, messageRouter.getIncomingMessageQueue());
|
||||
|
||||
//ConnectionToServerController receives messages, and places commands on the above command queue.
|
||||
BlockingQueue<AC35Data> incomingJoinMessages = new LinkedBlockingQueue<>();
|
||||
this.connectionToServerController = new ConnectionToServerController(incomingJoinMessages, connectionToServer, commands);
|
||||
|
||||
//Route JoinAcceptance messages to the controller, and RequestToJoin to the socket.
|
||||
this.messageRouter.addRoute(MessageType.JOIN_ACCEPTANCE, incomingJoinMessages);
|
||||
this.messageRouter.addRoute(MessageType.REQUEST_TO_JOIN, messageSerialiser.getMessagesToSend());
|
||||
|
||||
|
||||
//Start the above on new threads.
|
||||
this.connectionToServerThread = new Thread(connectionToServer, "ServerConnection()->ConnectionToServer thread " + connectionToServer);
|
||||
this.connectionToServerThread.start();
|
||||
|
||||
this.connectionToServerControllerThread = new Thread(connectionToServerController,"ServerConnection()->ConnectionToServerController thread " + connectionToServerController);
|
||||
this.connectionToServerControllerThread.start();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates the {@link #messageSerialiser} and starts its thread.
|
||||
* @param socket The socket to write to.
|
||||
* @throws IOException Thrown if we cannot get an outputStream from the socket
|
||||
*/
|
||||
private void createMessageSerialiser(Socket socket) throws IOException {
|
||||
BlockingQueue<AC35Data> outputQueue = new LinkedBlockingQueue<>();
|
||||
this.messageSerialiser = new MessageSerialiser(socket.getOutputStream(), outputQueue);
|
||||
|
||||
this.messageSerialiserThread = new Thread(messageSerialiser, "ServerConnection()->MessageSerialiser thread " + messageSerialiser);
|
||||
this.messageSerialiserThread.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the {@link #messageDeserialiser} and starts its thread.
|
||||
* @param socket The socket to read from.
|
||||
* @throws IOException Thrown if we cannot get an inputStream from the socket
|
||||
*/
|
||||
private void createMessageDeserialiser(Socket socket) throws IOException {
|
||||
BlockingQueue<AC35Data> inputQueue = new LinkedBlockingQueue<>();
|
||||
this.messageDeserialiser = new MessageDeserialiser(socket.getInputStream(), inputQueue);
|
||||
|
||||
this.messageDeserialiserThread = new Thread(messageDeserialiser, "ServerConnection()->MessageDeserialiser thread " + messageDeserialiser);
|
||||
this.messageDeserialiserThread.start();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates the {@link #heartBeatService} and {@link #heartBeatController} and starts its thread.
|
||||
*/
|
||||
private void createHeartBeatService() {
|
||||
|
||||
//IncomingHeartBeatService executes these commands.
|
||||
BlockingQueue<Command> commands = new LinkedBlockingQueue<>();
|
||||
this.heartBeatService = new IncomingHeartBeatService(commands);
|
||||
|
||||
//IncomingHeartBeatController receives messages, and places commands on the above command queue.
|
||||
BlockingQueue<AC35Data> incomingHeartBeatMessages = new LinkedBlockingQueue<>();
|
||||
this.heartBeatController = new IncomingHeartBeatController(incomingHeartBeatMessages, heartBeatService, commands);
|
||||
|
||||
//Route HeartBeat messages to the controller.
|
||||
this.messageRouter.addRoute(MessageType.HEARTBEAT, incomingHeartBeatMessages);
|
||||
|
||||
|
||||
//Start the above on new threads.
|
||||
this.heartBeatServiceThread = new Thread(heartBeatService, "ServerConnection()->IncomingHeartBeatService thread " + connectionToServer);
|
||||
this.heartBeatServiceThread.start();
|
||||
|
||||
this.heartBeatControllerThread = new Thread(heartBeatController,"ServerConnection()->IncomingHeartBeatController thread " + connectionToServerController);
|
||||
this.heartBeatControllerThread.start();
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
private void createVisualiserRace() {
|
||||
|
||||
BlockingQueue<AC35Data> incomingMessages = new LinkedBlockingQueue<>();
|
||||
|
||||
this.visualiserRaceController = new VisualiserRaceController(latestMessages, incomingMessages);
|
||||
this.visualiserInputThread = new Thread(visualiserRaceController, "ServerConnection()->VisualiserInput thread " + visualiserRaceController);
|
||||
this.visualiserInputThread.start();
|
||||
|
||||
//Routes.
|
||||
this.messageRouter.addRoute(MessageType.BOATLOCATION, incomingMessages);
|
||||
this.messageRouter.addRoute(MessageType.RACESTATUS, incomingMessages);
|
||||
this.messageRouter.addRoute(MessageType.RACESTARTSTATUS, incomingMessages);
|
||||
this.messageRouter.addRoute(MessageType.AVGWIND, incomingMessages);
|
||||
this.messageRouter.addRoute(MessageType.COURSEWIND, incomingMessages);
|
||||
this.messageRouter.addRoute(MessageType.CHATTERTEXT, incomingMessages);
|
||||
this.messageRouter.addRoute(MessageType.DISPLAYTEXTMESSAGE, incomingMessages);
|
||||
this.messageRouter.addRoute(MessageType.YACHTACTIONCODE, incomingMessages);
|
||||
this.messageRouter.addRoute(MessageType.YACHTEVENTCODE, incomingMessages);
|
||||
this.messageRouter.addRoute(MessageType.MARKROUNDING, incomingMessages);
|
||||
this.messageRouter.addRoute(MessageType.XMLMESSAGE, incomingMessages);
|
||||
this.messageRouter.removeDefaultRoute();
|
||||
|
||||
//TODO create VisualiserRace here or somewhere else?
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
private void createPlayerInputController() {
|
||||
|
||||
this.messageRouter.addRoute(MessageType.BOATACTION, messageSerialiser.getMessagesToSend());
|
||||
//TODO routes
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
//Monitor the connection state.
|
||||
|
||||
long previousFrameTime = System.currentTimeMillis();
|
||||
|
||||
while (!Thread.interrupted()) {
|
||||
|
||||
long currentFrameTime = System.currentTimeMillis();
|
||||
waitForFramePeriod(previousFrameTime, currentFrameTime, 100);
|
||||
previousFrameTime = currentFrameTime;
|
||||
|
||||
|
||||
ConnectionToServerState state = connectionToServer.getConnectionState();
|
||||
|
||||
switch (state) {
|
||||
|
||||
case CONNECTED:
|
||||
connected();
|
||||
break;
|
||||
|
||||
case DECLINED:
|
||||
declined();
|
||||
break;
|
||||
|
||||
case TIMED_OUT:
|
||||
timedOut();
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Called when the {@link #connectionToServer} state changes to {@link ConnectionToServerState#CONNECTED}.
|
||||
*/
|
||||
private void connected() {
|
||||
|
||||
JoinAcceptance joinAcceptance = connectionToServer.getJoinAcceptance();
|
||||
|
||||
allocatedSourceID = joinAcceptance.getSourceID();
|
||||
|
||||
|
||||
createHeartBeatService();
|
||||
|
||||
createVisualiserRace();
|
||||
|
||||
createPlayerInputController();
|
||||
|
||||
|
||||
//We interrupt as this thread's run() isn't needed anymore.
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Called when the {@link #connectionToServer} state changes to {@link ConnectionToServerState#DECLINED}.
|
||||
*/
|
||||
private void declined() {
|
||||
Logger.getGlobal().log(Level.WARNING, "Server handshake failed. Connection was declined.");
|
||||
|
||||
terminate();
|
||||
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Called when the {@link #connectionToServer} state changes to {@link ConnectionToServerState#TIMED_OUT}.
|
||||
*/
|
||||
private void timedOut() {
|
||||
Logger.getGlobal().log(Level.WARNING, "Server handshake failed. Connection timed out.");
|
||||
|
||||
terminate();
|
||||
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Determines whether or not this connection is still alive.
|
||||
* This is based off whether the {@link #messageDeserialiser}, {@link #messageSerialiser}, and {@link #heartBeatService} are alive.
|
||||
* @return True if it is alive, false otherwise.
|
||||
*/
|
||||
public boolean isAlive() {
|
||||
return messageDeserialiser.isRunning() && messageSerialiser.isRunning() && heartBeatService.isAlive();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the controller client, which writes BoatAction messages to the outgoing queue.
|
||||
* @return The ControllerClient.
|
||||
*/
|
||||
public ControllerClient getControllerClient() {
|
||||
return controllerClient;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the source ID that has been allocated to the client.
|
||||
* @return Source ID allocated to the client. 0 if it hasn't been allocated.
|
||||
*/
|
||||
public int getAllocatedSourceID() {
|
||||
return allocatedSourceID;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Terminates the connection and any running threads.
|
||||
*/
|
||||
public void terminate() {
|
||||
|
||||
if (this.messageRouterThread != null) {
|
||||
this.messageRouterThread.interrupt();
|
||||
}
|
||||
|
||||
|
||||
if (this.messageSerialiserThread != null) {
|
||||
this.messageSerialiserThread.interrupt();
|
||||
}
|
||||
if (this.messageDeserialiserThread != null) {
|
||||
this.messageDeserialiserThread.interrupt();
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (this.connectionToServerThread != null) {
|
||||
this.connectionToServerThread.interrupt();
|
||||
}
|
||||
if (this.connectionToServerControllerThread != null) {
|
||||
this.connectionToServerControllerThread.interrupt();
|
||||
}
|
||||
|
||||
|
||||
if (this.heartBeatServiceThread != null) {
|
||||
this.heartBeatServiceThread.interrupt();
|
||||
}
|
||||
if (this.heartBeatControllerThread != null) {
|
||||
this.heartBeatControllerThread.interrupt();
|
||||
}
|
||||
|
||||
|
||||
if (this.visualiserInputThread != null) {
|
||||
this.visualiserInputThread.interrupt();
|
||||
}
|
||||
//TODO visualiser race?
|
||||
//TODO input controller?
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@ -0,0 +1,150 @@
|
||||
package visualiser.network;
|
||||
|
||||
import mock.model.commandFactory.Command;
|
||||
import network.Messages.AC35Data;
|
||||
import network.Messages.Enums.JoinAcceptanceEnum;
|
||||
import network.Messages.Enums.RequestToJoinEnum;
|
||||
import network.Messages.JoinAcceptance;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import visualiser.Commands.ConnectionToServerCommands.JoinSuccessfulCommand;
|
||||
import visualiser.Commands.ConnectionToServerCommands.RaceParticipantsFullCommand;
|
||||
import visualiser.Commands.ConnectionToServerCommands.ServerFullCommand;
|
||||
import visualiser.enums.ConnectionToServerState;
|
||||
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* Tests the {@link ConnectionToServer} class with a Participant request, and how it reacts to various commands.
|
||||
*/
|
||||
public class ConnectionToServerParticipantTest {
|
||||
|
||||
private ConnectionToServer connectionToServer;
|
||||
private Thread connectionToServerThread;
|
||||
|
||||
private BlockingQueue<AC35Data> outgoingMessages;
|
||||
private BlockingQueue<Command> incomingCommands;
|
||||
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
|
||||
incomingCommands = new LinkedBlockingQueue<>();
|
||||
outgoingMessages = new LinkedBlockingQueue<>();
|
||||
|
||||
connectionToServer = new ConnectionToServer(ConnectionToServerState.UNKNOWN, RequestToJoinEnum.PARTICIPANT, incomingCommands, outgoingMessages);
|
||||
connectionToServerThread = new Thread(connectionToServer);
|
||||
connectionToServerThread.start();
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* When a connection to server is created, is it expected that it will have sent a request and be in the Request_sent state.
|
||||
* @throws Exception On error.
|
||||
*/
|
||||
@Test
|
||||
public void expectRequestSent() throws Exception {
|
||||
|
||||
//Need to wait for connection thread to execute commands.
|
||||
Thread.sleep(20);
|
||||
|
||||
assertEquals(ConnectionToServerState.REQUEST_SENT, connectionToServer.getConnectionState());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* When the connection to server thread is interrupted, it is expected the connection state will become TimedOut.
|
||||
* @throws Exception On error.
|
||||
*/
|
||||
@Test
|
||||
public void interruptTimedOut() throws Exception {
|
||||
|
||||
//Need to wait for connection thread to execute commands.
|
||||
Thread.sleep(20);
|
||||
|
||||
connectionToServerThread.interrupt();
|
||||
connectionToServerThread.join();
|
||||
|
||||
assertEquals(ConnectionToServerState.TIMED_OUT, connectionToServer.getConnectionState());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sends a join successful command. Expects that the connection becomes Connected.
|
||||
* @throws Exception On error.
|
||||
*/
|
||||
@Test
|
||||
public void sendJoinSuccessCommand() throws Exception {
|
||||
int sourceID = 123;
|
||||
JoinAcceptance joinAcceptance = new JoinAcceptance(JoinAcceptanceEnum.JOIN_SUCCESSFUL, sourceID);
|
||||
|
||||
Command command = new JoinSuccessfulCommand(joinAcceptance, connectionToServer);
|
||||
|
||||
incomingCommands.put(command);
|
||||
|
||||
//Need to wait for connection thread to execute commands.
|
||||
Thread.sleep(20);
|
||||
|
||||
assertEquals(ConnectionToServerState.CONNECTED, connectionToServer.getConnectionState());
|
||||
assertTrue(connectionToServer.getJoinAcceptance() != null);
|
||||
assertEquals(sourceID, connectionToServer.getJoinAcceptance().getSourceID());
|
||||
assertNotEquals(0, connectionToServer.getJoinAcceptance().getSourceID());
|
||||
assertEquals(JoinAcceptanceEnum.JOIN_SUCCESSFUL, connectionToServer.getJoinAcceptance().getAcceptanceType());
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sends a participants full command. Expects that the connection becomes Declined.
|
||||
* @throws Exception On error.
|
||||
*/
|
||||
@Test
|
||||
public void sendRaceParticipantsFullCommand() throws Exception {
|
||||
int sourceID = 0;
|
||||
JoinAcceptance joinAcceptance = new JoinAcceptance(JoinAcceptanceEnum.RACE_PARTICIPANTS_FULL, sourceID);
|
||||
|
||||
Command command = new RaceParticipantsFullCommand(joinAcceptance, connectionToServer);
|
||||
|
||||
incomingCommands.put(command);
|
||||
|
||||
//Need to wait for connection thread to execute commands.
|
||||
Thread.sleep(20);
|
||||
|
||||
assertEquals(ConnectionToServerState.DECLINED, connectionToServer.getConnectionState());
|
||||
assertTrue(connectionToServer.getJoinAcceptance() != null);
|
||||
assertEquals(sourceID, connectionToServer.getJoinAcceptance().getSourceID());
|
||||
assertEquals(JoinAcceptanceEnum.RACE_PARTICIPANTS_FULL, connectionToServer.getJoinAcceptance().getAcceptanceType());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sends a server full command. Expects that the connection becomes Declined.
|
||||
* @throws Exception On error.
|
||||
*/
|
||||
@Test
|
||||
public void sendServerFullCommand() throws Exception {
|
||||
int sourceID = 0;
|
||||
JoinAcceptance joinAcceptance = new JoinAcceptance(JoinAcceptanceEnum.SERVER_FULL, sourceID);
|
||||
|
||||
Command command = new ServerFullCommand(joinAcceptance, connectionToServer);
|
||||
|
||||
incomingCommands.put(command);
|
||||
|
||||
//Need to wait for connection thread to execute commands.
|
||||
Thread.sleep(20);
|
||||
|
||||
assertEquals(ConnectionToServerState.DECLINED, connectionToServer.getConnectionState());
|
||||
assertTrue(connectionToServer.getJoinAcceptance() != null);
|
||||
assertEquals(sourceID, connectionToServer.getJoinAcceptance().getSourceID());
|
||||
assertEquals(JoinAcceptanceEnum.SERVER_FULL, connectionToServer.getJoinAcceptance().getAcceptanceType());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
@ -0,0 +1,109 @@
|
||||
package visualiser.network;
|
||||
|
||||
import mock.model.commandFactory.Command;
|
||||
import network.Messages.AC35Data;
|
||||
import network.Messages.Enums.JoinAcceptanceEnum;
|
||||
import network.Messages.Enums.RequestToJoinEnum;
|
||||
import network.Messages.JoinAcceptance;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import visualiser.Commands.ConnectionToServerCommands.JoinSuccessfulCommand;
|
||||
import visualiser.Commands.ConnectionToServerCommands.RaceParticipantsFullCommand;
|
||||
import visualiser.Commands.ConnectionToServerCommands.ServerFullCommand;
|
||||
import visualiser.enums.ConnectionToServerState;
|
||||
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* Tests the {@link ConnectionToServer} class with a Spectator request, and how it reacts to various commands.
|
||||
*/
|
||||
public class ConnectionToServerSpectatorTest {
|
||||
|
||||
private ConnectionToServer connectionToServer;
|
||||
private Thread connectionToServerThread;
|
||||
|
||||
private BlockingQueue<AC35Data> outgoingMessages;
|
||||
private BlockingQueue<Command> incomingCommands;
|
||||
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
|
||||
incomingCommands = new LinkedBlockingQueue<>();
|
||||
outgoingMessages = new LinkedBlockingQueue<>();
|
||||
|
||||
connectionToServer = new ConnectionToServer(ConnectionToServerState.UNKNOWN, RequestToJoinEnum.SPECTATOR, incomingCommands, outgoingMessages);
|
||||
connectionToServerThread = new Thread(connectionToServer);
|
||||
connectionToServerThread.start();
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* When a connection to server is created, is it expected that it will have sent a request and be in the Request_sent state.
|
||||
* @throws Exception On error.
|
||||
*/
|
||||
@Test
|
||||
public void expectRequestSent() throws Exception {
|
||||
|
||||
//Need to wait for connection thread to execute commands.
|
||||
Thread.sleep(20);
|
||||
|
||||
assertEquals(ConnectionToServerState.REQUEST_SENT, connectionToServer.getConnectionState());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sends a join successful command. Expects that the connection becomes Connected.
|
||||
* @throws Exception On error.
|
||||
*/
|
||||
@Test
|
||||
public void sendJoinSuccessCommand() throws Exception {
|
||||
int sourceID = 0;
|
||||
JoinAcceptance joinAcceptance = new JoinAcceptance(JoinAcceptanceEnum.JOIN_SUCCESSFUL, sourceID);
|
||||
|
||||
Command command = new JoinSuccessfulCommand(joinAcceptance, connectionToServer);
|
||||
|
||||
incomingCommands.put(command);
|
||||
|
||||
//Need to wait for connection thread to execute commands.
|
||||
Thread.sleep(20);
|
||||
|
||||
assertEquals(ConnectionToServerState.CONNECTED, connectionToServer.getConnectionState());
|
||||
assertTrue(connectionToServer.getJoinAcceptance() != null);
|
||||
assertEquals(sourceID, connectionToServer.getJoinAcceptance().getSourceID());
|
||||
assertEquals(JoinAcceptanceEnum.JOIN_SUCCESSFUL, connectionToServer.getJoinAcceptance().getAcceptanceType());
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sends a server full command. Expects that the connection becomes Declined.
|
||||
* @throws Exception On error.
|
||||
*/
|
||||
@Test
|
||||
public void sendServerFullCommand() throws Exception {
|
||||
int sourceID = 0;
|
||||
JoinAcceptance joinAcceptance = new JoinAcceptance(JoinAcceptanceEnum.SERVER_FULL, sourceID);
|
||||
|
||||
Command command = new ServerFullCommand(joinAcceptance, connectionToServer);
|
||||
|
||||
incomingCommands.put(command);
|
||||
|
||||
//Need to wait for connection thread to execute commands.
|
||||
Thread.sleep(20);
|
||||
|
||||
assertEquals(ConnectionToServerState.DECLINED, connectionToServer.getConnectionState());
|
||||
assertTrue(connectionToServer.getJoinAcceptance() != null);
|
||||
assertEquals(sourceID, connectionToServer.getJoinAcceptance().getSourceID());
|
||||
assertEquals(JoinAcceptanceEnum.SERVER_FULL, connectionToServer.getJoinAcceptance().getAcceptanceType());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in new issue