package eu.hgross.blaubot.ethernet; import java.io.IOException; import java.net.BindException; import java.net.InetSocketAddress; import java.net.ServerSocket; import java.net.Socket; import java.net.SocketException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import eu.hgross.blaubot.core.acceptor.IBlaubotIncomingConnectionListener; import eu.hgross.blaubot.core.acceptor.discovery.BlaubotBeaconService; import eu.hgross.blaubot.util.Log; /** * Accepting connections to the beacon and hands them to the {@link IBlaubotIncomingConnectionListener} which will * be the {@link BlaubotBeaconService}. * * The accept thread - once started - will check if he is in charge using the {@link IEthernetBeacon}s getAcceptThread() method * and kill himself, if he is obsolete. * * @author Henning Gross {@literal (mail.to@henning-gross.de)} * */ public class EthernetBeaconAcceptThread extends Thread { private static final String LOG_TAG = "EthernetBeaconAcceptThread"; /** * This is the timeout for read operations on the accepted client {@link Socket}s. * If not set a crashed client would cause a resource blocking {@link Thread} without a chance * to finish. */ private static final int SOCKET_TIMEOUT = 5000; private ServerSocket serverSocket; private final IEthernetBeacon ethernetBeacon; private final IBlaubotIncomingConnectionListener incomingConnectionListener; private ExecutorService executorService = Executors.newCachedThreadPool(); /** * @param incomingConnectionListener the listener to report to * @param ethernetBeacon the {@link IEthernetBeacon} using this object */ public EthernetBeaconAcceptThread(IBlaubotIncomingConnectionListener incomingConnectionListener, IEthernetBeacon ethernetBeacon) { setName("ethernet-beacon-accept-thread"); this.ethernetBeacon = ethernetBeacon; this.incomingConnectionListener = incomingConnectionListener; } @Override public void interrupt() { if(serverSocket != null) { if (Log.logDebugMessages()) { Log.d(LOG_TAG, "Closing ServerSocket ..."); } try { serverSocket.close(); } catch (IOException e) { if (Log.logWarningMessages()) { Log.w(LOG_TAG, "Got IOException during close!"); } } serverSocket = null; } super.interrupt(); } @Override public void run() { if (Log.logDebugMessages()) { Log.d(LOG_TAG, "BeaconAcceptThread started ..."); } final boolean shouldRun = !isInterrupted() && Thread.currentThread() == ethernetBeacon.getAcceptThread(); if(!shouldRun) { return; } ServerSocket lServerSocket; int beaconPort = ethernetBeacon.getBeaconPort(); try { lServerSocket = new ServerSocket(); lServerSocket.setReuseAddress(true); lServerSocket.bind(new InetSocketAddress(beaconPort)); serverSocket = lServerSocket; } catch (BindException e1) { if(Log.logErrorMessages()) { Log.e(LOG_TAG, "Could not bind socket to port " + beaconPort + "; Error: " + e1); } try { // retry every 300 ms until stopped Thread.sleep(300); run(); } catch (InterruptedException e) { } finally { return; } //throw new RuntimeException(e1); } catch (IOException e1) { if (Log.logErrorMessages()) { Log.e(LOG_TAG, "Failed to create ServerSocket on port " + beaconPort + ": " + e1.getMessage(), e1); } return; } // this is busy wait (for ~3 to ~5 iterations) - I can live with that. Socket clientSocket; while (!isInterrupted() && this.serverSocket != null && Thread.currentThread() == ethernetBeacon.getAcceptThread()) { try { if (Log.logDebugMessages()) { Log.d(LOG_TAG, "Waiting for incoming beacon connections ..."); } clientSocket = lServerSocket.accept(); } catch (IOException e) { if (Log.logWarningMessages() && !lServerSocket.isClosed()) { Log.w(LOG_TAG, "Beacon communication failed with I/O Exception (could not accept() -> " + e.getMessage() + ")", e); } continue; } if (Log.logDebugMessages()) { Log.d(LOG_TAG, "Got a new beacon connection from " + clientSocket); } // Dispatch to executor service final Socket finalClientSocket = clientSocket; executorService.execute(new Runnable() { @Override public void run() { try { // Set a timeout to avoid zombie threads @see SOCKET_TIMEOUT finalClientSocket.setSoTimeout(SOCKET_TIMEOUT); } catch (SocketException e) { if(Log.logErrorMessages()) { Log.e(LOG_TAG, "Failed to set socket timeout for incoming beacon client socket! This will cause a memory leak!"); } } BlaubotEthernetConnection connection = BlaubotEthernetUtils.getEthernetConnectionFromSocket(finalClientSocket); if(connection == null) { if (Log.logWarningMessages()) { Log.w(LOG_TAG, "Failed to create connection from incoming socket. Closed connection."); } return; } if (incomingConnectionListener != null) { incomingConnectionListener.onConnectionEstablished(connection); } else { if (Log.logWarningMessages()) { Log.w(LOG_TAG, "Got a beacon connection but no acceptor listener was there to handle it!"); } connection.disconnect(); } } }); } if (Log.logDebugMessages()) { Log.d(LOG_TAG, "BeaconAcceptThread finished ..."); } if(serverSocket != null) { if(!serverSocket.isClosed()) { try { serverSocket.close(); } catch (IOException e) { if(Log.logErrorMessages()) { Log.e(LOG_TAG, "Could not close serverSocket! " + e.getMessage()); } } } } } }