com.kolibrifx.plovercrest.server.internal.protocol.StreamServer Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of plovercrest-server Show documentation
Show all versions of plovercrest-server Show documentation
Plovercrest server library.
The newest version!
/*
* Copyright (c) 2010-2017, KolibriFX AS. Licensed under the Apache License, version 2.0.
*/
package com.kolibrifx.plovercrest.server.internal.protocol;
import java.io.IOException;
import java.lang.Thread.UncaughtExceptionHandler;
import java.net.InetSocketAddress;
import java.net.StandardSocketOptions;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import org.apache.log4j.Logger;
import com.kolibrifx.plovercrest.client.PlovercrestException;
import com.kolibrifx.plovercrest.server.internal.EngineAdapter;
import com.kolibrifx.plovercrest.server.security.AccessControlFilter;
public class StreamServer {
private static final Logger log = Logger.getLogger(StreamServer.class);
private final EngineAdapter adapter;
private int port;
private final Collection connections =
Collections.synchronizedCollection(new LinkedList());
private ServerSocketChannel channel;
private final ScheduledExecutorService executor;
private final AccessControlFilter accessControlFilter;
private volatile boolean isShuttingDown = false;
private volatile boolean stopped = false;
private TimeoutProvider timeoutProvider = null;
public StreamServer(final EngineAdapter engine, final int port, final AccessControlFilter accessControlFilter) {
this.adapter = engine;
this.port = port;
this.accessControlFilter = accessControlFilter;
this.executor = Executors.newSingleThreadScheduledExecutor(new ThreadFactory() {
@Override
public Thread newThread(final Runnable r) {
final Thread t = new Thread(r, "Plovercrest stream server");
t.setUncaughtExceptionHandler(new UncaughtExceptionHandler() {
@Override
public void uncaughtException(final Thread t, final Throwable e) {
log.fatal("Uncaught exception", e);
}
});
return t;
}
});
}
public void start() {
stopped = false;
isShuttingDown = false;
timeoutProvider = new TimeoutProvider();
try {
channel = ServerSocketChannel.open();
channel.socket().bind(new InetSocketAddress(port));
executor.submit(new Runnable() {
@Override
public void run() {
try {
while (!isShuttingDown) {
final SocketChannel ch = channel.accept();
if (isShuttingDown) {
// somewhat ugly hack to stop accepting new connections
try {
ch.close();
} catch (final IOException e) {
log.error("Failed to close incoming connection while shutting down", e);
}
if (log.isInfoEnabled()) {
log.info("Shutting down, exiting accept loop");
}
break;
}
ch.setOption(StandardSocketOptions.TCP_NODELAY, true);
final InetSocketAddress remoteSocketAddress = (InetSocketAddress) ch.getRemoteAddress();
if (log.isInfoEnabled()) {
log.info("New stream client connected: " + remoteSocketAddress);
}
while (!ch.finishConnect()) {
}
final StreamConnection sc =
new StreamConnection(StreamServer.this, adapter, ch, remoteSocketAddress,
accessControlFilter, timeoutProvider);
connections.add(sc);
// TODO: handle isShuttingDown here...?
sc.start();
}
} catch (final ClosedChannelException e) {
// Triggered by stop()
log.info("Server channel closed, exiting normally");
} catch (final IOException e) {
log.error(e.getMessage(), e);
throw new RuntimeException(e);
} finally {
// TODO: await clean shutdown before close?
try {
if (channel.isOpen()) {
channel.close();
}
} catch (final IOException e) {
log.error(e.getMessage(), e);
}
}
}
});
} catch (final IOException e) {
throw new PlovercrestException("Failed to open socket", e);
}
}
private void stop() {
if (stopped) {
return;
}
log.debug("Stopping server");
try {
if (channel.isOpen()) {
channel.close();
}
} catch (final IOException e) {
log.error(e.getMessage(), e);
}
executor.shutdown();
try {
if (!executor.awaitTermination(5, TimeUnit.SECONDS)) {
log.error("Timed out while awaiting termination");
}
} catch (final InterruptedException e) {
log.error(e.getMessage(), e);
}
final ArrayList unsynchronizedConnections = new ArrayList();
synchronized (connections) {
unsynchronizedConnections.addAll(connections);
connections.clear();
}
for (final StreamConnection sc : unsynchronizedConnections) {
sc.doStop(); // redundant?
try {
sc.join(); // do not hold connections lock here
} catch (final InterruptedException e) {
log.error("Interrupted while joining stream connection thread");
Thread.currentThread().interrupt();
break;
}
}
stopped = true;
timeoutProvider.close();
}
void startCleanShutdown() {
isShuttingDown = true;
synchronized (connections) {
if (connections.isEmpty()) {
log.info("No clients connected, shutting down immediately");
stop();
} else {
if (log.isInfoEnabled()) {
log.info(connections.size() + " clients connected, sending shutdown events");
}
for (final StreamConnection sc : connections) {
sc.startCleanShutdown();
}
}
}
}
boolean awaitTermination(final long timeout, final TimeUnit unit) throws InterruptedException {
return executor.awaitTermination(timeout, unit);
}
public boolean shutdown(final long timeout, final TimeUnit unit) throws InterruptedException {
startCleanShutdown();
return executor.awaitTermination(timeout, unit);
}
public boolean awaitTermination() throws InterruptedException {
return executor.awaitTermination(Long.MAX_VALUE, TimeUnit.DAYS);
}
public void shutdownNow() {
stop();
}
/**
* Returns the port on which the server is listening. Normally the same as given in the
* constructor, unless port 0 has been specified.
*/
public int getOpenPort() {
if (channel == null) {
throw new IllegalStateException("The server has not been started.");
}
return channel.socket().getLocalPort();
}
void deregister(final StreamConnection sc) {
synchronized (connections) {
connections.remove(sc);
if (isShuttingDown && connections.isEmpty()) {
stop();
}
}
}
int getOpenClientsCount() {
return connections.size();
}
}