WIP. Probably need to cherry pick stuff out of here.
Added ClientConnection and server-side handshake. Added MessageSerialiser and Deserialiser. #story[1095]
This commit is contained in:
@@ -0,0 +1,228 @@
|
||||
package visualiser.model;
|
||||
|
||||
|
||||
import mock.app.MockOutput;
|
||||
import mock.enums.ConnectionStateEnum;
|
||||
import mock.exceptions.SourceIDAllocationException;
|
||||
import mock.model.HeartBeatService;
|
||||
import mock.model.SourceIdAllocator;
|
||||
import mock.model.commandFactory.CompositeCommand;
|
||||
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 visualiser.gameController.ControllerServer;
|
||||
|
||||
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;
|
||||
|
||||
|
||||
/**
|
||||
* 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();
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@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;
|
||||
|
||||
|
||||
sendJoinAcceptanceMessage(RequestToJoinEnum.PARTICIPANT);
|
||||
|
||||
|
||||
JoinAcceptance joinAcceptance = waitForJoinAcceptance();
|
||||
|
||||
int allocatedSourceID = 0;
|
||||
|
||||
//If we join successfully...
|
||||
if (joinAcceptance.getAcceptanceType() == JoinAcceptanceEnum.JOIN_SUCCESSFUL) {
|
||||
|
||||
allocatedSourceID = joinAcceptance.getSourceID();
|
||||
//TODO need to do something with the ID - maybe flag the correct visualiser boat as being the client's boat?
|
||||
|
||||
this.controllerClient = new ControllerClient(inputQueue);
|
||||
//new Thread(controllerClient, "ServerConnection.run()->ControllerClient thread " + controllerClient).start();
|
||||
|
||||
}
|
||||
|
||||
this.visualiserInput = new VisualiserInput(latestMessages, outputQueue);
|
||||
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 sendJoinAcceptanceMessage(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 MessageSerialiser} is still alive.
|
||||
* @return True if it is alive, false otherwise.
|
||||
*/
|
||||
public boolean isAlive() {
|
||||
return messageSerialiser.isRunning();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user