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