DasherJava / src / dasherJava / core / network / SingleSocketConnection.java
SingleSocketConnection.java
Raw
package dasherJava.core.network;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.ServerSocket;
import java.net.Socket;

public class SingleSocketConnection {
	
	private final String name; //only needed for logging
	
	private final Object monitor = new Object(); //for synchronization
	private final Object monitor2 = new Object(); //for synchronization
	private final Thread serverSocketThread;
	
	private volatile ServerSocket serverSocket = null;
	private volatile Socket currentSocket = null;
	private volatile BufferedReader currentReader = null;
	private volatile BufferedWriter currentWriter = null;
	private volatile boolean shouldExit = false;
	
	public SingleSocketConnection(String name, int port) {
		this.name=name;
		serverSocketThread=new Thread(() -> {
			try {
				serverSocket=new ServerSocket(port, 1);
				while (!shouldExit) {
					try {
						System.out.println(name+": Waiting for connection on port "+port);
						currentSocket=serverSocket.accept();
						currentReader=new BufferedReader(new InputStreamReader(currentSocket.getInputStream()));
						currentWriter=new BufferedWriter(new OutputStreamWriter(currentSocket.getOutputStream()));
						System.out.println(name+": Made new connection");
						synchronized (monitor2) {
							monitor2.notifyAll();
						}
						while (currentSocket!=null) { //loop until current connection is closed
							synchronized (monitor) {
								try {
									monitor.wait();
								} catch (InterruptedException ex) {
									System.out.println(name+": ServerSocketThread: Wait interrupted");
									if (shouldExit) break;
								}
							}
						}
					} catch (IOException ex) {
						if (!shouldExit) {
							System.out.println(name+": ServerSocketThread: IOException: "+ex.getMessage());
							closeReaderAndWriterAndSocket();
						}
					}
				}
				System.out.println(name+": Exiting");
			} catch (IOException|SecurityException|IllegalArgumentException ex) {
				System.out.println(name+": Couldn't open ServerSocket on port "+port);
			}
		}, name+"ServerSocketThread");
		serverSocketThread.start();
	}
	
	public BufferedReader getCurrentReader() { //blocking, waits until there is a connection, but may still
	                                           //return null on rare occasions and should thus be used in a loop
		while (currentReader==null) { //loop until a connection is made
			synchronized (monitor2) {
				try {
					monitor2.wait();
				} catch (InterruptedException ex) {
					System.out.println(name+": Waiting for reader interrupted");
					break;
				}
			}
		}
		return currentReader;
	}
	
	public BufferedWriter getCurrentWriter() { //non-blocking, returns null if there is no connection
		return currentWriter;
	}
	
	public void closeCurrentConnection() {
		closeReaderAndWriterAndSocket();
		synchronized (monitor) {
			monitor.notifyAll();
		}
	}
	
	public void terminate() {
		shouldExit=true;
		if (serverSocket!=null) {
			try {
				serverSocket.close();
			} catch (IOException ex) {
				System.out.println(name+": Couldn't close ServerSocket: "+ex.getMessage());
			}
		}
		serverSocketThread.interrupt();
		closeReaderAndWriterAndSocket();
	}
	
	private void closeReaderAndWriterAndSocket() {
		//Note: It's probably not necessary to close the reader, writer and socket separately since closing
		//the socket should close all streams as well. However, since closing an already closed stream is
		//defined to do nothing, we can explicitly close the reader and writer anyway just to be sure.
		try {
			if (currentWriter!=null) currentWriter.close();
		} catch (IOException ex) {
			System.out.println(name+": Couldn't close writer: "+ex.getMessage());
		}
		currentWriter=null;
		try {
			if (currentSocket!=null) currentSocket.close();
		} catch (IOException ex) {
			System.out.println(name+": Couldn't close socket: "+ex.getMessage());
		}
		currentSocket=null;
		//Must close the reader after closing the socket to avoid a deadlock, since it is not possible
		//to close the BufferedReader itself while another thread is still blocked in a read operation.
		try {
			if (currentReader!=null) currentReader.close();
		} catch (IOException ex) {
			System.out.println(name+": Couldn't close reader: "+ex.getMessage());
		}
		currentReader=null;
	}
}