Updated version

This commit is contained in:
nathan 2020-05-08 16:29:59 +02:00
parent f01c565392
commit 1cf4457f77
3 changed files with 1331 additions and 1331 deletions

View File

@ -1,331 +1,331 @@
/* /*
* Copyright 2010 dorkbox, llc * Copyright 2010 dorkbox, llc
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package dorkbox.network; package dorkbox.network;
import java.io.IOException; import java.io.IOException;
import java.net.Inet6Address; import java.net.Inet6Address;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.InterfaceAddress; import java.net.InterfaceAddress;
import java.net.NetworkInterface; import java.net.NetworkInterface;
import java.net.SocketException; import java.net.SocketException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.Enumeration; import java.util.Enumeration;
import java.util.List; import java.util.List;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import org.slf4j.Logger; import org.slf4j.Logger;
import dorkbox.network.pipeline.MagicBytes; import dorkbox.network.pipeline.MagicBytes;
import dorkbox.network.pipeline.discovery.BroadcastResponse; import dorkbox.network.pipeline.discovery.BroadcastResponse;
import dorkbox.network.pipeline.discovery.ClientDiscoverHostHandler; import dorkbox.network.pipeline.discovery.ClientDiscoverHostHandler;
import dorkbox.network.pipeline.discovery.ClientDiscoverHostInitializer; import dorkbox.network.pipeline.discovery.ClientDiscoverHostInitializer;
import dorkbox.util.OS; import dorkbox.util.OS;
import io.netty.bootstrap.Bootstrap; import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled; import io.netty.buffer.Unpooled;
import io.netty.channel.Channel; import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelOption; import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup; import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.oio.OioEventLoopGroup; import io.netty.channel.oio.OioEventLoopGroup;
import io.netty.channel.socket.DatagramPacket; import io.netty.channel.socket.DatagramPacket;
import io.netty.channel.socket.nio.NioDatagramChannel; import io.netty.channel.socket.nio.NioDatagramChannel;
import io.netty.channel.socket.oio.OioDatagramChannel; import io.netty.channel.socket.oio.OioDatagramChannel;
@SuppressWarnings({"unused", "AutoBoxing"}) @SuppressWarnings({"unused", "AutoBoxing"})
public final public final
class Broadcast { class Broadcast {
private static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(Client.class.getSimpleName()); private static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(Client.class.getSimpleName());
/** /**
* Gets the version number. * Gets the version number.
*/ */
public static public static
String getVersion() { String getVersion() {
return "4.0"; return "4.1";
} }
/** /**
* Broadcasts a UDP message on the LAN to discover any running servers. The address of the first server to respond is returned. * Broadcasts a UDP message on the LAN to discover any running servers. The address of the first server to respond is returned.
* <p/> * <p/>
* From KryoNet * From KryoNet
* *
* @param udpPort * @param udpPort
* The UDP port of the server. * The UDP port of the server.
* @param discoverTimeoutMillis * @param discoverTimeoutMillis
* The number of milliseconds to wait for a response. * The number of milliseconds to wait for a response.
* *
* @return the first server found, or null if no server responded. * @return the first server found, or null if no server responded.
*/ */
public static public static
BroadcastResponse discoverHost(int udpPort, int discoverTimeoutMillis) throws IOException { BroadcastResponse discoverHost(int udpPort, int discoverTimeoutMillis) throws IOException {
BroadcastResponse discoverHost = discoverHostAddress(udpPort, discoverTimeoutMillis); BroadcastResponse discoverHost = discoverHostAddress(udpPort, discoverTimeoutMillis);
if (discoverHost != null) { if (discoverHost != null) {
return discoverHost; return discoverHost;
} }
return null; return null;
} }
/** /**
* Broadcasts a UDP message on the LAN to discover any running servers. The address of the first server to respond is returned. * Broadcasts a UDP message on the LAN to discover any running servers. The address of the first server to respond is returned.
* *
* @param udpPort * @param udpPort
* The UDP port of the server. * The UDP port of the server.
* @param discoverTimeoutMillis * @param discoverTimeoutMillis
* The number of milliseconds to wait for a response. * The number of milliseconds to wait for a response.
* *
* @return the first server found, or null if no server responded. * @return the first server found, or null if no server responded.
*/ */
public static public static
BroadcastResponse discoverHostAddress(int udpPort, int discoverTimeoutMillis) throws IOException { BroadcastResponse discoverHostAddress(int udpPort, int discoverTimeoutMillis) throws IOException {
List<BroadcastResponse> servers = discoverHosts0(logger, udpPort, discoverTimeoutMillis, false); List<BroadcastResponse> servers = discoverHosts0(logger, udpPort, discoverTimeoutMillis, false);
if (servers.isEmpty()) { if (servers.isEmpty()) {
return null; return null;
} }
else { else {
return servers.get(0); return servers.get(0);
} }
} }
/** /**
* Broadcasts a UDP message on the LAN to discover all running servers. * Broadcasts a UDP message on the LAN to discover all running servers.
* *
* @param udpPort * @param udpPort
* The UDP port of the server. * The UDP port of the server.
* @param discoverTimeoutMillis * @param discoverTimeoutMillis
* The number of milliseconds to wait for a response. * The number of milliseconds to wait for a response.
* *
* @return the list of found servers (if they responded) * @return the list of found servers (if they responded)
*/ */
public static public static
List<BroadcastResponse> discoverHosts(int udpPort, int discoverTimeoutMillis) throws IOException { List<BroadcastResponse> discoverHosts(int udpPort, int discoverTimeoutMillis) throws IOException {
return discoverHosts0(logger, udpPort, discoverTimeoutMillis, true); return discoverHosts0(logger, udpPort, discoverTimeoutMillis, true);
} }
static static
List<BroadcastResponse> discoverHosts0(Logger logger, int udpPort, int discoverTimeoutMillis, boolean fetchAllServers) throws IOException { List<BroadcastResponse> discoverHosts0(Logger logger, int udpPort, int discoverTimeoutMillis, boolean fetchAllServers) throws IOException {
// fetch a buffer that contains the serialized object. // fetch a buffer that contains the serialized object.
ByteBuf buffer = Unpooled.wrappedBuffer(new byte[]{MagicBytes.broadcastID}); ByteBuf buffer = Unpooled.wrappedBuffer(new byte[]{MagicBytes.broadcastID});
List<BroadcastResponse> servers = new ArrayList<BroadcastResponse>(); List<BroadcastResponse> servers = new ArrayList<BroadcastResponse>();
Enumeration<NetworkInterface> networkInterfaces; Enumeration<NetworkInterface> networkInterfaces;
try { try {
networkInterfaces = NetworkInterface.getNetworkInterfaces(); networkInterfaces = NetworkInterface.getNetworkInterfaces();
} catch (SocketException e) { } catch (SocketException e) {
if (logger != null) { if (logger != null) {
logger.error("Host discovery failed.", e); logger.error("Host discovery failed.", e);
} }
throw new IOException("Host discovery failed. No interfaces found."); throw new IOException("Host discovery failed. No interfaces found.");
} }
scan: scan:
for (NetworkInterface networkInterface : Collections.list(networkInterfaces)) { for (NetworkInterface networkInterface : Collections.list(networkInterfaces)) {
for (InterfaceAddress interfaceAddress : networkInterface.getInterfaceAddresses()) { for (InterfaceAddress interfaceAddress : networkInterface.getInterfaceAddresses()) {
InetAddress address = interfaceAddress.getAddress(); InetAddress address = interfaceAddress.getAddress();
InetAddress broadcast = interfaceAddress.getBroadcast(); InetAddress broadcast = interfaceAddress.getBroadcast();
// don't use IPv6! // don't use IPv6!
if (address instanceof Inet6Address) { if (address instanceof Inet6Address) {
if (logger != null) { if (logger != null) {
if (logger.isInfoEnabled()) { if (logger.isInfoEnabled()) {
logger.info("Not using IPv6 address: {}", address); logger.info("Not using IPv6 address: {}", address);
} }
} }
continue; continue;
} }
try { try {
if (logger != null) { if (logger != null) {
if (logger.isInfoEnabled()) { if (logger.isInfoEnabled()) {
logger.info("Searching for host on [{}:{}]", address.getHostAddress(), udpPort); logger.info("Searching for host on [{}:{}]", address.getHostAddress(), udpPort);
} }
} }
EventLoopGroup group; EventLoopGroup group;
Class<? extends Channel> channelClass; Class<? extends Channel> channelClass;
if (OS.isAndroid()) { if (OS.isAndroid()) {
// android ONLY supports OIO (not NIO) // android ONLY supports OIO (not NIO)
group = new OioEventLoopGroup(1); group = new OioEventLoopGroup(1);
channelClass = OioDatagramChannel.class; channelClass = OioDatagramChannel.class;
} else { } else {
group = new NioEventLoopGroup(1); group = new NioEventLoopGroup(1);
channelClass = NioDatagramChannel.class; channelClass = NioDatagramChannel.class;
} }
Bootstrap udpBootstrap = new Bootstrap().group(group) Bootstrap udpBootstrap = new Bootstrap().group(group)
.channel(channelClass) .channel(channelClass)
.option(ChannelOption.SO_BROADCAST, true) .option(ChannelOption.SO_BROADCAST, true)
.handler(new ClientDiscoverHostInitializer()) .handler(new ClientDiscoverHostInitializer())
.localAddress(new InetSocketAddress(address, .localAddress(new InetSocketAddress(address,
0)); // pick random address. Not listen for broadcast. 0)); // pick random address. Not listen for broadcast.
// we don't care about RECEIVING a broadcast packet, we are only SENDING one. // we don't care about RECEIVING a broadcast packet, we are only SENDING one.
ChannelFuture future; ChannelFuture future;
try { try {
future = udpBootstrap.bind(); future = udpBootstrap.bind();
future.await(); future.await();
} catch (InterruptedException e) { } catch (InterruptedException e) {
if (logger != null) { if (logger != null) {
logger.error("Could not bind to random UDP address on the server.", e.getCause()); logger.error("Could not bind to random UDP address on the server.", e.getCause());
} }
throw new IOException("Could not bind to random UDP address on the server."); throw new IOException("Could not bind to random UDP address on the server.");
} }
if (!future.isSuccess()) { if (!future.isSuccess()) {
if (logger != null) { if (logger != null) {
logger.error("Could not bind to random UDP address on the server.", future.cause()); logger.error("Could not bind to random UDP address on the server.", future.cause());
} }
throw new IOException("Could not bind to random UDP address on the server."); throw new IOException("Could not bind to random UDP address on the server.");
} }
Channel channel1 = future.channel(); Channel channel1 = future.channel();
if (broadcast != null) { if (broadcast != null) {
// try the "defined" broadcast first if we have it (not always!) // try the "defined" broadcast first if we have it (not always!)
channel1.writeAndFlush(new DatagramPacket(buffer, new InetSocketAddress(broadcast, udpPort))); channel1.writeAndFlush(new DatagramPacket(buffer, new InetSocketAddress(broadcast, udpPort)));
// response is received. If the channel is not closed within 5 seconds, move to the next one. // response is received. If the channel is not closed within 5 seconds, move to the next one.
if (!channel1.closeFuture().awaitUninterruptibly(discoverTimeoutMillis)) { if (!channel1.closeFuture().awaitUninterruptibly(discoverTimeoutMillis)) {
if (logger != null) { if (logger != null) {
if (logger.isInfoEnabled()) { if (logger.isInfoEnabled()) {
logger.info("Host discovery timed out."); logger.info("Host discovery timed out.");
} }
} }
} }
else { else {
BroadcastResponse broadcastResponse = channel1.attr(ClientDiscoverHostHandler.STATE).get(); BroadcastResponse broadcastResponse = channel1.attr(ClientDiscoverHostHandler.STATE).get();
servers.add(broadcastResponse); servers.add(broadcastResponse);
} }
// keep going if we want to fetch all servers. Break if we found one. // keep going if we want to fetch all servers. Break if we found one.
if (!(fetchAllServers || servers.isEmpty())) { if (!(fetchAllServers || servers.isEmpty())) {
channel1.close().await(); channel1.close().await();
group.shutdownGracefully().await(); group.shutdownGracefully().await();
break scan; break scan;
} }
} }
// continue with "common" broadcast addresses. // continue with "common" broadcast addresses.
// Java 1.5 doesn't support getting the subnet mask, so try them until we find one. // Java 1.5 doesn't support getting the subnet mask, so try them until we find one.
byte[] ip = address.getAddress(); byte[] ip = address.getAddress();
for (int octect = 3; octect >= 0; octect--) { for (int octect = 3; octect >= 0; octect--) {
ip[octect] = -1; // 255.255.255.0 ip[octect] = -1; // 255.255.255.0
// don't error out on one particular octect // don't error out on one particular octect
try { try {
InetAddress byAddress = InetAddress.getByAddress(ip); InetAddress byAddress = InetAddress.getByAddress(ip);
channel1.writeAndFlush(new DatagramPacket(buffer, new InetSocketAddress(byAddress, udpPort))); channel1.writeAndFlush(new DatagramPacket(buffer, new InetSocketAddress(byAddress, udpPort)));
// response is received. If the channel is not closed within 5 seconds, move to the next one. // response is received. If the channel is not closed within 5 seconds, move to the next one.
if (!channel1.closeFuture().awaitUninterruptibly(discoverTimeoutMillis)) { if (!channel1.closeFuture().awaitUninterruptibly(discoverTimeoutMillis)) {
if (logger != null) { if (logger != null) {
if (logger.isInfoEnabled()) { if (logger.isInfoEnabled()) {
logger.info("Host discovery timed out."); logger.info("Host discovery timed out.");
} }
} }
} }
else { else {
BroadcastResponse broadcastResponse = channel1.attr(ClientDiscoverHostHandler.STATE).get(); BroadcastResponse broadcastResponse = channel1.attr(ClientDiscoverHostHandler.STATE).get();
servers.add(broadcastResponse); servers.add(broadcastResponse);
if (!fetchAllServers) { if (!fetchAllServers) {
break; break;
} }
} }
} catch (Exception ignored) { } catch (Exception ignored) {
} }
} }
channel1.close().sync(); channel1.close().sync();
group.shutdownGracefully(0, discoverTimeoutMillis, TimeUnit.MILLISECONDS); group.shutdownGracefully(0, discoverTimeoutMillis, TimeUnit.MILLISECONDS);
} catch (Exception ignored) { } catch (Exception ignored) {
} }
// keep going if we want to fetch all servers. Break if we found one. // keep going if we want to fetch all servers. Break if we found one.
if (!(fetchAllServers || servers.isEmpty())) { if (!(fetchAllServers || servers.isEmpty())) {
break scan; break scan;
} }
} }
} }
if (logger != null && logger.isInfoEnabled() && !servers.isEmpty()) { if (logger != null && logger.isInfoEnabled() && !servers.isEmpty()) {
StringBuilder stringBuilder = new StringBuilder(256); StringBuilder stringBuilder = new StringBuilder(256);
if (fetchAllServers) { if (fetchAllServers) {
stringBuilder.append("Discovered servers: (") stringBuilder.append("Discovered servers: (")
.append(servers.size()) .append(servers.size())
.append(")"); .append(")");
for (BroadcastResponse server : servers) { for (BroadcastResponse server : servers) {
stringBuilder.append("/n") stringBuilder.append("/n")
.append(server.remoteAddress) .append(server.remoteAddress)
.append(":"); .append(":");
if (server.tcpPort > 0) { if (server.tcpPort > 0) {
stringBuilder.append(server.tcpPort); stringBuilder.append(server.tcpPort);
if (server.udpPort > 0) { if (server.udpPort > 0) {
stringBuilder.append(":"); stringBuilder.append(":");
} }
} }
if (server.udpPort > 0) { if (server.udpPort > 0) {
stringBuilder.append(udpPort); stringBuilder.append(udpPort);
} }
} }
logger.info(stringBuilder.toString()); logger.info(stringBuilder.toString());
} }
else { else {
BroadcastResponse server = servers.get(0); BroadcastResponse server = servers.get(0);
stringBuilder.append(server.remoteAddress) stringBuilder.append(server.remoteAddress)
.append(":"); .append(":");
if (server.tcpPort > 0) { if (server.tcpPort > 0) {
stringBuilder.append(server.tcpPort); stringBuilder.append(server.tcpPort);
if (server.udpPort > 0) { if (server.udpPort > 0) {
stringBuilder.append(":"); stringBuilder.append(":");
} }
} }
if (server.udpPort > 0) { if (server.udpPort > 0) {
stringBuilder.append(udpPort); stringBuilder.append(udpPort);
} }
logger.info("Discovered server [{}]", stringBuilder.toString()); logger.info("Discovered server [{}]", stringBuilder.toString());
} }
} }
return servers; return servers;
} }
private private
Broadcast() { Broadcast() {
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -1,485 +1,485 @@
/* /*
* Copyright 2010 dorkbox, llc * Copyright 2010 dorkbox, llc
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package dorkbox.network; package dorkbox.network;
import static dorkbox.network.pipeline.ConnectionType.LOCAL; import static dorkbox.network.pipeline.ConnectionType.LOCAL;
import java.io.IOException; import java.io.IOException;
import java.net.Socket; import java.net.Socket;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import dorkbox.network.connection.Connection; import dorkbox.network.connection.Connection;
import dorkbox.network.connection.EndPoint; import dorkbox.network.connection.EndPoint;
import dorkbox.network.connection.EndPointServer; import dorkbox.network.connection.EndPointServer;
import dorkbox.network.connection.RegistrationWrapperServer; import dorkbox.network.connection.RegistrationWrapperServer;
import dorkbox.network.connection.connectionType.ConnectionRule; import dorkbox.network.connection.connectionType.ConnectionRule;
import dorkbox.network.connection.registration.local.RegistrationLocalHandlerServer; import dorkbox.network.connection.registration.local.RegistrationLocalHandlerServer;
import dorkbox.network.connection.registration.remote.RegistrationRemoteHandlerServerTCP; import dorkbox.network.connection.registration.remote.RegistrationRemoteHandlerServerTCP;
import dorkbox.network.connection.registration.remote.RegistrationRemoteHandlerServerUDP; import dorkbox.network.connection.registration.remote.RegistrationRemoteHandlerServerUDP;
import dorkbox.network.pipeline.discovery.BroadcastResponse; import dorkbox.network.pipeline.discovery.BroadcastResponse;
import dorkbox.util.OS; import dorkbox.util.OS;
import dorkbox.util.Property; import dorkbox.util.Property;
import dorkbox.util.exceptions.SecurityException; import dorkbox.util.exceptions.SecurityException;
import io.netty.bootstrap.ServerBootstrap; import io.netty.bootstrap.ServerBootstrap;
import io.netty.bootstrap.SessionBootstrap; import io.netty.bootstrap.SessionBootstrap;
import io.netty.buffer.PooledByteBufAllocator; import io.netty.buffer.PooledByteBufAllocator;
import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelOption; import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup; import io.netty.channel.EventLoopGroup;
import io.netty.channel.FixedRecvByteBufAllocator; import io.netty.channel.FixedRecvByteBufAllocator;
import io.netty.channel.WriteBufferWaterMark; import io.netty.channel.WriteBufferWaterMark;
import io.netty.channel.epoll.EpollDatagramChannel; import io.netty.channel.epoll.EpollDatagramChannel;
import io.netty.channel.epoll.EpollServerSocketChannel; import io.netty.channel.epoll.EpollServerSocketChannel;
import io.netty.channel.kqueue.KQueueDatagramChannel; import io.netty.channel.kqueue.KQueueDatagramChannel;
import io.netty.channel.kqueue.KQueueServerSocketChannel; import io.netty.channel.kqueue.KQueueServerSocketChannel;
import io.netty.channel.local.LocalAddress; import io.netty.channel.local.LocalAddress;
import io.netty.channel.local.LocalServerChannel; import io.netty.channel.local.LocalServerChannel;
import io.netty.channel.socket.nio.NioDatagramChannel; import io.netty.channel.socket.nio.NioDatagramChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.oio.OioDatagramChannel; import io.netty.channel.socket.oio.OioDatagramChannel;
import io.netty.channel.socket.oio.OioServerSocketChannel; import io.netty.channel.socket.oio.OioServerSocketChannel;
import io.netty.handler.ipfilter.IpFilterRule; import io.netty.handler.ipfilter.IpFilterRule;
import io.netty.handler.ipfilter.IpFilterRuleType; import io.netty.handler.ipfilter.IpFilterRuleType;
import io.netty.handler.ipfilter.IpSubnetFilterRule; import io.netty.handler.ipfilter.IpSubnetFilterRule;
import io.netty.util.NetUtil; import io.netty.util.NetUtil;
/** /**
* The server can only be accessed in an ASYNC manner. This means that the server can only be used in RESPONSE to events. If you access the * The server can only be accessed in an ASYNC manner. This means that the server can only be used in RESPONSE to events. If you access the
* server OUTSIDE of events, you will get inaccurate information from the server (such as getConnections()) * server OUTSIDE of events, you will get inaccurate information from the server (such as getConnections())
* <p/> * <p/>
* To put it bluntly, ONLY have the server do work inside of a listener! * To put it bluntly, ONLY have the server do work inside of a listener!
*/ */
public public
class Server<C extends Connection> extends EndPointServer { class Server<C extends Connection> extends EndPointServer {
/** /**
* Rule that will always allow LOCALHOST to connect to the server. This is not added by default * Rule that will always allow LOCALHOST to connect to the server. This is not added by default
*/ */
public static final IpFilterRule permitLocalHostRule = new IpSubnetFilterRule(NetUtil.LOCALHOST, 32, IpFilterRuleType.ACCEPT); public static final IpFilterRule permitLocalHostRule = new IpSubnetFilterRule(NetUtil.LOCALHOST, 32, IpFilterRuleType.ACCEPT);
/** /**
* Gets the version number. * Gets the version number.
*/ */
public static public static
String getVersion() { String getVersion() {
return "4.0"; return "4.1";
} }
/** /**
* The maximum queue length for incoming connection indications (a request to connect). If a connection indication arrives when the * The maximum queue length for incoming connection indications (a request to connect). If a connection indication arrives when the
* queue is full, the connection is refused. * queue is full, the connection is refused.
*/ */
@Property @Property
public static int backlogConnectionCount = 50; public static int backlogConnectionCount = 50;
private final ServerBootstrap localBootstrap; private final ServerBootstrap localBootstrap;
private final ServerBootstrap tcpBootstrap; private final ServerBootstrap tcpBootstrap;
private final SessionBootstrap udpBootstrap; private final SessionBootstrap udpBootstrap;
private final int tcpPort; private final int tcpPort;
private final int udpPort; private final int udpPort;
private final String localChannelName; private final String localChannelName;
private final String hostName; private final String hostName;
private volatile boolean isRunning = false; private volatile boolean isRunning = false;
/** /**
* Starts a LOCAL <b>only</b> server, with the default serialization scheme. * Starts a LOCAL <b>only</b> server, with the default serialization scheme.
*/ */
public public
Server() throws SecurityException { Server() throws SecurityException {
this(Configuration.localOnly()); this(Configuration.localOnly());
} }
/** /**
* Convenience method to starts a server with the specified Connection Options * Convenience method to starts a server with the specified Connection Options
*/ */
@SuppressWarnings("AutoBoxing") @SuppressWarnings("AutoBoxing")
public public
Server(Configuration config) throws SecurityException { Server(Configuration config) throws SecurityException {
// watch-out for serialization... it can be NULL incoming. The EndPoint (superclass) sets it, if null, so // watch-out for serialization... it can be NULL incoming. The EndPoint (superclass) sets it, if null, so
// you have to make sure to use this.serialization // you have to make sure to use this.serialization
super(config); super(config);
tcpPort = config.tcpPort; tcpPort = config.tcpPort;
udpPort = config.udpPort; udpPort = config.udpPort;
localChannelName = config.localChannelName; localChannelName = config.localChannelName;
if (config.host == null) { if (config.host == null) {
// we set this to "0.0.0.0" so that it is clear that we are trying to bind to that address. // we set this to "0.0.0.0" so that it is clear that we are trying to bind to that address.
hostName = "0.0.0.0"; hostName = "0.0.0.0";
// make it clear that this is what we do (configuration wise) so that variable examination is consistent // make it clear that this is what we do (configuration wise) so that variable examination is consistent
config.host = hostName; config.host = hostName;
} }
else { else {
hostName = config.host; hostName = config.host;
} }
if (localChannelName != null) { if (localChannelName != null) {
localBootstrap = new ServerBootstrap(); localBootstrap = new ServerBootstrap();
} }
else { else {
localBootstrap = null; localBootstrap = null;
} }
if (tcpPort > 0) { if (tcpPort > 0) {
tcpBootstrap = new ServerBootstrap(); tcpBootstrap = new ServerBootstrap();
} }
else { else {
tcpBootstrap = null; tcpBootstrap = null;
} }
if (udpPort > 0) { if (udpPort > 0) {
// This is what allows us to have UDP behave "similar" to TCP, in that a session is established based on the port/ip of the // This is what allows us to have UDP behave "similar" to TCP, in that a session is established based on the port/ip of the
// remote connection. This allows us to reuse channels and have "state" for a UDP connection that normally wouldn't exist. // remote connection. This allows us to reuse channels and have "state" for a UDP connection that normally wouldn't exist.
// Additionally, this is what responds to discovery broadcast packets // Additionally, this is what responds to discovery broadcast packets
udpBootstrap = new SessionBootstrap(tcpPort, udpPort); udpBootstrap = new SessionBootstrap(tcpPort, udpPort);
} }
else { else {
udpBootstrap = null; udpBootstrap = null;
} }
String threadName = Server.class.getSimpleName(); String threadName = Server.class.getSimpleName();
// always use local channels on the server. // always use local channels on the server.
if (localBootstrap != null) { if (localBootstrap != null) {
localBootstrap.group(newEventLoop(LOCAL, 1, threadName + "-JVM-BOSS"), localBootstrap.group(newEventLoop(LOCAL, 1, threadName + "-JVM-BOSS"),
newEventLoop(LOCAL, 1, threadName )) newEventLoop(LOCAL, 1, threadName ))
.channel(LocalServerChannel.class) .channel(LocalServerChannel.class)
.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT) .option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)
.option(ChannelOption.WRITE_BUFFER_WATER_MARK, new WriteBufferWaterMark(WRITE_BUFF_LOW, WRITE_BUFF_HIGH)) .option(ChannelOption.WRITE_BUFFER_WATER_MARK, new WriteBufferWaterMark(WRITE_BUFF_LOW, WRITE_BUFF_HIGH))
.localAddress(new LocalAddress(localChannelName)) .localAddress(new LocalAddress(localChannelName))
.childHandler(new RegistrationLocalHandlerServer(threadName, (RegistrationWrapperServer) registrationWrapper)); .childHandler(new RegistrationLocalHandlerServer(threadName, (RegistrationWrapperServer) registrationWrapper));
} }
EventLoopGroup workerEventLoop = null; EventLoopGroup workerEventLoop = null;
if (tcpBootstrap != null || udpBootstrap != null) { if (tcpBootstrap != null || udpBootstrap != null) {
workerEventLoop = newEventLoop(config.workerThreadPoolSize, threadName); workerEventLoop = newEventLoop(config.workerThreadPoolSize, threadName);
} }
if (tcpBootstrap != null) { if (tcpBootstrap != null) {
if (OS.isAndroid()) { if (OS.isAndroid()) {
// android ONLY supports OIO (not NIO) // android ONLY supports OIO (not NIO)
tcpBootstrap.channel(OioServerSocketChannel.class); tcpBootstrap.channel(OioServerSocketChannel.class);
} }
else if (OS.isLinux() && NativeLibrary.isAvailable()) { else if (OS.isLinux() && NativeLibrary.isAvailable()) {
// epoll network stack is MUCH faster (but only on linux) // epoll network stack is MUCH faster (but only on linux)
tcpBootstrap.channel(EpollServerSocketChannel.class); tcpBootstrap.channel(EpollServerSocketChannel.class);
} }
else if (OS.isMacOsX() && NativeLibrary.isAvailable()) { else if (OS.isMacOsX() && NativeLibrary.isAvailable()) {
// KQueue network stack is MUCH faster (but only on macosx) // KQueue network stack is MUCH faster (but only on macosx)
tcpBootstrap.channel(KQueueServerSocketChannel.class); tcpBootstrap.channel(KQueueServerSocketChannel.class);
} }
else { else {
tcpBootstrap.channel(NioServerSocketChannel.class); tcpBootstrap.channel(NioServerSocketChannel.class);
} }
// TODO: If we use netty for an HTTP server, // TODO: If we use netty for an HTTP server,
// Beside the usual ChannelOptions the Native Transport allows to enable TCP_CORK which may come in handy if you implement a HTTP Server. // Beside the usual ChannelOptions the Native Transport allows to enable TCP_CORK which may come in handy if you implement a HTTP Server.
tcpBootstrap.group(newEventLoop(1, threadName + "-TCP-BOSS"), tcpBootstrap.group(newEventLoop(1, threadName + "-TCP-BOSS"),
newEventLoop(1, threadName + "-TCP-REGISTRATION")) newEventLoop(1, threadName + "-TCP-REGISTRATION"))
.option(ChannelOption.SO_BACKLOG, backlogConnectionCount) .option(ChannelOption.SO_BACKLOG, backlogConnectionCount)
.option(ChannelOption.SO_REUSEADDR, true) .option(ChannelOption.SO_REUSEADDR, true)
.option(ChannelOption.WRITE_BUFFER_WATER_MARK, new WriteBufferWaterMark(WRITE_BUFF_LOW, WRITE_BUFF_HIGH)) .option(ChannelOption.WRITE_BUFFER_WATER_MARK, new WriteBufferWaterMark(WRITE_BUFF_LOW, WRITE_BUFF_HIGH))
.childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT) .childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)
.childOption(ChannelOption.SO_KEEPALIVE, true) .childOption(ChannelOption.SO_KEEPALIVE, true)
.childHandler(new RegistrationRemoteHandlerServerTCP(threadName, (RegistrationWrapperServer) registrationWrapper, workerEventLoop)); .childHandler(new RegistrationRemoteHandlerServerTCP(threadName, (RegistrationWrapperServer) registrationWrapper, workerEventLoop));
// have to check options.host for "0.0.0.0". we don't bind to "0.0.0.0", we bind to "null" to get the "any" address! // have to check options.host for "0.0.0.0". we don't bind to "0.0.0.0", we bind to "null" to get the "any" address!
if (hostName.equals("0.0.0.0")) { if (hostName.equals("0.0.0.0")) {
tcpBootstrap.localAddress(tcpPort); tcpBootstrap.localAddress(tcpPort);
} }
else { else {
tcpBootstrap.localAddress(hostName, tcpPort); tcpBootstrap.localAddress(hostName, tcpPort);
} }
// android screws up on this!! // android screws up on this!!
tcpBootstrap.option(ChannelOption.TCP_NODELAY, !OS.isAndroid()) tcpBootstrap.option(ChannelOption.TCP_NODELAY, !OS.isAndroid())
.childOption(ChannelOption.TCP_NODELAY, !OS.isAndroid()); .childOption(ChannelOption.TCP_NODELAY, !OS.isAndroid());
} }
if (udpBootstrap != null) { if (udpBootstrap != null) {
if (OS.isAndroid()) { if (OS.isAndroid()) {
// android ONLY supports OIO (not NIO) // android ONLY supports OIO (not NIO)
udpBootstrap.channel(OioDatagramChannel.class); udpBootstrap.channel(OioDatagramChannel.class);
} }
else if (OS.isLinux() && NativeLibrary.isAvailable()) { else if (OS.isLinux() && NativeLibrary.isAvailable()) {
// epoll network stack is MUCH faster (but only on linux) // epoll network stack is MUCH faster (but only on linux)
udpBootstrap.channel(EpollDatagramChannel.class); udpBootstrap.channel(EpollDatagramChannel.class);
} }
else if (OS.isMacOsX() && NativeLibrary.isAvailable()) { else if (OS.isMacOsX() && NativeLibrary.isAvailable()) {
// KQueue network stack is MUCH faster (but only on macosx) // KQueue network stack is MUCH faster (but only on macosx)
udpBootstrap.channel(KQueueDatagramChannel.class); udpBootstrap.channel(KQueueDatagramChannel.class);
} }
else { else {
// windows and linux/mac that are incompatible with the native implementations // windows and linux/mac that are incompatible with the native implementations
udpBootstrap.channel(NioDatagramChannel.class); udpBootstrap.channel(NioDatagramChannel.class);
} }
// Netty4 has a default of 2048 bytes as upper limit for datagram packets, we want this to be whatever we specify // Netty4 has a default of 2048 bytes as upper limit for datagram packets, we want this to be whatever we specify
FixedRecvByteBufAllocator recvByteBufAllocator = new FixedRecvByteBufAllocator(EndPoint.udpMaxSize); FixedRecvByteBufAllocator recvByteBufAllocator = new FixedRecvByteBufAllocator(EndPoint.udpMaxSize);
udpBootstrap.group(newEventLoop(1, threadName + "-UDP-BOSS"), udpBootstrap.group(newEventLoop(1, threadName + "-UDP-BOSS"),
newEventLoop(1, threadName + "-UDP-REGISTRATION")) newEventLoop(1, threadName + "-UDP-REGISTRATION"))
.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT) .option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)
.option(ChannelOption.RCVBUF_ALLOCATOR, recvByteBufAllocator) .option(ChannelOption.RCVBUF_ALLOCATOR, recvByteBufAllocator)
.option(ChannelOption.WRITE_BUFFER_WATER_MARK, new WriteBufferWaterMark(WRITE_BUFF_LOW, WRITE_BUFF_HIGH)) .option(ChannelOption.WRITE_BUFFER_WATER_MARK, new WriteBufferWaterMark(WRITE_BUFF_LOW, WRITE_BUFF_HIGH))
// a non-root user can't receive a broadcast packet on *nix if the socket is bound on non-wildcard address. // a non-root user can't receive a broadcast packet on *nix if the socket is bound on non-wildcard address.
// TODO: move broadcast to it's own handler, and have UDP server be able to be bound to a specific IP // TODO: move broadcast to it's own handler, and have UDP server be able to be bound to a specific IP
// OF NOTE: At the end in my case I decided to bind to .255 broadcast address on Linux systems. (to receive broadcast packets) // OF NOTE: At the end in my case I decided to bind to .255 broadcast address on Linux systems. (to receive broadcast packets)
.localAddress(udpPort) // if you bind to a specific interface, Linux will be unable to receive broadcast packets! see: http://developerweb.net/viewtopic.php?id=5722 .localAddress(udpPort) // if you bind to a specific interface, Linux will be unable to receive broadcast packets! see: http://developerweb.net/viewtopic.php?id=5722
.childHandler(new RegistrationRemoteHandlerServerUDP(threadName, (RegistrationWrapperServer) registrationWrapper, workerEventLoop)); .childHandler(new RegistrationRemoteHandlerServerUDP(threadName, (RegistrationWrapperServer) registrationWrapper, workerEventLoop));
// // have to check options.host for null. we don't bind to 0.0.0.0, we bind to "null" to get the "any" address! // // have to check options.host for null. we don't bind to 0.0.0.0, we bind to "null" to get the "any" address!
// if (hostName.equals("0.0.0.0")) { // if (hostName.equals("0.0.0.0")) {
// udpBootstrap.localAddress(hostName, tcpPort); // udpBootstrap.localAddress(hostName, tcpPort);
// } // }
// else { // else {
// udpBootstrap.localAddress(udpPort); // udpBootstrap.localAddress(udpPort);
// } // }
// Enable to READ from MULTICAST data (ie, 192.168.1.0) // Enable to READ from MULTICAST data (ie, 192.168.1.0)
// in order to WRITE: write as normal, just make sure it ends in .255 // in order to WRITE: write as normal, just make sure it ends in .255
// in order to LISTEN: // in order to LISTEN:
// InetAddress group = InetAddress.getByName("203.0.113.0"); // InetAddress group = InetAddress.getByName("203.0.113.0");
// socket.joinGroup(group); // socket.joinGroup(group);
// THEN once done // THEN once done
// socket.leaveGroup(group), close the socket // socket.leaveGroup(group), close the socket
// Enable to WRITE to MULTICAST data (ie, 192.168.1.0) // Enable to WRITE to MULTICAST data (ie, 192.168.1.0)
udpBootstrap.option(ChannelOption.SO_BROADCAST, false) udpBootstrap.option(ChannelOption.SO_BROADCAST, false)
.option(ChannelOption.SO_SNDBUF, udpMaxSize); .option(ChannelOption.SO_SNDBUF, udpMaxSize);
} }
} }
/** /**
* Binds the server to the configured, underlying protocols. * Binds the server to the configured, underlying protocols.
* <p/> * <p/>
* This method will also BLOCK until the stop method is called, and if you want to continue running code after this method invocation, * This method will also BLOCK until the stop method is called, and if you want to continue running code after this method invocation,
* bind should be called in a separate, non-daemon thread. * bind should be called in a separate, non-daemon thread.
*/ */
public public
void bind() { void bind() {
bind(true); bind(true);
} }
/** /**
* Binds the server to the configured, underlying protocols. * Binds the server to the configured, underlying protocols.
* <p/> * <p/>
* This is a more advanced method, and you should consider calling <code>bind()</code> instead. * This is a more advanced method, and you should consider calling <code>bind()</code> instead.
* *
* @param blockUntilTerminate * @param blockUntilTerminate
* will BLOCK until the server stop method is called, and if you want to continue running code after this method * will BLOCK until the server stop method is called, and if you want to continue running code after this method
* invocation, bind should be called in a separate, non-daemon thread - or with false as the parameter. * invocation, bind should be called in a separate, non-daemon thread - or with false as the parameter.
*/ */
@SuppressWarnings("AutoBoxing") @SuppressWarnings("AutoBoxing")
public public
void bind(boolean blockUntilTerminate) { void bind(boolean blockUntilTerminate) {
// make sure we are not trying to connect during a close or stop event. // make sure we are not trying to connect during a close or stop event.
// This will wait until we have finished starting up/shutting down. // This will wait until we have finished starting up/shutting down.
synchronized (shutdownInProgress) { synchronized (shutdownInProgress) {
} }
// The bootstraps will be accessed ONE AT A TIME, in this order! // The bootstraps will be accessed ONE AT A TIME, in this order!
ChannelFuture future; ChannelFuture future;
// LOCAL // LOCAL
if (localBootstrap != null) { if (localBootstrap != null) {
try { try {
future = localBootstrap.bind(); future = localBootstrap.bind();
future.await(); future.await();
} catch (InterruptedException e) { } catch (InterruptedException e) {
throw new IllegalArgumentException("Could not bind to LOCAL address '" + localChannelName + "' on the server.", e); throw new IllegalArgumentException("Could not bind to LOCAL address '" + localChannelName + "' on the server.", e);
} }
if (!future.isSuccess()) { if (!future.isSuccess()) {
throw new IllegalArgumentException("Could not bind to LOCAL address '" + localChannelName + "' on the server.", future.cause()); throw new IllegalArgumentException("Could not bind to LOCAL address '" + localChannelName + "' on the server.", future.cause());
} }
logger.info("Listening on LOCAL address: [{}]", localChannelName); logger.info("Listening on LOCAL address: [{}]", localChannelName);
manageForShutdown(future); manageForShutdown(future);
} }
// TCP // TCP
if (tcpBootstrap != null) { if (tcpBootstrap != null) {
// Wait until the connection attempt succeeds or fails. // Wait until the connection attempt succeeds or fails.
try { try {
future = tcpBootstrap.bind(); future = tcpBootstrap.bind();
future.await(); future.await();
} catch (Exception e) { } catch (Exception e) {
stop(); stop();
throw new IllegalArgumentException("Could not bind to address " + hostName + " TCP port " + tcpPort + " on the server.", e); throw new IllegalArgumentException("Could not bind to address " + hostName + " TCP port " + tcpPort + " on the server.", e);
} }
if (!future.isSuccess()) { if (!future.isSuccess()) {
stop(); stop();
throw new IllegalArgumentException("Could not bind to address " + hostName + " TCP port " + tcpPort + " on the server.", future.cause()); throw new IllegalArgumentException("Could not bind to address " + hostName + " TCP port " + tcpPort + " on the server.", future.cause());
} }
logger.info("TCP server listen address [{}:{}]", hostName, tcpPort); logger.info("TCP server listen address [{}:{}]", hostName, tcpPort);
manageForShutdown(future); manageForShutdown(future);
} }
// UDP // UDP
if (udpBootstrap != null) { if (udpBootstrap != null) {
// Wait until the connection attempt succeeds or fails. // Wait until the connection attempt succeeds or fails.
try { try {
future = udpBootstrap.bind(); future = udpBootstrap.bind();
future.await(); future.await();
} catch (Exception e) { } catch (Exception e) {
throw new IllegalArgumentException("Could not bind to address " + hostName + " UDP port " + udpPort + " on the server.", e); throw new IllegalArgumentException("Could not bind to address " + hostName + " UDP port " + udpPort + " on the server.", e);
} }
if (!future.isSuccess()) { if (!future.isSuccess()) {
throw new IllegalArgumentException("Could not bind to address " + hostName + " UDP port " + udpPort + " on the server.", throw new IllegalArgumentException("Could not bind to address " + hostName + " UDP port " + udpPort + " on the server.",
future.cause()); future.cause());
} }
logger.info("UDP server listen address [{}:{}]", hostName, udpPort); logger.info("UDP server listen address [{}:{}]", hostName, udpPort);
manageForShutdown(future); manageForShutdown(future);
} }
isRunning = true; isRunning = true;
// we now BLOCK until the stop method is called. // we now BLOCK until the stop method is called.
// if we want to continue running code in the server, bind should be called in a separate, non-daemon thread. // if we want to continue running code in the server, bind should be called in a separate, non-daemon thread.
if (blockUntilTerminate) { if (blockUntilTerminate) {
waitForShutdown(); waitForShutdown();
} }
} }
/** /**
* Adds an IP+subnet rule that defines if that IP+subnet is allowed/denied connectivity to this server. * Adds an IP+subnet rule that defines if that IP+subnet is allowed/denied connectivity to this server.
* <p> * <p>
* If there are any IP+subnet added to this list - then ONLY those are permitted (all else are denied) * If there are any IP+subnet added to this list - then ONLY those are permitted (all else are denied)
* <p> * <p>
* If there is nothing added to this list - then ALL are permitted * If there is nothing added to this list - then ALL are permitted
*/ */
public public
void addIpFilter(IpFilterRule... rules) { void addIpFilter(IpFilterRule... rules) {
ipFilterRules.addAll(Arrays.asList(rules)); ipFilterRules.addAll(Arrays.asList(rules));
} }
/** /**
* Adds an IP+subnet rule that defines what type of connection this IP+subnet should have. * Adds an IP+subnet rule that defines what type of connection this IP+subnet should have.
* - NOTHING : Nothing happens to the in/out bytes * - NOTHING : Nothing happens to the in/out bytes
* - COMPRESS: The in/out bytes are compressed with LZ4-fast * - COMPRESS: The in/out bytes are compressed with LZ4-fast
* - COMPRESS_AND_ENCRYPT: The in/out bytes are compressed (LZ4-fast) THEN encrypted (AES-256-GCM) * - COMPRESS_AND_ENCRYPT: The in/out bytes are compressed (LZ4-fast) THEN encrypted (AES-256-GCM)
* *
* If no rules are defined, then for LOOPBACK, it will always be `COMPRESS` and for everything else it will always be `COMPRESS_AND_ENCRYPT`. * If no rules are defined, then for LOOPBACK, it will always be `COMPRESS` and for everything else it will always be `COMPRESS_AND_ENCRYPT`.
* *
* If rules are defined, then everything by default is `COMPRESS_AND_ENCRYPT`. * If rules are defined, then everything by default is `COMPRESS_AND_ENCRYPT`.
* *
* The compression algorithm is LZ4-fast, so there is a small performance impact for a very large gain * The compression algorithm is LZ4-fast, so there is a small performance impact for a very large gain
* Compress : 6.210 micros/op; 629.0 MB/s (output: 55.4%) * Compress : 6.210 micros/op; 629.0 MB/s (output: 55.4%)
* Uncompress : 0.641 micros/op; 6097.9 MB/s * Uncompress : 0.641 micros/op; 6097.9 MB/s
*/ */
public public
void addConnectionTypeFilter(ConnectionRule... rules) { void addConnectionTypeFilter(ConnectionRule... rules) {
connectionRules.addAll(Arrays.asList(rules)); connectionRules.addAll(Arrays.asList(rules));
} }
// called when we are stopped/shut down // called when we are stopped/shut down
@Override @Override
protected protected
void stopExtraActions() { void stopExtraActions() {
isRunning = false; isRunning = false;
// now WAIT until bind has released the socket // now WAIT until bind has released the socket
// wait a max of 10 tries // wait a max of 10 tries
int tries = 10; int tries = 10;
while (tries-- > 0 && isRunning(this.config)) { while (tries-- > 0 && isRunning(this.config)) {
logger.warn("Server has requested shutdown, but the socket is still bound. Waiting {} more times", tries); logger.warn("Server has requested shutdown, but the socket is still bound. Waiting {} more times", tries);
try { try {
Thread.sleep(2000); Thread.sleep(2000);
} catch (InterruptedException ignored) { } catch (InterruptedException ignored) {
} }
} }
} }
/** /**
* @return true if this server has successfully bound to an IP address and is running * @return true if this server has successfully bound to an IP address and is running
*/ */
public public
boolean isRunning() { boolean isRunning() {
return isRunning; return isRunning;
} }
/** /**
* Checks to see if a server (using the specified configuration) is running. This will check across JVMs by checking the * Checks to see if a server (using the specified configuration) is running. This will check across JVMs by checking the
* network socket directly, and assumes that if the port is in use and answers, then the server is "running". This does not try to * network socket directly, and assumes that if the port is in use and answers, then the server is "running". This does not try to
* authenticate or validate the connection. * authenticate or validate the connection.
* <p> * <p>
* This does not check local-channels (which are intra-JVM only). Uses `Broadcast` to check for UDP servers * This does not check local-channels (which are intra-JVM only). Uses `Broadcast` to check for UDP servers
* </p> * </p>
* *
* @return true if the configuration matches and can connect (but not verify) to the TCP control socket. * @return true if the configuration matches and can connect (but not verify) to the TCP control socket.
*/ */
public static public static
boolean isRunning(Configuration config) { boolean isRunning(Configuration config) {
String host = config.host; String host = config.host;
// for us, we want a "null" host to connect to the "any" interface. // for us, we want a "null" host to connect to the "any" interface.
if (host == null) { if (host == null) {
host = "0.0.0.0"; host = "0.0.0.0";
} }
if (config.tcpPort > 0) { if (config.tcpPort > 0) {
Socket sock = null; Socket sock = null;
// since we check the socket, if we cannot connect to a socket, then we're done. // since we check the socket, if we cannot connect to a socket, then we're done.
try { try {
sock = new Socket(host, config.tcpPort); sock = new Socket(host, config.tcpPort);
// if we can connect to the socket, it means that we are already running. // if we can connect to the socket, it means that we are already running.
return sock.isConnected(); return sock.isConnected();
} catch (Exception ignored) { } catch (Exception ignored) {
if (sock != null) { if (sock != null) {
try { try {
sock.close(); sock.close();
} catch (IOException ignored2) { } catch (IOException ignored2) {
} }
} }
} }
} }
// use Broadcast to see if there is a UDP server connected // use Broadcast to see if there is a UDP server connected
if (config.udpPort > 0) { if (config.udpPort > 0) {
List<BroadcastResponse> broadcastResponses = null; List<BroadcastResponse> broadcastResponses = null;
try { try {
broadcastResponses = Broadcast.discoverHosts0(null, config.udpPort, 500, true); broadcastResponses = Broadcast.discoverHosts0(null, config.udpPort, 500, true);
return !broadcastResponses.isEmpty(); return !broadcastResponses.isEmpty();
} catch (IOException ignored) { } catch (IOException ignored) {
} }
} }
return false; return false;
} }
} }