programmieren-projekt/src/AsyncSocket.java

238 lines
6.0 KiB
Java

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
/**
* Provides an Interface to communicate using a socket asynchronously
* @author Luca Conte
*/
public class AsyncSocket {
private Socket socket;
private Thread checkerThread;
private Thread connectorThread;
private AsyncSocketListener handler;
private boolean shouldStop = false;
private String sendBuffer = "";
private BufferedReader in;
private BufferedWriter out;
/**
* Creates a socket server and turns the first incoming connection into an AsyncSocket
* @param port the port to listen on for a connection
* @param handler the handler that will be called when a message is received
* @author Luca Conte
*/
public AsyncSocket(int port, AsyncSocketListener handler) {
this.setHandler(handler);
// start server in new thread
this.connectorThread = new Thread(() -> {
try {
ServerSocket serverSocket = new ServerSocket(port);
System.out.println("Waiting for client connection on port " + port);
Socket socket = serverSocket.accept();
System.out.println("Socket connected.");
serverSocket.close();
this.initSocket(socket);
} catch (IOException e) {
e.printStackTrace();
// TODO: proper error handling
}
});
this.connectorThread.start();
}
/**
* Connects to the address provided and turns the resulting socket into an AsyncSocket
* @param address the socket address to connect to
* @param handler the handler that will be called when a message is received
* @author Luca Conte
*/
public AsyncSocket(InetSocketAddress address, AsyncSocketListener handler) {
this.setHandler(handler);
// start client in new thread
this.connectorThread = new Thread(() -> {
System.out.println("Connecting to " + address.toString());
Socket socket = new Socket();
try {
socket.connect(address, 10);
System.out.println("Socket connected.");
this.initSocket(socket);
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException("Connection timed out");
}
});
this.connectorThread.start();
}
/**
* @param socket the socket to be wrapped in an AsyncSocket
* @param handler the handler that will be called when a message is received
* @author Luca Conte
*/
public AsyncSocket(Socket socket, AsyncSocketListener handler) throws IOException {
this.setHandler(handler);
this.initSocket(socket);
}
/**
* creates input and ouput writer / readers as well as a checker thread to repeatedly check
* for incoming messages
* @param socket the socket to be wrapped
* @author Luca Conte
*/
private void initSocket(Socket socket) throws IOException {
System.out.println("Initialising sockets");
this.socket = socket;
this.in = new BufferedReader(new InputStreamReader(this.socket.getInputStream()));
this.out = new BufferedWriter(new OutputStreamWriter(this.socket.getOutputStream()));
this.shouldStop = false;
this.checkerThread = new Thread(() -> {
while (!this.shouldStop) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (!this.connectorThread.isAlive()) {
try {
this.connectorThread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
try {
if (!this.in.ready()) {
continue;
}
if (this.handler == null) {
continue;
}
String message = this.in.readLine();
if (message.length() <= 0) continue;
message = message.strip();
System.out.println("RECEIVED - " + message);
this.handler.receive(message);
} catch (IOException e) {
e.printStackTrace();
}
}
});
System.out.println("starting checker thread");
this.checkerThread.start();
this.flushBuffer();
}
/**
* sets the message handler for the async socket
* @param handler the `AsyncSocketListener` to be set as the new handler
* @author Luca Conte
*/
public void setHandler(AsyncSocketListener handler) {
this.handler = handler;
}
/**
* sends a message through the socket
* @param socketPackage the socket package to be sent
* @author Luca Conte
*/
public synchronized void send(SocketPackage socketPackage) {
this.sendLine(socketPackage.toString());
}
/**
* sends a message through the socket
* @param packageName the name of the package to be sent
* @author Luca Conte
*/
public synchronized void send(String packageName) {
this.send(packageName, "");
}
/**
* sends a message through the socket
* @param packageName the name of the package to be sent
* @param packageContent the content of the package to be sent.
* `packageName` and `packageContent` are joined with a space " "
* @author Luca Conte
*/
public synchronized void send(String packageName, String packageContent) {
if (packageContent.length() > 0) {
packageContent = " " + packageContent;
}
this.sendLine(packageName + packageContent);
}
/**
* sends a string of text into the socket, concatenated with CRLF
* @author Luca Conte
*/
public synchronized void sendLine(String message) {
sendBuffer = sendBuffer + message + "\r\n";
this.flushBuffer();
}
/**
* flushes the buffers to send all pending messages
* @author Luca Conte
*/
private synchronized void flushBuffer() {
if (!this.sendBuffer.isEmpty() && this.out != null) {
try {
this.out.write(sendBuffer);
System.out.println("SENT - " + sendBuffer);
sendBuffer = "";
this.out.flush();
} catch (IOException e) {
e.printStackTrace();
// TODO: handle writing error
}
}
}
/**
* closes the socket connection and removes the checker thread
* @author Luca Conte
*/
public void close() {
this.shouldStop = true;
try {
this.socket.close();
if (this.checkerThread != null) {
this.checkerThread.interrupt();
this.checkerThread.join();
}
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}