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,9 @@
|
||||
package network.MessageControllers;
|
||||
|
||||
|
||||
|
||||
public class MessageController {
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package network.MessageEncoders;
|
||||
|
||||
|
||||
import network.BinaryMessageEncoder;
|
||||
import network.Exceptions.InvalidMessageException;
|
||||
import network.Exceptions.InvalidMessageTypeException;
|
||||
import network.Messages.*;
|
||||
@@ -104,7 +105,7 @@ public class RaceVisionByteEncoder {
|
||||
|
||||
|
||||
/**
|
||||
* Encodes a given message.
|
||||
* Encodes a given message, to be placed inside a binary message (see {@link BinaryMessageEncoder}).
|
||||
* @param message Message to encode.
|
||||
* @return Encoded message.
|
||||
* @throws InvalidMessageException If the message cannot be encoded.
|
||||
@@ -126,4 +127,29 @@ public class RaceVisionByteEncoder {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Encodes a given messages, using a given ackNumber, and returns a binary message ready to be sent over-the-wire.
|
||||
* @param message The message to send.
|
||||
* @param ackNumber The ackNumber of the message.
|
||||
* @return A binary message ready to be transmitted.
|
||||
* @throws InvalidMessageException Thrown if the message cannot be encoded.
|
||||
*/
|
||||
public static byte[] encodeBinaryMessage(AC35Data message, int ackNumber) throws InvalidMessageException {
|
||||
|
||||
//Encodes the message.
|
||||
byte[] encodedMessage = RaceVisionByteEncoder.encode(message);
|
||||
|
||||
//Encodes the full message with header.
|
||||
BinaryMessageEncoder binaryMessageEncoder = new BinaryMessageEncoder(
|
||||
message.getType(),
|
||||
System.currentTimeMillis(),
|
||||
ackNumber,
|
||||
(short) encodedMessage.length,
|
||||
encodedMessage );
|
||||
|
||||
|
||||
return binaryMessageEncoder.getFullMessage();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
package network.MessageRouters;
|
||||
|
||||
|
||||
/**
|
||||
* This class routes {@link network.Messages.AC35Data} messages to an appropriate message controller.
|
||||
*/
|
||||
public class MessageRouter {
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -13,6 +13,12 @@ public class BoatAction extends AC35Data {
|
||||
*/
|
||||
private BoatActionEnum boatAction;
|
||||
|
||||
|
||||
/**
|
||||
* The source ID of the boat this action relates to.
|
||||
*/
|
||||
private int sourceID = 0;
|
||||
|
||||
/**
|
||||
* Constructs a BoatActon message with a given action.
|
||||
* @param boatAction Action to use.
|
||||
@@ -30,4 +36,19 @@ public class BoatAction extends AC35Data {
|
||||
return boatAction;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the boat source ID for this message.
|
||||
* @return The source ID for this message.
|
||||
*/
|
||||
public int getSourceID() {
|
||||
return sourceID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the boat source ID for this message.
|
||||
* @param sourceID The source for this message.
|
||||
*/
|
||||
public void setSourceID(int sourceID) {
|
||||
this.sourceID = sourceID;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,9 +3,7 @@ package network.Messages;
|
||||
import network.Messages.Enums.XMLMessageType;
|
||||
import shared.dataInput.RaceDataSource;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Observable;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* This class contains a set of the latest messages received (e.g., the latest RaceStatus, the latest BoatLocation for each boat, etc...).
|
||||
@@ -44,6 +42,12 @@ public class LatestMessages extends Observable {
|
||||
private CourseWinds courseWinds;
|
||||
|
||||
|
||||
/**
|
||||
* A list of messages containing a snapshot of the race.
|
||||
*/
|
||||
private List<AC35Data> snapshot = new ArrayList<>();
|
||||
|
||||
|
||||
/**
|
||||
* The latest race data XML message.
|
||||
*/
|
||||
@@ -69,6 +73,22 @@ public class LatestMessages extends Observable {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a copy of the race snapshot.
|
||||
* @return Copy of the race snapshot.
|
||||
*/
|
||||
public List<AC35Data> getSnapshot() {
|
||||
return new ArrayList<>(snapshot);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the snapshot of the race.
|
||||
* @param snapshot New snapshot of race.
|
||||
*/
|
||||
public void setSnapshot(List<AC35Data> snapshot) {
|
||||
this.snapshot = snapshot;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
package network.Messages;
|
||||
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
/**
|
||||
* Represents a snapshot of the race's state.
|
||||
* Contains a list of {@link AC35Data} messages.
|
||||
* Send a copy of each message to a connected client.
|
||||
*/
|
||||
public class RaceSnapshot {
|
||||
|
||||
/**
|
||||
* The contents of the snapshot.
|
||||
*/
|
||||
private List<AC35Data> snapshot;
|
||||
|
||||
|
||||
/**
|
||||
* Constructs a snapshot using a given list of messages.
|
||||
* @param snapshot Messages to use as snapshot.
|
||||
*/
|
||||
public RaceSnapshot(List<AC35Data> snapshot) {
|
||||
this.snapshot = snapshot;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets the contents of the snapshot.
|
||||
* This is a shallow copy.
|
||||
* @return Contents of the snapshot.
|
||||
*/
|
||||
public List<AC35Data> getSnapshot() {
|
||||
|
||||
List<AC35Data> copy = new ArrayList<>(snapshot);
|
||||
|
||||
return copy;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
package network.StreamRelated;
|
||||
|
||||
|
||||
import network.BinaryMessageDecoder;
|
||||
import network.Exceptions.InvalidMessageException;
|
||||
import network.MessageEncoders.RaceVisionByteEncoder;
|
||||
import network.Messages.AC35Data;
|
||||
import shared.model.RunnableWithFramePeriod;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import static network.Utils.ByteConverter.bytesToShort;
|
||||
|
||||
/**
|
||||
* This class is responsible for converting data from an input stream into a queue of {@link AC35Data} messages.
|
||||
*/
|
||||
public class MessageDeserialiser implements RunnableWithFramePeriod {
|
||||
|
||||
|
||||
/**
|
||||
* The stream we're reading from.
|
||||
*/
|
||||
private DataInputStream inputStream;
|
||||
|
||||
/**
|
||||
* The messages we've read.
|
||||
*/
|
||||
private BlockingQueue<AC35Data> messagesRead;
|
||||
|
||||
|
||||
/**
|
||||
* Ack numbers used in messages.
|
||||
*/
|
||||
private int ackNumber = 1;
|
||||
|
||||
|
||||
/**
|
||||
* Constructs a new MessageSerialiser to write a queue of messages to a given stream.
|
||||
* @param inputStream The stream to write to.
|
||||
* @param messagesRead The messages to send.
|
||||
*/
|
||||
public MessageDeserialiser(InputStream inputStream, BlockingQueue<AC35Data> messagesRead) {
|
||||
this.inputStream = new DataInputStream(inputStream);
|
||||
this.messagesRead = messagesRead;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Increments the ackNumber value, and returns it.
|
||||
* @return Incremented ackNumber.
|
||||
*/
|
||||
private int getNextAckNumber(){
|
||||
this.ackNumber++;
|
||||
|
||||
return this.ackNumber;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Reads and returns the next message as an array of bytes from the input stream. Use getNextMessage() to get the actual message object instead.
|
||||
* @return Encoded binary message bytes.
|
||||
* @throws IOException Thrown when an error occurs while reading from the input stream.
|
||||
*/
|
||||
private byte[] getNextMessageBytes() throws IOException {
|
||||
inputStream.mark(0);
|
||||
short CRCLength = 4;
|
||||
short headerLength = 15;
|
||||
|
||||
//Read the header of the next message.
|
||||
byte[] headerBytes = new byte[headerLength];
|
||||
inputStream.readFully(headerBytes);
|
||||
|
||||
//Read the message body length.
|
||||
byte[] messageBodyLengthBytes = Arrays.copyOfRange(headerBytes, headerLength - 2, headerLength);
|
||||
short messageBodyLength = bytesToShort(messageBodyLengthBytes);
|
||||
|
||||
//Read the message body.
|
||||
byte[] messageBodyBytes = new byte[messageBodyLength];
|
||||
inputStream.readFully(messageBodyBytes);
|
||||
|
||||
//Read the message CRC.
|
||||
byte[] messageCRCBytes = new byte[CRCLength];
|
||||
inputStream.readFully(messageCRCBytes);
|
||||
|
||||
//Put the head + body + crc into one large array.
|
||||
ByteBuffer messageBytes = ByteBuffer.allocate(headerBytes.length + messageBodyBytes.length + messageCRCBytes.length);
|
||||
messageBytes.put(headerBytes);
|
||||
messageBytes.put(messageBodyBytes);
|
||||
messageBytes.put(messageCRCBytes);
|
||||
|
||||
return messageBytes.array();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Reads and returns the next message object from the input stream.
|
||||
* @return The message object.
|
||||
* @throws IOException Thrown when an error occurs while reading from the input stream.
|
||||
* @throws InvalidMessageException Thrown when the message is invalid in some way.
|
||||
*/
|
||||
private AC35Data getNextMessage() throws IOException, InvalidMessageException
|
||||
{
|
||||
//Get the next message from the socket as a block of bytes.
|
||||
byte[] messageBytes = this.getNextMessageBytes();
|
||||
|
||||
//Decode the binary message into an appropriate message object.
|
||||
BinaryMessageDecoder decoder = new BinaryMessageDecoder(messageBytes);
|
||||
|
||||
return decoder.decode();
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
long previousFrameTime = System.currentTimeMillis();
|
||||
|
||||
while (!Thread.interrupted()) {
|
||||
|
||||
|
||||
long currentFrameTime = System.currentTimeMillis();
|
||||
waitForFramePeriod(previousFrameTime, currentFrameTime, 16);
|
||||
previousFrameTime = currentFrameTime;
|
||||
|
||||
|
||||
//Reads the next message.
|
||||
try {
|
||||
AC35Data message = this.getNextMessage();
|
||||
messagesRead.add(message);
|
||||
}
|
||||
catch (InvalidMessageException | IOException e) {
|
||||
|
||||
Logger.getGlobal().log(Level.WARNING, "Unable to read message.", e);
|
||||
|
||||
try {
|
||||
inputStream.reset();
|
||||
} catch (IOException e1) {
|
||||
Logger.getGlobal().log(Level.WARNING, "Unable to reset inputStream.", e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
package network.StreamRelated;
|
||||
|
||||
|
||||
import network.Exceptions.InvalidMessageException;
|
||||
import network.MessageEncoders.RaceVisionByteEncoder;
|
||||
import network.Messages.AC35Data;
|
||||
import shared.model.RunnableWithFramePeriod;
|
||||
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* This class is responsible for writing a queue of {@link network.Messages.AC35Data} messages to an output stream.
|
||||
*/
|
||||
public class MessageSerialiser implements RunnableWithFramePeriod {
|
||||
|
||||
|
||||
/**
|
||||
* The stream we're writing to.
|
||||
*/
|
||||
private DataOutputStream outputStream;
|
||||
|
||||
/**
|
||||
* The messages we're writing to the stream.
|
||||
*/
|
||||
private BlockingQueue<AC35Data> messagesToSend;
|
||||
|
||||
|
||||
/**
|
||||
* Ack numbers used in messages.
|
||||
*/
|
||||
private int ackNumber = 1;
|
||||
|
||||
/**
|
||||
* Determines whether or not this runnable is currently running.
|
||||
*/
|
||||
private boolean isRunning;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Constructs a new MessageSerialiser to write a queue of messages to a given stream.
|
||||
* @param outputStream The stream to write to.
|
||||
* @param messagesToSend The messages to send.
|
||||
*/
|
||||
public MessageSerialiser(OutputStream outputStream, BlockingQueue<AC35Data> messagesToSend) {
|
||||
this.outputStream = new DataOutputStream(outputStream);
|
||||
this.messagesToSend = messagesToSend;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Increments the ackNumber value, and returns it.
|
||||
* @return Incremented ackNumber.
|
||||
*/
|
||||
private int getNextAckNumber(){
|
||||
this.ackNumber++;
|
||||
|
||||
return this.ackNumber;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether or not this runnable is running.
|
||||
* @return True means that it is still running, false means that it has stopped.
|
||||
*/
|
||||
public boolean isRunning() {
|
||||
return isRunning;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
long previousFrameTime = System.currentTimeMillis();
|
||||
|
||||
isRunning = true;
|
||||
|
||||
while (isRunning) {
|
||||
|
||||
|
||||
long currentFrameTime = System.currentTimeMillis();
|
||||
waitForFramePeriod(previousFrameTime, currentFrameTime, 16);
|
||||
previousFrameTime = currentFrameTime;
|
||||
|
||||
|
||||
//Send the messages.
|
||||
List<AC35Data> messages = new ArrayList<>();
|
||||
messagesToSend.drainTo(messages);
|
||||
|
||||
for (AC35Data message : messages) {
|
||||
try {
|
||||
byte[] messageBytes = RaceVisionByteEncoder.encodeBinaryMessage(message, getNextAckNumber());
|
||||
|
||||
outputStream.write(messageBytes);
|
||||
|
||||
|
||||
} catch (InvalidMessageException e) {
|
||||
Logger.getGlobal().log(Level.WARNING, "Could not encode message: " + message, e);
|
||||
|
||||
} catch (IOException e) {
|
||||
Logger.getGlobal().log(Level.WARNING, "Could not write message to outputStream: " + outputStream, e);
|
||||
isRunning = false;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user