From 9f99e212324e27c81f259bc29b20427eab56758a Mon Sep 17 00:00:00 2001 From: Fan-Wu Yang Date: Mon, 24 Jul 2017 17:27:49 +1200 Subject: [PATCH] Added functionality for more than one visualiser to connect to the mock. - more than one visualiser can now connect to the mock. - Created class ConnectionAcceptor that will accept and allocate to each socket - Issue of sockets connecting after the race starts then disconnecting will use a socket slot. #story[1010] --- .idea/copyright/profiles_settings.xml | 3 + .../src/main/java/mock/app/App.java | 2 + .../java/mock/app/ConnectionAcceptor.java | 201 ++++++++++++++++++ .../src/main/java/mock/app/Event.java | 7 +- .../src/main/java/mock/app/MockOutput.java | 138 ++---------- .../java/network/Messages/LatestMessages.java | 3 +- .../Controllers/ConnectionController.java | 1 + .../java/visualiser/app/VisualiserInput.java | 3 +- .../java/visualiser/model/RaceConnection.java | 4 + 9 files changed, 233 insertions(+), 129 deletions(-) create mode 100644 .idea/copyright/profiles_settings.xml create mode 100644 racevisionGame/src/main/java/mock/app/ConnectionAcceptor.java diff --git a/.idea/copyright/profiles_settings.xml b/.idea/copyright/profiles_settings.xml new file mode 100644 index 00000000..e7bedf33 --- /dev/null +++ b/.idea/copyright/profiles_settings.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/racevisionGame/src/main/java/mock/app/App.java b/racevisionGame/src/main/java/mock/app/App.java index 06f628d5..d8dbebae 100644 --- a/racevisionGame/src/main/java/mock/app/App.java +++ b/racevisionGame/src/main/java/mock/app/App.java @@ -16,6 +16,8 @@ import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.TransformerException; import java.io.IOException; +import java.net.ServerSocket; +import java.net.Socket; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; diff --git a/racevisionGame/src/main/java/mock/app/ConnectionAcceptor.java b/racevisionGame/src/main/java/mock/app/ConnectionAcceptor.java new file mode 100644 index 00000000..844e7509 --- /dev/null +++ b/racevisionGame/src/main/java/mock/app/ConnectionAcceptor.java @@ -0,0 +1,201 @@ +package mock.app; + +import network.Messages.Enums.XMLMessageType; +import network.Messages.LatestMessages; +import network.Messages.XMLMessage; +import org.mockito.Mock; + +import java.io.DataOutputStream; +import java.io.IOException; +import java.lang.reflect.Array; +import java.net.ServerSocket; +import java.net.Socket; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ArrayBlockingQueue; + +/** + * Connection acceptor for multiple clients + */ +public class ConnectionAcceptor implements Runnable { + + + /** + * Port to expose server on. + */ + private int serverPort = 4942; + /** + * Socket used to listen for clients on. + */ + private ServerSocket serverSocket; + //mock outputs + private ArrayBlockingQueue mockOutputList = new ArrayBlockingQueue<>(16); + //latest messages + private LatestMessages latestMessages; + //Acknowledgement number for packets + private int ackNumber = 0; + //race xml sequence number + private short raceXMLSequenceNumber; + //boat xml sequence number + private short boatXMLSequenceNumber; + //regatta xml sequence number + private short regattaXMLSequenceNumber; + + /** + * Connection Acceptor Constructor + * @param latestMessages Latest messages to be sent + * @throws IOException if a server socket cannot be instantiated. + */ + public ConnectionAcceptor(LatestMessages latestMessages) throws IOException { + + this.latestMessages =latestMessages; + this.serverSocket = new ServerSocket(serverPort); + CheckClientConnection checkClientConnection = new CheckClientConnection(mockOutputList); + new Thread(checkClientConnection).start(); + } + + /** + * Run the Acceptor + */ + @Override + public void run() { + while(true){//should be connections not filled up + try { + System.out.println("Waiting for a connection...");//TEMP DEBUG REMOVE + Socket mockSocket = serverSocket.accept(); + DataOutputStream outToVisualiser = new DataOutputStream(mockSocket.getOutputStream()); + MockOutput mockOutput = new MockOutput(latestMessages, outToVisualiser); + new Thread(mockOutput).start(); + mockOutputList.add(mockOutput); + System.out.println(String.format("%d number of Visualisers Connected.", mockOutputList.size())); + } catch (IOException e) { + e.printStackTrace(); + } + + } + } + + /** + * Nested class to remove disconnected clients + */ + class CheckClientConnection implements Runnable{ + + private ArrayBlockingQueue mocks; + + /** + * Constructor + * @param mocks Mocks "connected" + */ + public CheckClientConnection(ArrayBlockingQueue mocks){ + this.mocks = mocks; + } + + /** + * Run the remover. + */ + @Override + public void run() { + double timeSinceLastHeartBeat = System.currentTimeMillis(); + while(true) { + ArrayBlockingQueue m = new ArrayBlockingQueue(16, true, mocks); + for (MockOutput mo : m) { + try { + mo.sendHeartBeat(); + } catch (IOException e) { + mocks.remove(mo); + } + } + try { + Thread.sleep(100); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } + } + + /** + * 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); + } + + /** + * 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; + } + + /** + * Increments the ackNumber value, and returns it. + * @return Incremented ackNumber. + */ + private int getNextAckNumber(){ + this.ackNumber++; + + return this.ackNumber; + } + + +} diff --git a/racevisionGame/src/main/java/mock/app/Event.java b/racevisionGame/src/main/java/mock/app/Event.java index 080e8405..71cc288c 100644 --- a/racevisionGame/src/main/java/mock/app/Event.java +++ b/racevisionGame/src/main/java/mock/app/Event.java @@ -29,7 +29,7 @@ public class Event { private Polars boatPolars; - private MockOutput mockOutput; + private ConnectionAcceptor mockOutput; private LatestMessages latestMessages; @@ -46,6 +46,9 @@ public class Event { this.raceXML = getRaceXMLAtCurrentTime(raceXML); this.boatXML = boatXML; this.regattaXML = regattaXML; + System.out.println(raceXML); + System.out.println(boatXML); + System.out.println(regattaXML); this.xmlFileType = type; @@ -55,7 +58,7 @@ public class Event { try { - this.mockOutput = new MockOutput(this.latestMessages); + this.mockOutput = new ConnectionAcceptor(latestMessages); new Thread(mockOutput).start(); } catch (IOException e) { diff --git a/racevisionGame/src/main/java/mock/app/MockOutput.java b/racevisionGame/src/main/java/mock/app/MockOutput.java index e217accf..12eaf807 100644 --- a/racevisionGame/src/main/java/mock/app/MockOutput.java +++ b/racevisionGame/src/main/java/mock/app/MockOutput.java @@ -29,18 +29,6 @@ public class MockOutput implements Runnable */ 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. */ @@ -60,22 +48,6 @@ public class MockOutput implements Runnable */ 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. */ @@ -86,13 +58,14 @@ public class MockOutput implements Runnable /** * 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 { + public MockOutput(LatestMessages latestMessages, DataOutputStream outToVisualiser) throws IOException { + + this.outToVisualiser = outToVisualiser; this.lastHeartbeatTime = System.currentTimeMillis(); - this.serverSocket = new ServerSocket(serverPort); this.latestMessages = latestMessages; @@ -155,44 +128,6 @@ public class MockOutput implements Runnable } - - /** - * 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. @@ -266,6 +201,17 @@ public class MockOutput implements Runnable } + /** + * Sends a heartbeat + * @throws IOException if the socket is no longer open at both ends the heartbeat returns an error. + */ + public void sendHeartBeat() throws IOException { + //Sends a heartbeat every so often. + if (timeSinceHeartbeat() >= heartbeatPeriod) { + outToVisualiser.write(parseHeartbeat(createHeartbeatMessage())); + lastHeartbeatTime = System.currentTimeMillis(); + } + } /** * Sending loop of the Server @@ -274,11 +220,6 @@ public class MockOutput implements Runnable 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()) { @@ -307,12 +248,6 @@ public class MockOutput implements Runnable 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. @@ -385,47 +320,4 @@ public class MockOutput implements Runnable 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(); - } - } diff --git a/racevisionGame/src/main/java/network/Messages/LatestMessages.java b/racevisionGame/src/main/java/network/Messages/LatestMessages.java index c0da436b..f35fc52e 100644 --- a/racevisionGame/src/main/java/network/Messages/LatestMessages.java +++ b/racevisionGame/src/main/java/network/Messages/LatestMessages.java @@ -289,8 +289,7 @@ public class LatestMessages extends Observable { * @return True if race, boat, and regatta have an xml message, false otherwise. */ public boolean hasAllXMLMessages() { - - if ((this.regattaXMLMessage == null) || (this.boatXMLMessage == null) || (this.raceXMLMessage == null)) { + if (this.regattaXMLMessage == null || this.boatXMLMessage == null || this.raceXMLMessage == null) { return false; } else { diff --git a/racevisionGame/src/main/java/visualiser/Controllers/ConnectionController.java b/racevisionGame/src/main/java/visualiser/Controllers/ConnectionController.java index 7dacbd5e..dd82065c 100644 --- a/racevisionGame/src/main/java/visualiser/Controllers/ConnectionController.java +++ b/racevisionGame/src/main/java/visualiser/Controllers/ConnectionController.java @@ -75,6 +75,7 @@ public class ConnectionController extends Controller { try{ RaceConnection connection = connectionTable.getSelectionModel().getSelectedItem(); Socket socket = new Socket(connection.getHostname(), connection.getPort()); + socket.setKeepAlive(true); connectionWrapper.setVisible(false); parent.enterLobby(socket); } catch (IOException e) { /* Never reached */ } diff --git a/racevisionGame/src/main/java/visualiser/app/VisualiserInput.java b/racevisionGame/src/main/java/visualiser/app/VisualiserInput.java index d86e0224..0b8102b5 100644 --- a/racevisionGame/src/main/java/visualiser/app/VisualiserInput.java +++ b/racevisionGame/src/main/java/visualiser/app/VisualiserInput.java @@ -20,6 +20,7 @@ import java.nio.ByteBuffer; import java.util.Arrays; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.ArrayBlockingQueue; import static network.Utils.ByteConverter.bytesToShort; @@ -372,6 +373,4 @@ public class VisualiserInput implements Runnable { } } - - } diff --git a/racevisionGame/src/main/java/visualiser/model/RaceConnection.java b/racevisionGame/src/main/java/visualiser/model/RaceConnection.java index 848b747e..cb898e81 100644 --- a/racevisionGame/src/main/java/visualiser/model/RaceConnection.java +++ b/racevisionGame/src/main/java/visualiser/model/RaceConnection.java @@ -33,6 +33,10 @@ public class RaceConnection { try (Socket s = new Socket()){ s.connect(i, 750);//TODO this should be at least a second or two, once moved to its own thread status.set("Ready"); + s.shutdownInput(); + s.shutdownOutput(); + s.close(); + System.out.println(String.valueOf(s.isClosed())); return true; } catch (IOException e) {}