From bf7100b28d760b7df59bf8ccede9c8968a75f5ea Mon Sep 17 00:00:00 2001 From: David Westgate Date: Mon, 25 Jan 2021 13:57:29 -0800 Subject: [PATCH] Initial commit --- .gitignore | 2 + Main.java | 28 +++++++ Server.java | 181 ++++++++++++++++++++++++++++++++++++++++++++++ SocketThread.java | 124 +++++++++++++++++++++++++++++++ 4 files changed, 335 insertions(+) create mode 100644 .gitignore create mode 100644 Main.java create mode 100644 Server.java create mode 100644 SocketThread.java diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..52edfe5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*class +*log diff --git a/Main.java b/Main.java new file mode 100644 index 0000000..e3f2985 --- /dev/null +++ b/Main.java @@ -0,0 +1,28 @@ +/* + Main class for server execution. + Arguments: None + + */ +public class Main { + + private static Server server; + + public static void main(String[] args) { + + server = new Server(4000); + System.out.println("Starting up server ...."); + server.start(); + + while (server.getActive() == true) { + //Loop while server is running + } + System.out.println("Server terminating. Goodbye"); + } + + +} + + + + + diff --git a/Server.java b/Server.java new file mode 100644 index 0000000..a31cb14 --- /dev/null +++ b/Server.java @@ -0,0 +1,181 @@ + + +import java.io.BufferedWriter; +import java.io.FileWriter; +import java.io.IOException; +import java.net.ServerSocket; +import java.util.*; + +/* + Server to create socket and manage threads of client connections. + */ +public class Server { + + private final boolean DEBUG = false; + private final int NUMSOCKETS = 5; //Number of socket threads to run + private final int REFRATE = 10; //Refresh period of report, in seconds + private final String fileName = "numbers.log"; + + private ServerSocket serverSocket; + private SocketThread[] threads; + private ThreadGroup threadGroup; + private int port; + private boolean active; + private long refTime; + private ArrayList uniqueList; //A final list of all the unique 9 digit numbers + private List incomingList;//A list to hold all new numbers from socket threads + private int totalUniqueCount; + private int newUniqueCount; + private int newDupesCount; + private BufferedWriter writer; + + //initialize variables, open the socket. + public Server(int port) { + try { + writer = new BufferedWriter(new FileWriter(fileName)); + } catch (IOException e) { + e.printStackTrace(); + } + totalUniqueCount = 0; + newUniqueCount = 0; + newDupesCount = 0; + refTime = System.nanoTime(); + + this.port = port; + active = true; + threads = new SocketThread[5]; + threadGroup = new ThreadGroup("socket threads"); + uniqueList = new ArrayList(); + incomingList = Collections.synchronizedList(new LinkedList()); //thread safe list + } + + //Start the server, called by main + public void start() { + try { + serverSocket = new ServerSocket(port); + } catch (IOException e) { + e.printStackTrace(); + } + + //start all socket threads + for (int i = 0; i < NUMSOCKETS; i++) { + threads[i] = new SocketThread(threadGroup, serverSocket, incomingList); + threads[i].start(); + } + + //Loop forever while all 5 threads running. + while (threadGroup.activeCount() == NUMSOCKETS) { + + //Update unique list and print report every refresh period + if ((System.nanoTime() - refTime) / 1000000000 > REFRATE) { + + updateList(); + displayReport(); + refTime = System.nanoTime(); + } + + } + + stop(); // a thread stops running, implies terminate. Stop the server + updateList(); //Last update after stopping all connections. + try { + writer.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + + //Method called to stop all threads and shut down the server + public void stop() { + + //First, Loop through all threads and tell them to release clients and self-terminate + for (int i = 0; i < NUMSOCKETS; i++) { + if ((threads[i] != null) && (threads[i].isAlive())) { + threads[i].setTerminate(true); + threads[i].setRelease(true); + } + } + + //Second, Wait for each thread to die + for (int i = 0; i < NUMSOCKETS; i++) { + + try { + //Close the server socket + serverSocket.close(); + + //If the thread is still running now, wait for it to die + if (threads[i] != null && threads[i].isAlive()) { + threads[i].join(); + } + + } catch (Exception e) { + e.printStackTrace(); + } + } + + threadGroup.destroy(); //If all threads dead, group will destroy without error + active = false; + } + + + //Update list of unique new numbers from the list of incoming numbers + private void updateList() { + int index = uniqueList.size(); + int newSize = 0; + newUniqueCount = 0; + newDupesCount = 0; + + //Cannot allow other threads to modify incomingList while we are working with it. + synchronized (incomingList) { + uniqueList.addAll(incomingList); + incomingList.clear(); + } + + //Calculating report data + newDupesCount = removeDupes(uniqueList); + newSize = uniqueList.size(); + newUniqueCount = newSize - index; + totalUniqueCount = newSize; + + for (int i = index; i < uniqueList.size(); ++i) { + updateFile(uniqueList.get(i)); + } + } + + //Print report to console + private void displayReport() { + // System.out.print("\033[H\033[2J"); //clear the console (optional) + System.out.println("Received " + newUniqueCount + " unique numbers, " + + newDupesCount + " duplicates. Unique total: " + totalUniqueCount); + } + + //Write the parameter string to the log file + private void updateFile(String newNum) { + try { + writer.write(newNum); + writer.newLine(); + writer.flush(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + //Allow main program to check if server is still active. + public boolean getActive() { + return this.active; + } + + //Remove duplicate entries from src list and return number of duplicates + private int removeDupes(ArrayList src) { + if (src == null || src.size() < 2) { + return 0; + } + int size = src.size(); + LinkedHashSet noDupes = new LinkedHashSet(src); + src.clear(); + src.addAll(noDupes); + return size - src.size(); + } + +} diff --git a/SocketThread.java b/SocketThread.java new file mode 100644 index 0000000..f1f4303 --- /dev/null +++ b/SocketThread.java @@ -0,0 +1,124 @@ + + +import java.io.*; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.SocketException; +import java.util.LinkedList; +import java.util.List; + + +/* +The server creates SocketThread objects to accept new sockets. +SocketThread must manage socket connection and properly process data. + */ +public class SocketThread extends Thread { +private final String expression = "^[0-9]{9}$";//regex for 9 digit number + private final boolean DEBUG = false; + private Socket clientSocket; + private ServerSocket serverSocket; + private ThreadGroup threadGroup; + private InputStream inputStream; + private OutputStream outputStream; + private BufferedReader bufferedReader; + private PrintWriter printWriter; + private List incomingList; + private String line; //Buffer string + private boolean release; //flag to release current client + private boolean terminate; //flag to terminate this thread + + public SocketThread(ThreadGroup threadGroup, ServerSocket serverSocket, List incomingList) { + super(threadGroup, ""); + + this.incomingList = incomingList; + this.serverSocket = serverSocket; + this.threadGroup = threadGroup; + + line = null; + release = false; + terminate = false; + } + + + //Methods allows the server to tell thread to finish up + public void setRelease(boolean release) { + this.release = release; + } + + public void setTerminate(boolean terminate) { + this.terminate = terminate; + } + + + public void run() { + try { + + //Loop to re-open socket and listen after it is closed + while ((clientSocket == null) && !terminate) { + + release = false; + clientSocket = serverSocket.accept(); + inputStream = clientSocket.getInputStream(); + outputStream = clientSocket.getOutputStream(); + bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); + printWriter = new PrintWriter(outputStream, true); + + //Loop to keep reading lines from buffer while socket is connected + while (!release && !terminate) { + line = bufferedReader.readLine(); + + //If the line read is 9 digits + if (line != null && (line.length() == 9)) { + + //Case of valid, 9 digit number + if (line.matches(expression)) { + + //Only allow one thread to add to shared list at a time + synchronized (incomingList){ + incomingList.add(line); + } + } + + //Case of terminate input + else if (line.equals("terminate")) { + release = true; + terminate = true; + } + + //Case of invalid input + else { + release = true; + } + } + + //Case of invalid input + else if(line != null){ + release = true; + } + + //Last, If buffer is null, wait and read again. if still null, assume client closed connection + else{ + sleep(500); + line = bufferedReader.readLine(); + if(line == null){ + release = true; + } + } + } + + //Release the current client + if (release) { + clientSocket.close(); + clientSocket = null; + } + } + } + catch (SocketException e){ + // Do nothing, this exception is expected when another thread causes closure of serverSocket and + // this client socket is still accepting. + } + catch (IOException | InterruptedException e) { + e.printStackTrace(); + } + } +}