package mock.app; import network.Messages.Enums.XMLMessageType; import network.Messages.LatestMessages; import network.Messages.XMLMessage; import org.mockito.Mock; import visualiser.gameController.ControllerServer; import java.io.DataOutputStream; import java.io.IOException; import java.lang.reflect.Array; import java.net.InetAddress; import java.net.ServerSocket; import java.net.Socket; import java.net.UnknownHostException; 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, true); //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(); } public String getAddress() throws UnknownHostException { return InetAddress.getLocalHost().getHostAddress(); } public int getServerPort() { return serverPort; } /** * 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); ControllerServer controllerServer = new ControllerServer(mockSocket); new Thread(mockOutput).start(); new Thread(controllerServer).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) { //System.out.println(mocks.size());//used to see current amount of visualisers connected. 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; } }