You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

252 lines
8.5 KiB

package seng302;
import seng302.Networking.BinaryMessageEncoder;
import seng302.Networking.MessageEncoders.RaceVisionByteEncoder;
import seng302.Networking.MessageEncoders.XMLMessageEncoder;
import seng302.Networking.Messages.BoatLocation;
import seng302.Networking.Messages.Enums.MessageType;
import seng302.Networking.Messages.RaceStatus;
import seng302.Networking.Messages.XMLMessage;
import java.io.*;
import java.net.*;
import java.util.concurrent.ArrayBlockingQueue;
/**
* TCP server to send race information to connected clients.
*/
public class MockOutput implements Runnable
{
///Timestamp of the last sent heartbeat message.
private long lastHeartbeatTime;
///Period for the heartbeat - that is, how often we send it.
private double heartbeatPeriod = 5.0;
///Port to expose server on.
private int serverPort = 4942;
///Socket used to listen for clients on.
private ServerSocket serverSocket;
///Socket used to communicate with a client.
private Socket mockSocket;
///Output stream which wraps around mockSocket outstream.
private DataOutputStream outToVisualiser;
///A queue that contains items that are waiting to be sent.
private ArrayBlockingQueue<byte[]> messagesToSendQueue = new ArrayBlockingQueue<>(99999999);
///Sequence numbers used in messages.
private short messageNumber = 1;
private short xmlSequenceNumber = 1;
private int heartbeatSequenceNum = 1;
private int boatLocationSequenceNumber = 1;
private int raceStatusSequenceNumber = 1;
///Strings containing XML data as strings.
private String raceXml;
private String regattaXml;
private String boatsXml;
private boolean stop = false; //whether or not hte thread keeps running
/**
* Ctor.
* @throws IOException if server socket cannot be opened.
*/
public MockOutput() throws IOException {
lastHeartbeatTime = System.currentTimeMillis();
serverSocket = new ServerSocket(serverPort);
}
/**
* Calculates the time since last heartbeat message, in seconds.
* @return Time since last heartbeat message, in seconds.
*/
private double timeSinceHeartbeat() {
long now = System.currentTimeMillis();
return (now - lastHeartbeatTime) / 1000.0;
}
//returns the heartbeat message
/**
* Increment the heartbeat value
* @return message for heartbeat data
*/
private byte[] heartbeat(){
byte[] heartbeatMessage = RaceVisionByteEncoder.heartBeat(heartbeatSequenceNum);
heartbeatSequenceNum++;
BinaryMessageEncoder binaryMessageEncoder = new BinaryMessageEncoder(MessageType.HEARTBEAT, System.currentTimeMillis(), messageNumber, (short)heartbeatMessage.length, heartbeatMessage);
messageNumber++;
return binaryMessageEncoder.getFullMessage();
}
/**
* Used to give the mockOutput an xml string to be made into a message and sent
* @param xmlString the xml string to send
* @param messageType the kind of xml string, values given in AC35 spec (5 regatta, 6 race, 7 boat)
*/
public synchronized void parseXMLString(String xmlString, int messageType){
XMLMessageEncoder encoder = new XMLMessageEncoder(messageNumber, System.currentTimeMillis(), messageType, xmlSequenceNumber,(short) xmlString.length(), xmlString);
//iterates the sequence numbers
xmlSequenceNumber++;
byte[] encodedXML = encoder.encode();
BinaryMessageEncoder binaryMessageEncoder = new BinaryMessageEncoder(MessageType.XMLMESSAGE, System.currentTimeMillis(), messageNumber, (short)encodedXML.length, encodedXML);
//iterates the message number
messageNumber++;
addMessageToBufferToSend(binaryMessageEncoder.getFullMessage());
}
/**
* Used to give the mocOutput information about boat location to be made into a message and sent
* @param sourceID id of the boat
* @param lat latitude of boat
* @param lon longitude of boat
* @param heading heading of boat
* @param speed speed of boat
* @param time historical time of race
*/
public synchronized void parseBoatLocation(int sourceID, double lat, double lon, double heading, double speed, long time){
BoatLocation boatLocation = new BoatLocation(sourceID, lat, lon, boatLocationSequenceNumber, heading, speed, time);
//iterates the sequence number
boatLocationSequenceNumber++;
//encodeds the messages
byte[] encodedBoatLoc = RaceVisionByteEncoder.boatLocation(boatLocation);
//encodeds the full message with header
BinaryMessageEncoder binaryMessageEncoder = new BinaryMessageEncoder(MessageType.BOATLOCATION, System.currentTimeMillis(), messageNumber, (short)encodedBoatLoc.length,
encodedBoatLoc);
//iterates the message number
messageNumber++;
addMessageToBufferToSend(binaryMessageEncoder.getFullMessage());
}
/**
* Parse the race status data and add it to the buffer to be sent
* @param raceStatus race status to parses
*/
public synchronized void parseRaceStatus(RaceStatus raceStatus){
//iterates the sequence number
raceStatusSequenceNumber++;
//encodeds the messages
byte[] encodedRaceStatus = RaceVisionByteEncoder.raceStatus(raceStatus);
//encodeds the full message with header
BinaryMessageEncoder binaryMessageEncoder = new BinaryMessageEncoder(MessageType.RACESTATUS, System.currentTimeMillis(), messageNumber, (short)encodedRaceStatus.length,
encodedRaceStatus);
//iterates the message number
messageNumber++;
addMessageToBufferToSend(binaryMessageEncoder.getFullMessage());
}
/**
* Add a message to the buffer to be sent
* @param messagesToSendBuffer message to add to the buffer
*/
private synchronized void addMessageToBufferToSend(byte[] messagesToSendBuffer) {
this.messagesToSendQueue.add(messagesToSendBuffer);
}
/**
* Sending loop of the Server
*/
public void run() {
try {
while (!stop){
System.out.println("Waiting for a connection...");//TEMP DEBUG REMOVE
mockSocket = serverSocket.accept();
outToVisualiser = new DataOutputStream(mockSocket.getOutputStream());
if (boatsXml == null || regattaXml == null || raceXml == null){
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
continue;
}
parseXMLString(raceXml, XMLMessage.XMLTypeRace);
parseXMLString(regattaXml, XMLMessage.XMLTypeRegatta);
parseXMLString(boatsXml, XMLMessage.XMLTypeBoat);
while(true) {
try {
//Sends a heartbeat every so often.
if (timeSinceHeartbeat() >= heartbeatPeriod) {
outToVisualiser.write(heartbeat());
lastHeartbeatTime = System.currentTimeMillis();
}
//Checks the buffer to see if there is anything to send.
while (messagesToSendQueue.size() > 0) {
//Grabs message from head of queue.
byte[] binaryMessage = messagesToSendQueue.remove();
//sends the message to the visualiser
outToVisualiser.write(binaryMessage);
}
}catch(SocketException e){
break;
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
public void stop(){
stop = true;
}
/**
* Sets the Race XML to send
* @param raceXml XML to send to the CLient
*/
public void setRaceXml(String raceXml) {
this.raceXml = raceXml;
}
/**
* Sets the Regatta XMl to send
* @param regattaXml XML to send to CLient
*/
public void setRegattaXml(String regattaXml) {
this.regattaXml = regattaXml;
}
/**
* Sets the Boats XML to send
* @param boatsXml XMl to send to the CLient
*/
public void setBoatsXml(String boatsXml) {
this.boatsXml = boatsXml;
}
public static void main(String argv[]) throws Exception
{
MockOutput client = new MockOutput();
client.run();
}
}