package com.framsticks.hosting; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.LogManager; import com.framsticks.params.ParamFlags; import com.framsticks.params.annotations.AutoAppendAnnotation; import com.framsticks.params.annotations.FramsClassAnnotation; import com.framsticks.params.annotations.ParamAnnotation; import com.framsticks.structure.Tree; import com.framsticks.util.FramsticksException; import com.framsticks.util.dispatching.AbstractJoinable; import com.framsticks.util.dispatching.Dispatching; import com.framsticks.util.dispatching.Joinable; import com.framsticks.util.dispatching.JoinableCollection; import com.framsticks.util.dispatching.JoinableParent; import com.framsticks.util.dispatching.JoinableState; import com.framsticks.util.dispatching.RunAt; import com.framsticks.util.dispatching.ThrowExceptionHandler; import java.io.IOException; import java.net.InetSocketAddress; import java.net.ServerSocket; import java.net.Socket; import java.util.TimerTask; import com.framsticks.util.dispatching.Thread; @FramsClassAnnotation public class Server extends AbstractJoinable implements JoinableParent { private final static Logger log = LogManager.getLogger(Server.class); protected int port; protected ServerSocket acceptSocket; protected Tree hosted; protected final JoinableCollection clients = new JoinableCollection(JoinableCollection.FinishPolicy.Never); public static class Accept { }; protected Thread acceptThread = new Thread<>(); /** * */ public Server() { log.debug("created server"); port = 9009; } /** * @return the port */ @ParamAnnotation public int getPort() { return port; } /** * @param port the port to set */ @ParamAnnotation(flags = ParamFlags.USERREADONLY) public void setPort(int port) { this.port = port; } /** * @return the hosted */ public Tree getHosted() { return hosted; } @AutoAppendAnnotation public void setHosted(Tree hosted) { if (this.hosted != null) { throw new FramsticksException().msg("hosted tree is already set").arg("current", this.hosted); } this.hosted = hosted; acceptThread.setName(hosted.getName() + " acceptor"); clients.setObservableName(hosted.getName() + " clients"); } @Override public void childChangedState(Joinable joinable, JoinableState state) { proceedToState(state); } @Override @ParamAnnotation public String getName() { return hosted != null ? hosted.getName() : "server"; } protected void acceptNext() { if (!isRunning()) { log.debug("server is not in running state, aborting accepting"); return; } acceptThread.dispatch(new RunAt(hosted) { @Override protected void runAt() { try { log.debug("accepting"); final Socket socket = acceptSocket.accept(); assert socket != null; log.debug("accepted socket: {}", socket.getInetAddress().getHostAddress()); hosted.dispatch(new RunAt(this) { @Override protected void runAt() { ClientAtServer client = new ClientAtServer(Server.this, socket); clients.add(client); log.info("client connected: {}", client); } }); } catch (IOException e) { log.log((isRunning() ? Level.ERROR : Level.DEBUG), "failed to accept socket: {}", e); } acceptNext(); } }); } protected void tryBind(int when) { Dispatching.getTimer().schedule(new TimerTask() { @Override public void run() { acceptThread.dispatch(new RunAt(ThrowExceptionHandler.getInstance()) { @Override protected void runAt() { try { acceptSocket.bind(new InetSocketAddress(port)); log.debug("started accepting on port {}", port); acceptNext(); return; } catch (IOException e) { log.warn("failed to accept on port {} (repeating): ", port, e); } tryBind(1000); } }); } }, when); } @Override protected void joinableStart() { Dispatching.use(acceptThread, this); Dispatching.use(hosted, this); Dispatching.use(clients, this); try { acceptSocket = new ServerSocket(); acceptSocket.setReuseAddress(true); } catch (IOException e) { throw new FramsticksException().msg("failed to create server socket").cause(e); } tryBind(0); } @Override protected void joinableInterrupt() { Dispatching.drop(acceptThread, this); Dispatching.drop(hosted, this); Dispatching.drop(clients, this); try { acceptSocket.close(); } catch (IOException e) { log.debug("exception caught during socket closing: ", e); } finishJoinable(); } @Override protected void joinableFinish() { } @Override protected void joinableJoin() throws InterruptedException { Dispatching.join(acceptThread); Dispatching.join(hosted); Dispatching.join(clients); } }