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;
}
}