Newer
Older
KeeperJerry_Launcher / LaunchServer / source / response / ServerSocketHandler.java
@KeeperJerry KeeperJerry on 27 Jun 2020 4 KB Рефактор кода
package launchserver.response;

import launcher.LauncherAPI;
import launcher.helper.CommonHelper;
import launcher.helper.LogHelper;
import launcher.helper.VerifyHelper;
import launcher.request.Request.Type;
import launcher.serialize.HInput;
import launcher.serialize.HOutput;
import launchserver.LaunchServer;
import launchserver.response.Response.Factory;

import java.io.IOException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;

public final class ServerSocketHandler implements Runnable, AutoCloseable
{
    private static final ThreadFactory THREAD_FACTORY = r -> CommonHelper.newThread("Network Thread", true, r);
    // Instance
    private final LaunchServer server;
    private final AtomicReference<ServerSocket> serverSocket = new AtomicReference<>();
    private final ExecutorService threadPool = Executors.newCachedThreadPool(THREAD_FACTORY);
    // API
    private final Map<String, Factory> customResponses = new ConcurrentHashMap<>(2);
    private final AtomicLong idCounter = new AtomicLong(0L);
    @LauncherAPI
    public volatile boolean logConnections = Boolean.getBoolean("launcher.logConnections");
    private volatile Listener listener;

    public ServerSocketHandler(LaunchServer server)
    {
        this.server = server;
    }

    @Override
    public void close()
    {
        ServerSocket socket = serverSocket.getAndSet(null);
        if (socket != null)
        {
            LogHelper.info("Closing server socket listener");
            try
            {
                socket.close();
            }
            catch (IOException e)
            {
                LogHelper.error(e);
            }
        }
    }

    @Override
    public void run()
    {
        LogHelper.info("Starting server socket thread");
        try (ServerSocket serverSocket = new ServerSocket())
        {
            if (!this.serverSocket.compareAndSet(null, serverSocket))
            {
                throw new IllegalStateException("Previous socket wasn't closed");
            }

            // Set socket params
            serverSocket.setReuseAddress(true);
            serverSocket.setPerformancePreferences(1, 0, 2);
            //serverSocket.setReceiveBufferSize(0x10000);
            serverSocket.bind(server.config.getSocketAddress());
            LogHelper.info("Server socket thread successfully started");

            // Listen for incoming connections
            while (serverSocket.isBound())
            {
                Socket socket = serverSocket.accept();

                // Invoke pre-connect listener
                long id = idCounter.incrementAndGet();
                if (listener != null && !listener.onConnect(id, socket.getInetAddress()))
                {
                    continue; // Listener didn't accepted this connection
                }

                // Reply in separate thread
                threadPool.execute(new ResponseThread(server, id, socket));
            }
        }
        catch (IOException e)
        {
            // Ignore error after close/rebind
            if (serverSocket.get() != null)
            {
                LogHelper.error(e);
            }
        }
    }

    @LauncherAPI
    public Response newCustomResponse(String name, long id, HInput input, HOutput output)
    {
        Factory factory = VerifyHelper.getMapValue(customResponses, name,
                String.format("Unknown custom response: '%s'", name));
        return factory.newResponse(server, id, input, output);
    }

    @LauncherAPI
    public void registerCustomResponse(String name, Factory factory)
    {
        VerifyHelper.verifyIDName(name);
        VerifyHelper.putIfAbsent(customResponses, name, Objects.requireNonNull(factory, "factory"),
                String.format("Custom response has been already registered: '%s'", name));
    }

    @LauncherAPI
    public void setListener(Listener listener)
    {
        this.listener = listener;
    }

    /*package*/ void onDisconnect(long id, Throwable exc)
    {
        if (listener != null)
        {
            listener.onDisconnect(id, exc);
        }
    }

    /*package*/ boolean onHandshake(long id, Type type)
    {
        return listener == null || listener.onHandshake(id, type);
    }

    public interface Listener
    {
        @LauncherAPI
        boolean onConnect(long id, InetAddress address);

        @LauncherAPI
        void onDisconnect(long id, Throwable exc);

        @LauncherAPI
        boolean onHandshake(long id, Type type);
    }
}