package mock.app; import network.BinaryMessageEncoder; import network.MessageEncoders.RaceVisionByteEncoder; import network.Messages.*; import network.Messages.Enums.MessageType; import network.Messages.Enums.XMLMessageType; import java.io.DataOutputStream; import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; import java.net.SocketException; /** * 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; /** * An object containing the set of latest messages to send. * Every server frame, MockOutput reads messages from this, and send them. */ private LatestMessages latestMessages; /** * Ack numbers used in messages. */ private int ackNumber = 1; /** * Sequence number for race xml messages. */ private short raceXMLSequenceNumber = 1; /** * Sequence number for boat xml messages. */ private short boatXMLSequenceNumber = 1; /** * Sequence number for regatta xml messages. */ private short regattaXMLSequenceNumber = 1; /** * Sequence number for heartbeat messages. */ private int heartbeatSequenceNum = 1; private boolean stop = false; //whether or not hte thread keeps running /** * Ctor. * @param latestMessages The collection of messages to send to connected clients. * @throws IOException if server socket cannot be opened. */ public MockOutput(LatestMessages latestMessages) throws IOException { this.lastHeartbeatTime = System.currentTimeMillis(); this.serverSocket = new ServerSocket(serverPort); this.latestMessages = latestMessages; } /** * Increments the ackNumber value, and returns it. * @return Incremented ackNumber. */ private int getNextAckNumber(){ this.ackNumber++; return this.ackNumber; } /** * 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; } /** * Generates the next heartbeat message and returns it. Increments the heartbeat sequence number. * @return The next heartbeat message. */ private Heartbeat createHeartbeatMessage() { //Create the heartbeat message. Heartbeat heartbeat = new Heartbeat(this.heartbeatSequenceNum); heartbeatSequenceNum++; return heartbeat; } /** * Serializes a heartbeat message into a packet to be sent, and returns the byte array. * @param heartbeat The heartbeat message to serialize. * @return Byte array containing the next heartbeat message. */ private byte[] parseHeartbeat(Heartbeat heartbeat) { //Serializes the heartbeat message. byte[] heartbeatMessage = RaceVisionByteEncoder.heartBeat(heartbeat); //Places the serialized message in a packet. BinaryMessageEncoder binaryMessageEncoder = new BinaryMessageEncoder( MessageType.HEARTBEAT, System.currentTimeMillis(), getNextAckNumber(), (short) heartbeatMessage.length, heartbeatMessage ); return binaryMessageEncoder.getFullMessage(); } /** * Creates an XMLMessage of a specified subtype using the xml contents string. * @param xmlString The contents of the xml file. * @param messageType The subtype of xml message (race, regatta, boat). * @return The created XMLMessage object. */ private XMLMessage createXMLMessage(String xmlString, XMLMessageType messageType) { //Get the correct sequence number to use, and increment it. short sequenceNumber = 0; if (messageType == XMLMessageType.RACE) { sequenceNumber = this.raceXMLSequenceNumber; this.raceXMLSequenceNumber++; } else if (messageType == XMLMessageType.BOAT) { sequenceNumber = this.boatXMLSequenceNumber; this.boatXMLSequenceNumber++; } else if (messageType == XMLMessageType.REGATTA) { sequenceNumber = this.regattaXMLSequenceNumber; this.regattaXMLSequenceNumber++; } //Create the message. XMLMessage message = new XMLMessage( XMLMessage.currentVersionNumber, getNextAckNumber(), System.currentTimeMillis(), messageType, sequenceNumber, xmlString ); return message; } /** * Encodes/serialises a XMLMessage message, and returns it. * @param xmlMessage The XMLMessage message to serialise. * @return The XMLMessage message in a serialised form. */ private synchronized byte[] parseXMLMessage(XMLMessage xmlMessage) { //Serialize the xml message. byte[] encodedXML = RaceVisionByteEncoder.xmlMessage(xmlMessage); //Place the message in a packet. BinaryMessageEncoder binaryMessageEncoder = new BinaryMessageEncoder( MessageType.XMLMESSAGE, System.currentTimeMillis(), xmlMessage.getAckNumber(), //We use the ack number from the xml message. (short) encodedXML.length, encodedXML ); return binaryMessageEncoder.getFullMessage(); } /** * Encodes/serialises a BoatLocation message, and returns it. * @param boatLocation The BoatLocation message to serialise. * @return The BoatLocation message in a serialised form. */ private synchronized byte[] parseBoatLocation(BoatLocation boatLocation){ //Encodes the message. byte[] encodedBoatLoc = RaceVisionByteEncoder.boatLocation(boatLocation); //Encodes the full message with header. BinaryMessageEncoder binaryMessageEncoder = new BinaryMessageEncoder( MessageType.BOATLOCATION, System.currentTimeMillis(), getNextAckNumber(), (short) encodedBoatLoc.length, encodedBoatLoc ); return binaryMessageEncoder.getFullMessage(); } /** * Encodes/serialises a RaceStatus message, and returns it. * @param raceStatus The RaceStatus message to serialise. * @return The RaceStatus message in a serialised form. */ private synchronized byte[] parseRaceStatus(RaceStatus raceStatus){ //Encodes the messages. byte[] encodedRaceStatus = RaceVisionByteEncoder.raceStatus(raceStatus); //Encodes the full message with header. BinaryMessageEncoder binaryMessageEncoder = new BinaryMessageEncoder( MessageType.RACESTATUS, System.currentTimeMillis(), getNextAckNumber(), (short) encodedRaceStatus.length, encodedRaceStatus ); return binaryMessageEncoder.getFullMessage(); } /** * Sending loop of the Server */ public void run() { try { while (!stop){ //Wait for a client to connect. System.out.println("Waiting for a connection...");//TEMP DEBUG REMOVE mockSocket = serverSocket.accept(); outToVisualiser = new DataOutputStream(mockSocket.getOutputStream()); //Wait until all of the xml files have been set. if (!this.latestMessages.hasAllXMLMessages()) { try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } continue; } long previousFrameTime = System.currentTimeMillis(); boolean sentXMLs = false; while(true) { try { long currentFrameTime = System.currentTimeMillis(); //This is the time elapsed, in milliseconds, since the last server "frame". long framePeriod = currentFrameTime - previousFrameTime; //We only attempt to send packets every X milliseconds. long minimumFramePeriod = 16; if (framePeriod >= minimumFramePeriod) { //Sends a heartbeat every so often. if (timeSinceHeartbeat() >= heartbeatPeriod) { outToVisualiser.write(parseHeartbeat(createHeartbeatMessage())); lastHeartbeatTime = System.currentTimeMillis(); } //Send XML messages. if (!sentXMLs) { //Serialise them. byte[] raceXMLBlob = parseXMLMessage(latestMessages.getRaceXMLMessage()); byte[] regattaXMLBlob = parseXMLMessage(latestMessages.getRegattaXMLMessage()); byte[] boatsXMLBlob = parseXMLMessage(latestMessages.getBoatXMLMessage()); //Send them. outToVisualiser.write(raceXMLBlob); outToVisualiser.write(regattaXMLBlob); outToVisualiser.write(boatsXMLBlob); sentXMLs = true; } //Sends the RaceStatus message. if (this.latestMessages.getRaceStatus() != null) { byte[] raceStatusBlob = this.parseRaceStatus(this.latestMessages.getRaceStatus()); this.outToVisualiser.write(raceStatusBlob); } //Send all of the BoatLocation messages. for (int sourceID : this.latestMessages.getBoatLocationMap().keySet()) { //Get the message. BoatLocation boatLocation = this.latestMessages.getBoatLocation(sourceID); if (boatLocation != null) { //Encode. byte[] boatLocationBlob = this.parseBoatLocation(boatLocation); //Write it. this.outToVisualiser.write(boatLocationBlob); } } previousFrameTime = currentFrameTime; } else { //Wait until the frame period will be large enough. long timeToWait = minimumFramePeriod - framePeriod; try { Thread.sleep(timeToWait); } catch (InterruptedException e) { //If we get interrupted, exit the function. e.printStackTrace(); //Re-set the interrupt flag. Thread.currentThread().interrupt(); return; } } } 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) { //Create the message. XMLMessage message = this.createXMLMessage(raceXml, XMLMessageType.RACE); //Place it in LatestMessages. this.latestMessages.setRaceXMLMessage(message); } /** * Sets the Regatta XMl to send. * @param regattaXml XML to send to Client. */ public void setRegattaXml(String regattaXml) { //Create the message. XMLMessage message = this.createXMLMessage(regattaXml, XMLMessageType.REGATTA); //Place it in LatestMessages. this.latestMessages.setRegattaXMLMessage(message); } /** * Sets the Boats XML to send. * @param boatsXml XMl to send to the Client. */ public void setBoatsXml(String boatsXml) { //Create the message. XMLMessage message = this.createXMLMessage(boatsXml, XMLMessageType.BOAT); //Place it in LatestMessages. this.latestMessages.setBoatXMLMessage(message); } public static void main(String argv[]) throws Exception { MockOutput client = new MockOutput(new LatestMessages()); client.run(); } }