Removed a decent amount of unnecessary generics. Fixed a few bugs wrt
removing connection managers/listeners. Moved RMI message logic to clean up how it worked.
This commit is contained in:
parent
1d2008beef
commit
4cb9d8ad59
@ -32,7 +32,6 @@ import dorkbox.network.rmi.RemoteObjectCallback;
|
|||||||
import dorkbox.network.rmi.TimeoutException;
|
import dorkbox.network.rmi.TimeoutException;
|
||||||
import dorkbox.util.NamedThreadFactory;
|
import dorkbox.util.NamedThreadFactory;
|
||||||
import dorkbox.util.OS;
|
import dorkbox.util.OS;
|
||||||
import dorkbox.util.exceptions.InitializationException;
|
|
||||||
import dorkbox.util.exceptions.SecurityException;
|
import dorkbox.util.exceptions.SecurityException;
|
||||||
import io.netty.bootstrap.Bootstrap;
|
import io.netty.bootstrap.Bootstrap;
|
||||||
import io.netty.buffer.PooledByteBufAllocator;
|
import io.netty.buffer.PooledByteBufAllocator;
|
||||||
@ -61,7 +60,7 @@ import io.netty.util.internal.PlatformDependent;
|
|||||||
*/
|
*/
|
||||||
@SuppressWarnings({"unused", "WeakerAccess"})
|
@SuppressWarnings({"unused", "WeakerAccess"})
|
||||||
public
|
public
|
||||||
class Client<C extends Connection> extends EndPointClient<C> implements Connection {
|
class Client<C extends Connection> extends EndPointClient implements Connection {
|
||||||
/**
|
/**
|
||||||
* Gets the version number.
|
* Gets the version number.
|
||||||
*/
|
*/
|
||||||
@ -78,7 +77,7 @@ class Client<C extends Connection> extends EndPointClient<C> implements Connecti
|
|||||||
* Starts a LOCAL <b>only</b> client, with the default local channel name and serialization scheme
|
* Starts a LOCAL <b>only</b> client, with the default local channel name and serialization scheme
|
||||||
*/
|
*/
|
||||||
public
|
public
|
||||||
Client() throws InitializationException, SecurityException, IOException {
|
Client() throws SecurityException {
|
||||||
this(Configuration.localOnly());
|
this(Configuration.localOnly());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -86,8 +85,7 @@ class Client<C extends Connection> extends EndPointClient<C> implements Connecti
|
|||||||
* Starts a TCP & UDP client (or a LOCAL client), with the specified serialization scheme
|
* Starts a TCP & UDP client (or a LOCAL client), with the specified serialization scheme
|
||||||
*/
|
*/
|
||||||
public
|
public
|
||||||
Client(String host, int tcpPort, int udpPort, String localChannelName)
|
Client(String host, int tcpPort, int udpPort, String localChannelName) throws SecurityException {
|
||||||
throws InitializationException, SecurityException, IOException {
|
|
||||||
this(new Configuration(host, tcpPort, udpPort, localChannelName));
|
this(new Configuration(host, tcpPort, udpPort, localChannelName));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -96,7 +94,7 @@ class Client<C extends Connection> extends EndPointClient<C> implements Connecti
|
|||||||
*/
|
*/
|
||||||
@SuppressWarnings("AutoBoxing")
|
@SuppressWarnings("AutoBoxing")
|
||||||
public
|
public
|
||||||
Client(final Configuration config) throws InitializationException, SecurityException, IOException {
|
Client(final Configuration config) throws SecurityException {
|
||||||
super(config);
|
super(config);
|
||||||
|
|
||||||
String threadName = Client.class.getSimpleName();
|
String threadName = Client.class.getSimpleName();
|
||||||
@ -144,7 +142,7 @@ class Client<C extends Connection> extends EndPointClient<C> implements Connecti
|
|||||||
localBootstrap.group(localBoss)
|
localBootstrap.group(localBoss)
|
||||||
.channel(LocalChannel.class)
|
.channel(LocalChannel.class)
|
||||||
.remoteAddress(new LocalAddress(config.localChannelName))
|
.remoteAddress(new LocalAddress(config.localChannelName))
|
||||||
.handler(new RegistrationLocalHandlerClient<C>(threadName, registrationWrapper));
|
.handler(new RegistrationLocalHandlerClient(threadName, registrationWrapper));
|
||||||
|
|
||||||
manageForShutdown(localBoss);
|
manageForShutdown(localBoss);
|
||||||
}
|
}
|
||||||
@ -181,9 +179,9 @@ class Client<C extends Connection> extends EndPointClient<C> implements Connecti
|
|||||||
.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))
|
||||||
.remoteAddress(config.host, config.tcpPort)
|
.remoteAddress(config.host, config.tcpPort)
|
||||||
.handler(new RegistrationRemoteHandlerClientTCP<C>(threadName,
|
.handler(new RegistrationRemoteHandlerClientTCP(threadName,
|
||||||
registrationWrapper,
|
registrationWrapper,
|
||||||
serializationManager));
|
serializationManager));
|
||||||
|
|
||||||
// android screws up on this!!
|
// android screws up on this!!
|
||||||
tcpBootstrap.option(ChannelOption.TCP_NODELAY, !isAndroid)
|
tcpBootstrap.option(ChannelOption.TCP_NODELAY, !isAndroid)
|
||||||
@ -217,9 +215,9 @@ class Client<C extends Connection> extends EndPointClient<C> implements Connecti
|
|||||||
.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 InetSocketAddress(0)) // bind to wildcard
|
.localAddress(new InetSocketAddress(0)) // bind to wildcard
|
||||||
.remoteAddress(new InetSocketAddress(config.host, config.udpPort))
|
.remoteAddress(new InetSocketAddress(config.host, config.udpPort))
|
||||||
.handler(new RegistrationRemoteHandlerClientUDP<C>(threadName,
|
.handler(new RegistrationRemoteHandlerClientUDP(threadName,
|
||||||
registrationWrapper,
|
registrationWrapper,
|
||||||
serializationManager));
|
serializationManager));
|
||||||
|
|
||||||
// Enable to READ and WRITE MULTICAST data (ie, 192.168.1.0)
|
// Enable to READ and WRITE 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
|
||||||
@ -419,9 +417,8 @@ class Client<C extends Connection> extends EndPointClient<C> implements Connecti
|
|||||||
public
|
public
|
||||||
<Iface> void createRemoteObject(final Class<Iface> interfaceClass, final RemoteObjectCallback<Iface> callback) {
|
<Iface> void createRemoteObject(final Class<Iface> interfaceClass, final RemoteObjectCallback<Iface> callback) {
|
||||||
try {
|
try {
|
||||||
C connection0 = connectionManager.getConnection0();
|
connection.createRemoteObject(interfaceClass, callback);
|
||||||
connection0.createRemoteObject(interfaceClass, callback);
|
} catch (NullPointerException e) {
|
||||||
} catch (IOException e) {
|
|
||||||
logger.error("Error creating remote object!", e);
|
logger.error("Error creating remote object!", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -452,9 +449,8 @@ class Client<C extends Connection> extends EndPointClient<C> implements Connecti
|
|||||||
public
|
public
|
||||||
<Iface> void getRemoteObject(final int objectId, final RemoteObjectCallback<Iface> callback) {
|
<Iface> void getRemoteObject(final int objectId, final RemoteObjectCallback<Iface> callback) {
|
||||||
try {
|
try {
|
||||||
C connection0 = connectionManager.getConnection0();
|
connection.getRemoteObject(objectId, callback);
|
||||||
connection0.getRemoteObject(objectId, callback);
|
} catch (NullPointerException e) {
|
||||||
} catch (IOException e) {
|
|
||||||
logger.error("Error getting remote object!", e);
|
logger.error("Error getting remote object!", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -464,11 +460,12 @@ class Client<C extends Connection> extends EndPointClient<C> implements Connecti
|
|||||||
* <p/>
|
* <p/>
|
||||||
* Make <b>sure</b> that you only call this <b>after</b> the client connects!
|
* Make <b>sure</b> that you only call this <b>after</b> the client connects!
|
||||||
* <p/>
|
* <p/>
|
||||||
* This is preferred to {@link EndPointBase#getConnections()} getConnections()}, as it properly does some error checking
|
* This is preferred to {@link EndPointBase#getConnections()}, as it properly does some error checking
|
||||||
*/
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
public
|
public
|
||||||
C getConnection() {
|
C getConnection() {
|
||||||
return connection;
|
return (C) connection;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -26,7 +26,6 @@ import dorkbox.network.connection.registration.remote.RegistrationRemoteHandlerS
|
|||||||
import dorkbox.util.NamedThreadFactory;
|
import dorkbox.util.NamedThreadFactory;
|
||||||
import dorkbox.util.OS;
|
import dorkbox.util.OS;
|
||||||
import dorkbox.util.Property;
|
import dorkbox.util.Property;
|
||||||
import dorkbox.util.exceptions.InitializationException;
|
|
||||||
import dorkbox.util.exceptions.SecurityException;
|
import dorkbox.util.exceptions.SecurityException;
|
||||||
import io.netty.bootstrap.Bootstrap;
|
import io.netty.bootstrap.Bootstrap;
|
||||||
import io.netty.bootstrap.ServerBootstrap;
|
import io.netty.bootstrap.ServerBootstrap;
|
||||||
@ -58,7 +57,7 @@ import io.netty.channel.unix.UnixChannelOption;
|
|||||||
* 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<C> {
|
class Server<C extends Connection> extends EndPointServer {
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -93,7 +92,7 @@ class Server<C extends Connection> extends EndPointServer<C> {
|
|||||||
* 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 InitializationException, SecurityException, IOException {
|
Server() throws SecurityException {
|
||||||
this(Configuration.localOnly());
|
this(Configuration.localOnly());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -102,7 +101,7 @@ class Server<C extends Connection> extends EndPointServer<C> {
|
|||||||
*/
|
*/
|
||||||
@SuppressWarnings("AutoBoxing")
|
@SuppressWarnings("AutoBoxing")
|
||||||
public
|
public
|
||||||
Server(Configuration config) throws InitializationException, SecurityException, IOException {
|
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);
|
||||||
@ -186,7 +185,7 @@ class Server<C extends Connection> extends EndPointServer<C> {
|
|||||||
.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<C>(threadName, registrationWrapper));
|
.childHandler(new RegistrationLocalHandlerServer(threadName, registrationWrapper));
|
||||||
|
|
||||||
manageForShutdown(localBoss);
|
manageForShutdown(localBoss);
|
||||||
manageForShutdown(localWorker);
|
manageForShutdown(localWorker);
|
||||||
@ -219,9 +218,9 @@ class Server<C extends Connection> extends EndPointServer<C> {
|
|||||||
.childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)
|
.childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)
|
||||||
.childOption(ChannelOption.SO_KEEPALIVE, true)
|
.childOption(ChannelOption.SO_KEEPALIVE, 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))
|
||||||
.childHandler(new RegistrationRemoteHandlerServerTCP<C>(threadName,
|
.childHandler(new RegistrationRemoteHandlerServerTCP(threadName,
|
||||||
registrationWrapper,
|
registrationWrapper,
|
||||||
serializationManager));
|
serializationManager));
|
||||||
|
|
||||||
// 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")) {
|
||||||
@ -265,9 +264,9 @@ class Server<C extends Connection> extends EndPointServer<C> {
|
|||||||
|
|
||||||
// not binding to specific address, since it's driven by TCP, and that can be bound to a specific address
|
// not binding to specific address, since it's driven by TCP, and that can be bound to a specific address
|
||||||
.localAddress(udpPort) // if you bind to a specific interface, Linux will be unable to receive broadcast packets!
|
.localAddress(udpPort) // if you bind to a specific interface, Linux will be unable to receive broadcast packets!
|
||||||
.handler(new RegistrationRemoteHandlerServerUDP<C>(threadName,
|
.handler(new RegistrationRemoteHandlerServerUDP(threadName,
|
||||||
registrationWrapper,
|
registrationWrapper,
|
||||||
serializationManager));
|
serializationManager));
|
||||||
|
|
||||||
|
|
||||||
// Enable to READ from MULTICAST data (ie, 192.168.1.0)
|
// Enable to READ from MULTICAST data (ie, 192.168.1.0)
|
||||||
|
@ -47,7 +47,6 @@ interface Connection {
|
|||||||
@SuppressWarnings("rawtypes")
|
@SuppressWarnings("rawtypes")
|
||||||
EndPointBase getEndPoint();
|
EndPointBase getEndPoint();
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return the connection (TCP or LOCAL) id of this connection.
|
* @return the connection (TCP or LOCAL) id of this connection.
|
||||||
*/
|
*/
|
||||||
|
@ -17,11 +17,12 @@ package dorkbox.network.connection;
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
|
import java.lang.reflect.Proxy;
|
||||||
import java.util.AbstractMap;
|
import java.util.AbstractMap;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.WeakHashMap;
|
import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
import java.util.concurrent.CountDownLatch;
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import java.util.concurrent.atomic.AtomicLong;
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
|
|
||||||
@ -38,13 +39,20 @@ import dorkbox.network.connection.ping.PingTuple;
|
|||||||
import dorkbox.network.connection.wrapper.ChannelNetworkWrapper;
|
import dorkbox.network.connection.wrapper.ChannelNetworkWrapper;
|
||||||
import dorkbox.network.connection.wrapper.ChannelNull;
|
import dorkbox.network.connection.wrapper.ChannelNull;
|
||||||
import dorkbox.network.connection.wrapper.ChannelWrapper;
|
import dorkbox.network.connection.wrapper.ChannelWrapper;
|
||||||
|
import dorkbox.network.rmi.InvokeMethod;
|
||||||
|
import dorkbox.network.rmi.InvokeMethodResult;
|
||||||
import dorkbox.network.rmi.RemoteObject;
|
import dorkbox.network.rmi.RemoteObject;
|
||||||
import dorkbox.network.rmi.RemoteObjectCallback;
|
import dorkbox.network.rmi.RemoteObjectCallback;
|
||||||
import dorkbox.network.rmi.Rmi;
|
import dorkbox.network.rmi.Rmi;
|
||||||
import dorkbox.network.rmi.RmiBridge;
|
import dorkbox.network.rmi.RmiBridge;
|
||||||
|
import dorkbox.network.rmi.RmiMessage;
|
||||||
|
import dorkbox.network.rmi.RmiObjectHandler;
|
||||||
|
import dorkbox.network.rmi.RmiProxyHandler;
|
||||||
import dorkbox.network.rmi.RmiRegistration;
|
import dorkbox.network.rmi.RmiRegistration;
|
||||||
|
import dorkbox.network.rmi.TimeoutException;
|
||||||
import dorkbox.network.serialization.CryptoSerializationManager;
|
import dorkbox.network.serialization.CryptoSerializationManager;
|
||||||
import dorkbox.util.collections.IntMap;
|
import dorkbox.util.collections.IntMap;
|
||||||
|
import dorkbox.util.collections.LockFreeHashMap;
|
||||||
import dorkbox.util.generics.ClassHelper;
|
import dorkbox.util.generics.ClassHelper;
|
||||||
import io.netty.channel.Channel;
|
import io.netty.channel.Channel;
|
||||||
import io.netty.channel.ChannelHandler.Sharable;
|
import io.netty.channel.ChannelHandler.Sharable;
|
||||||
@ -68,6 +76,24 @@ import io.netty.util.concurrent.Promise;
|
|||||||
@Sharable
|
@Sharable
|
||||||
public
|
public
|
||||||
class ConnectionImpl extends ChannelInboundHandlerAdapter implements ICryptoConnection, Connection, Listeners, ConnectionBridge {
|
class ConnectionImpl extends ChannelInboundHandlerAdapter implements ICryptoConnection, Connection, Listeners, ConnectionBridge {
|
||||||
|
// default false
|
||||||
|
public static boolean ENABLE_PROXY_OBJECTS = true;
|
||||||
|
|
||||||
|
public static
|
||||||
|
boolean isTcp(Class<? extends Channel> channelClass) {
|
||||||
|
return channelClass == NioSocketChannel.class || channelClass == EpollSocketChannel.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static
|
||||||
|
boolean isUdp(Class<? extends Channel> channelClass) {
|
||||||
|
return channelClass == NioDatagramChannel.class || channelClass == EpollDatagramChannel.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static
|
||||||
|
boolean isLocal(Class<? extends Channel> channelClass) {
|
||||||
|
return channelClass == LocalChannel.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private final org.slf4j.Logger logger;
|
private final org.slf4j.Logger logger;
|
||||||
|
|
||||||
@ -82,14 +108,14 @@ class ConnectionImpl extends ChannelInboundHandlerAdapter implements ICryptoConn
|
|||||||
private final Object messageInProgressLock = new Object();
|
private final Object messageInProgressLock = new Object();
|
||||||
private final AtomicBoolean messageInProgress = new AtomicBoolean(false);
|
private final AtomicBoolean messageInProgress = new AtomicBoolean(false);
|
||||||
|
|
||||||
private ISessionManager<Connection> sessionManager;
|
private ISessionManager sessionManager;
|
||||||
private ChannelWrapper<Connection> channelWrapper;
|
private ChannelWrapper channelWrapper;
|
||||||
private boolean isLoopback;
|
private boolean isLoopback;
|
||||||
|
|
||||||
private volatile PingFuture pingFuture = null;
|
private volatile PingFuture pingFuture = null;
|
||||||
|
|
||||||
// used to store connection local listeners (instead of global listeners). Only possible on the server.
|
// used to store connection local listeners (instead of global listeners). Only possible on the server.
|
||||||
private volatile ConnectionManager<Connection> localListenerManager;
|
private volatile ConnectionManager localListenerManager;
|
||||||
|
|
||||||
// while on the CLIENT, if the SERVER's ecc key has changed, the client will abort and show an error.
|
// while on the CLIENT, if the SERVER's ecc key has changed, the client will abort and show an error.
|
||||||
private boolean remoteKeyChanged;
|
private boolean remoteKeyChanged;
|
||||||
@ -108,21 +134,32 @@ class ConnectionImpl extends ChannelInboundHandlerAdapter implements ICryptoConn
|
|||||||
//
|
//
|
||||||
// RMI fields
|
// RMI fields
|
||||||
//
|
//
|
||||||
protected CountDownLatch rmi;
|
|
||||||
private final RmiBridge rmiBridge;
|
private final RmiBridge rmiBridge;
|
||||||
private final Map<Integer, RemoteObject> proxyIdCache = new WeakHashMap<Integer, RemoteObject>(8);
|
private final Map<Integer, RemoteObject> proxyIdCache;
|
||||||
private final IntMap<RemoteObjectCallback> rmiRegistrationCallbacks = new IntMap<RemoteObjectCallback>();
|
private final List<Listener.OnMessageReceived<Connection, InvokeMethodResult>> proxyListeners;
|
||||||
|
|
||||||
|
private final IntMap<RemoteObjectCallback> rmiRegistrationCallbacks;
|
||||||
private int rmiCallbackId = 0; // protected by synchronized (rmiRegistrationCallbacks)
|
private int rmiCallbackId = 0; // protected by synchronized (rmiRegistrationCallbacks)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* All of the parameters can be null, when metaChannel wants to get the base class type
|
* All of the parameters can be null, when metaChannel wants to get the base class type
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings({"rawtypes", "unchecked"})
|
|
||||||
public
|
public
|
||||||
ConnectionImpl(final Logger logger, final EndPointBase endPoint, final RmiBridge rmiBridge) {
|
ConnectionImpl(final Logger logger, final EndPointBase endPoint, final RmiBridge rmiBridge) {
|
||||||
this.logger = logger;
|
this.logger = logger;
|
||||||
this.endPoint = endPoint;
|
this.endPoint = endPoint;
|
||||||
this.rmiBridge = rmiBridge;
|
this.rmiBridge = rmiBridge;
|
||||||
|
|
||||||
|
if (endPoint != null && endPoint.globalRmiBridge != null) {
|
||||||
|
// rmi is enabled.
|
||||||
|
proxyIdCache = new LockFreeHashMap<Integer, RemoteObject>();
|
||||||
|
proxyListeners = new CopyOnWriteArrayList<Listener.OnMessageReceived<Connection, InvokeMethodResult>>();
|
||||||
|
rmiRegistrationCallbacks = new IntMap<RemoteObjectCallback>();
|
||||||
|
} else {
|
||||||
|
proxyIdCache = null;
|
||||||
|
proxyListeners = null;
|
||||||
|
rmiRegistrationCallbacks = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -130,8 +167,8 @@ class ConnectionImpl extends ChannelInboundHandlerAdapter implements ICryptoConn
|
|||||||
* <p/>
|
* <p/>
|
||||||
* This happens BEFORE prep.
|
* This happens BEFORE prep.
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unchecked")
|
final
|
||||||
void init(final ChannelWrapper channelWrapper, final ConnectionManager<Connection> sessionManager) {
|
void init(final ChannelWrapper channelWrapper, final ISessionManager sessionManager) {
|
||||||
this.sessionManager = sessionManager;
|
this.sessionManager = sessionManager;
|
||||||
this.channelWrapper = channelWrapper;
|
this.channelWrapper = channelWrapper;
|
||||||
|
|
||||||
@ -151,6 +188,7 @@ class ConnectionImpl extends ChannelInboundHandlerAdapter implements ICryptoConn
|
|||||||
* <p/>
|
* <p/>
|
||||||
* This happens AFTER init.
|
* This happens AFTER init.
|
||||||
*/
|
*/
|
||||||
|
final
|
||||||
void prep() {
|
void prep() {
|
||||||
if (this.channelWrapper != null) {
|
if (this.channelWrapper != null) {
|
||||||
this.channelWrapper.init();
|
this.channelWrapper.init();
|
||||||
@ -201,7 +239,6 @@ class ConnectionImpl extends ChannelInboundHandlerAdapter implements ICryptoConn
|
|||||||
return this.channelWrapper.getRemoteHost();
|
return this.channelWrapper.getRemoteHost();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return true if this connection is established on the loopback interface
|
* @return true if this connection is established on the loopback interface
|
||||||
*/
|
*/
|
||||||
@ -216,7 +253,7 @@ class ConnectionImpl extends ChannelInboundHandlerAdapter implements ICryptoConn
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public
|
public
|
||||||
EndPointBase<Connection> getEndPoint() {
|
EndPointBase getEndPoint() {
|
||||||
return this.endPoint;
|
return this.endPoint;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -529,7 +566,6 @@ class ConnectionImpl extends ChannelInboundHandlerAdapter implements ICryptoConn
|
|||||||
|
|
||||||
public
|
public
|
||||||
void channelRead(Object object) throws Exception {
|
void channelRead(Object object) throws Exception {
|
||||||
|
|
||||||
// prevent close from occurring SMACK in the middle of a message in progress.
|
// prevent close from occurring SMACK in the middle of a message in progress.
|
||||||
// delay close until it's finished.
|
// delay close until it's finished.
|
||||||
this.messageInProgress.set(true);
|
this.messageInProgress.set(true);
|
||||||
@ -568,7 +604,8 @@ class ConnectionImpl extends ChannelInboundHandlerAdapter implements ICryptoConn
|
|||||||
Channel channel = context.channel();
|
Channel channel = context.channel();
|
||||||
Class<? extends Channel> channelClass = channel.getClass();
|
Class<? extends Channel> channelClass = channel.getClass();
|
||||||
|
|
||||||
boolean isTCP = channelClass == NioSocketChannel.class || channelClass == EpollSocketChannel.class;
|
boolean isTCP = isTcp(channelClass);
|
||||||
|
boolean isLocal = isLocal(channelClass);
|
||||||
|
|
||||||
if (this.logger.isInfoEnabled()) {
|
if (this.logger.isInfoEnabled()) {
|
||||||
String type;
|
String type;
|
||||||
@ -576,10 +613,10 @@ class ConnectionImpl extends ChannelInboundHandlerAdapter implements ICryptoConn
|
|||||||
if (isTCP) {
|
if (isTCP) {
|
||||||
type = "TCP";
|
type = "TCP";
|
||||||
}
|
}
|
||||||
else if (channelClass == NioDatagramChannel.class || channelClass == EpollDatagramChannel.class) {
|
else if (isUdp(channelClass)) {
|
||||||
type = "UDP";
|
type = "UDP";
|
||||||
}
|
}
|
||||||
else if (channelClass == LocalChannel.class) {
|
else if (isLocal) {
|
||||||
type = "LOCAL";
|
type = "LOCAL";
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@ -597,7 +634,7 @@ class ConnectionImpl extends ChannelInboundHandlerAdapter implements ICryptoConn
|
|||||||
}
|
}
|
||||||
|
|
||||||
// our master channels are TCP/LOCAL (which are mutually exclusive). Only key disconnect events based on the status of them.
|
// our master channels are TCP/LOCAL (which are mutually exclusive). Only key disconnect events based on the status of them.
|
||||||
if (isTCP || channelClass == LocalChannel.class) {
|
if (isTCP || isLocal) {
|
||||||
// this is because channelInactive can ONLY happen when netty shuts down the channel.
|
// this is because channelInactive can ONLY happen when netty shuts down the channel.
|
||||||
// and connection.close() can be called by the user.
|
// and connection.close() can be called by the user.
|
||||||
this.sessionManager.onDisconnected(this);
|
this.sessionManager.onDisconnected(this);
|
||||||
@ -618,6 +655,12 @@ class ConnectionImpl extends ChannelInboundHandlerAdapter implements ICryptoConn
|
|||||||
@Override
|
@Override
|
||||||
public final
|
public final
|
||||||
void close() {
|
void close() {
|
||||||
|
close(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
final
|
||||||
|
void close(final boolean keepListeners) {
|
||||||
// only close if we aren't already in the middle of closing.
|
// only close if we aren't already in the middle of closing.
|
||||||
if (this.closeInProgress.compareAndSet(false, true)) {
|
if (this.closeInProgress.compareAndSet(false, true)) {
|
||||||
int idleTimeoutMs = this.endPoint.getIdleTimeout();
|
int idleTimeoutMs = this.endPoint.getIdleTimeout();
|
||||||
@ -657,9 +700,29 @@ class ConnectionImpl extends ChannelInboundHandlerAdapter implements ICryptoConn
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// remove all listeners, but ONLY if we are the server. If we remove all listeners and we are the client, then we remove
|
||||||
|
// ALL logic from the client! The server is OK because the server listeners per connection are dynamically added
|
||||||
|
if (!keepListeners) {
|
||||||
|
removeAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
// proxy listeners are cleared in the removeAll() call
|
||||||
|
if (proxyIdCache != null) {
|
||||||
|
synchronized (proxyIdCache) {
|
||||||
|
proxyIdCache.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rmiRegistrationCallbacks != null) {
|
||||||
|
synchronized (rmiRegistrationCallbacks) {
|
||||||
|
rmiRegistrationCallbacks.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Marks the connection to be closed as soon as possible. This is evaluated when the current
|
* Marks the connection to be closed as soon as possible. This is evaluated when the current
|
||||||
* thread execution returns to the network stack.
|
* thread execution returns to the network stack.
|
||||||
@ -716,7 +779,6 @@ class ConnectionImpl extends ChannelInboundHandlerAdapter implements ICryptoConn
|
|||||||
* (via connection.addListener), meaning that ONLY that listener attached to
|
* (via connection.addListener), meaning that ONLY that listener attached to
|
||||||
* the connection is notified on that event (ie, admin type listeners)
|
* the connection is notified on that event (ie, admin type listeners)
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("rawtypes")
|
|
||||||
@Override
|
@Override
|
||||||
public final
|
public final
|
||||||
Listeners add(Listener listener) {
|
Listeners add(Listener listener) {
|
||||||
@ -758,7 +820,6 @@ class ConnectionImpl extends ChannelInboundHandlerAdapter implements ICryptoConn
|
|||||||
* connection.removeListener), meaning that ONLY that listener attached to
|
* connection.removeListener), meaning that ONLY that listener attached to
|
||||||
* the connection is removed
|
* the connection is removed
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("rawtypes")
|
|
||||||
@Override
|
@Override
|
||||||
public final
|
public final
|
||||||
Listeners remove(Listener listener) {
|
Listeners remove(Listener listener) {
|
||||||
@ -777,7 +838,7 @@ class ConnectionImpl extends ChannelInboundHandlerAdapter implements ICryptoConn
|
|||||||
this.localListenerManager.remove(listener);
|
this.localListenerManager.remove(listener);
|
||||||
|
|
||||||
if (!this.localListenerManager.hasListeners()) {
|
if (!this.localListenerManager.hasListeners()) {
|
||||||
((EndPointServer<Connection>) this.endPoint).removeListenerManager(this);
|
((EndPointServer) this.endPoint).removeListenerManager(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -793,10 +854,16 @@ class ConnectionImpl extends ChannelInboundHandlerAdapter implements ICryptoConn
|
|||||||
/**
|
/**
|
||||||
* Removes all registered listeners from this connection/endpoint to NO
|
* Removes all registered listeners from this connection/endpoint to NO
|
||||||
* LONGER be notified of connect/disconnect/idle/receive(object) events.
|
* LONGER be notified of connect/disconnect/idle/receive(object) events.
|
||||||
|
*
|
||||||
|
* This includes all proxy listeners
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public final
|
public final
|
||||||
Listeners removeAll() {
|
Listeners removeAll() {
|
||||||
|
if (proxyListeners != null) {
|
||||||
|
proxyListeners.clear();
|
||||||
|
}
|
||||||
|
|
||||||
if (this.endPoint instanceof EndPointServer) {
|
if (this.endPoint instanceof EndPointServer) {
|
||||||
// when we are a server, NORMALLY listeners are added at the GLOBAL level
|
// when we are a server, NORMALLY listeners are added at the GLOBAL level
|
||||||
// meaning --
|
// meaning --
|
||||||
@ -812,7 +879,7 @@ class ConnectionImpl extends ChannelInboundHandlerAdapter implements ICryptoConn
|
|||||||
this.localListenerManager.removeAll();
|
this.localListenerManager.removeAll();
|
||||||
this.localListenerManager = null;
|
this.localListenerManager = null;
|
||||||
|
|
||||||
((EndPointServer<Connection>) this.endPoint).removeListenerManager(this);
|
((EndPointServer) this.endPoint).removeListenerManager(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -825,8 +892,7 @@ class ConnectionImpl extends ChannelInboundHandlerAdapter implements ICryptoConn
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes all registered listeners (of the object type) from this
|
* Removes all registered listeners (of the object type) from this connection/endpoint to NO LONGER be notified of
|
||||||
* connection/endpoint to NO LONGER be notified of
|
|
||||||
* connect/disconnect/idle/receive(object) events.
|
* connect/disconnect/idle/receive(object) events.
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
@ -848,7 +914,7 @@ class ConnectionImpl extends ChannelInboundHandlerAdapter implements ICryptoConn
|
|||||||
|
|
||||||
if (!this.localListenerManager.hasListeners()) {
|
if (!this.localListenerManager.hasListeners()) {
|
||||||
this.localListenerManager = null;
|
this.localListenerManager = null;
|
||||||
((EndPointServer<Connection>) this.endPoint).removeListenerManager(this);
|
((EndPointServer) this.endPoint).removeListenerManager(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -908,7 +974,6 @@ class ConnectionImpl extends ChannelInboundHandlerAdapter implements ICryptoConn
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
@SuppressWarnings({"UnnecessaryLocalVariable", "unchecked", "Duplicates"})
|
|
||||||
@Override
|
@Override
|
||||||
public final
|
public final
|
||||||
<Iface> void createRemoteObject(final Class<Iface> interfaceClass, final RemoteObjectCallback<Iface> callback) {
|
<Iface> void createRemoteObject(final Class<Iface> interfaceClass, final RemoteObjectCallback<Iface> callback) {
|
||||||
@ -931,7 +996,6 @@ class ConnectionImpl extends ChannelInboundHandlerAdapter implements ICryptoConn
|
|||||||
TCP(message).flush();
|
TCP(message).flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings({"UnnecessaryLocalVariable", "unchecked", "Duplicates"})
|
|
||||||
@Override
|
@Override
|
||||||
public final
|
public final
|
||||||
<Iface> void getRemoteObject(final int objectId, final RemoteObjectCallback<Iface> callback) {
|
<Iface> void getRemoteObject(final int objectId, final RemoteObjectCallback<Iface> callback) {
|
||||||
@ -952,21 +1016,59 @@ class ConnectionImpl extends ChannelInboundHandlerAdapter implements ICryptoConn
|
|||||||
TCP(message).flush();
|
TCP(message).flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
// default false
|
|
||||||
public static boolean ENABLE_PROXY_OBJECTS = true;
|
/**
|
||||||
|
* Manages the RMI stuff for a connection.
|
||||||
|
*
|
||||||
|
* @return true if there was RMI stuff done, false if the message was "normal" and nothing was done
|
||||||
|
*/
|
||||||
|
boolean manageRmi(final Object message) {
|
||||||
|
if (message instanceof RmiMessage) {
|
||||||
|
RmiObjectHandler rmiObjectHandler = channelWrapper.manageRmi();
|
||||||
|
|
||||||
|
if (message instanceof InvokeMethod) {
|
||||||
|
rmiObjectHandler.invoke(this, (InvokeMethod) message, rmiBridge.getListener());
|
||||||
|
}
|
||||||
|
else if (message instanceof InvokeMethodResult) {
|
||||||
|
for (Listener.OnMessageReceived<Connection, InvokeMethodResult> proxyListener : proxyListeners) {
|
||||||
|
proxyListener.received(this, (InvokeMethodResult) message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (message instanceof RmiRegistration) {
|
||||||
|
rmiObjectHandler.registration(this, (RmiRegistration) message);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Objects that are on the "local" in-jvm connection have fixup their objects. For "network" connections, this is automatically done.
|
||||||
|
*/
|
||||||
|
Object fixupRmi(final Object message) {
|
||||||
|
// "local RMI" objects have to be modified, this part does that
|
||||||
|
RmiObjectHandler rmiObjectHandler = channelWrapper.manageRmi();
|
||||||
|
return rmiObjectHandler.normalMessages(this, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This will remove the invoke and invoke response listeners for this the remote object
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
void removeRmiListeners(final int objectID, final Listener listener) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* For network connections, the interface class kryo ID == implementation class kryo ID, so they switch automatically.
|
* For network connections, the interface class kryo ID == implementation class kryo ID, so they switch automatically.
|
||||||
* For local connections, we have to switch it appropriately in the LocalRmiProxy
|
* For local connections, we have to switch it appropriately in the LocalRmiProxy
|
||||||
*
|
|
||||||
* @param implementationClass
|
|
||||||
* @param callbackId
|
|
||||||
* @return
|
|
||||||
*/
|
*/
|
||||||
public final
|
public final
|
||||||
RmiRegistration createNewRmiObject(final Class<?> interfaceClass, final Class<?> implementationClass, final int callbackId) {
|
RmiRegistration createNewRmiObject(final Class<?> interfaceClass, final Class<?> implementationClass, final int callbackId) {
|
||||||
|
|
||||||
CryptoSerializationManager manager = getEndPoint().serializationManager;
|
CryptoSerializationManager manager = getEndPoint().serializationManager;
|
||||||
|
|
||||||
KryoExtra kryo = null;
|
KryoExtra kryo = null;
|
||||||
@ -1090,14 +1192,37 @@ class ConnectionImpl extends ChannelInboundHandlerAdapter implements ICryptoConn
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used by RMI for the LOCAL side, to get the proxy object as an interface
|
* Warning. This is an advanced method. You should probably be using {@link Connection#createRemoteObject(Class, RemoteObjectCallback)}
|
||||||
|
* <p>
|
||||||
|
* <p>
|
||||||
|
* Returns a proxy object that implements the specified interface, and the methods invoked on the proxy object will be invoked
|
||||||
|
* remotely.
|
||||||
|
* <p>
|
||||||
|
* Methods that return a value will throw {@link TimeoutException} if the response is not received with the {@link
|
||||||
|
* RemoteObject#setResponseTimeout(int) response timeout}.
|
||||||
|
* <p/>
|
||||||
|
* If {@link RemoteObject#setAsync(boolean) non-blocking} is false (the default), then methods that return a value must not be
|
||||||
|
* called from the update thread for the connection. An exception will be thrown if this occurs. Methods with a void return value can be
|
||||||
|
* called on the update thread.
|
||||||
|
* <p/>
|
||||||
|
* If a proxy returned from this method is part of an object graph sent over the network, the object graph on the receiving side will
|
||||||
|
* have the proxy object replaced with the registered object.
|
||||||
*
|
*
|
||||||
* @param objectID is the RMI object ID
|
* @see RemoteObject
|
||||||
* @param iFace must be the interface the proxy will bind to
|
*
|
||||||
|
* @param objectID this is the remote object ID (assigned by RMI). This is NOT the kryo registration ID
|
||||||
|
* @param iFace this is the RMI interface
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public
|
public
|
||||||
RemoteObject getProxyObject(final int objectID, final Class<?> iFace) {
|
RemoteObject getProxyObject(final int objectID, final Class<?> iFace) {
|
||||||
|
if (iFace == null) {
|
||||||
|
throw new IllegalArgumentException("iface cannot be null.");
|
||||||
|
}
|
||||||
|
if (!iFace.isInterface()) {
|
||||||
|
throw new IllegalArgumentException("iface must be an interface.");
|
||||||
|
}
|
||||||
|
|
||||||
synchronized (proxyIdCache) {
|
synchronized (proxyIdCache) {
|
||||||
// we want to have a connection specific cache of IDs, using weak references.
|
// we want to have a connection specific cache of IDs, using weak references.
|
||||||
// because this is PER CONNECTION, this is safe.
|
// because this is PER CONNECTION, this is safe.
|
||||||
@ -1105,7 +1230,18 @@ class ConnectionImpl extends ChannelInboundHandlerAdapter implements ICryptoConn
|
|||||||
|
|
||||||
if (remoteObject == null) {
|
if (remoteObject == null) {
|
||||||
// duplicates are fine, as they represent the same object (as specified by the ID) on the remote side.
|
// duplicates are fine, as they represent the same object (as specified by the ID) on the remote side.
|
||||||
remoteObject = rmiBridge.createProxyObject(this, objectID, iFace);
|
// remoteObject = rmiBridge.createProxyObject(this, objectID, iFace);
|
||||||
|
|
||||||
|
// the ACTUAL proxy is created in the connection impl.
|
||||||
|
RmiProxyHandler proxyObject = new RmiProxyHandler(this, objectID, iFace);
|
||||||
|
proxyListeners.add(proxyObject.getListener());
|
||||||
|
|
||||||
|
Class<?>[] temp = new Class<?>[2];
|
||||||
|
temp[0] = RemoteObject.class;
|
||||||
|
temp[1] = iFace;
|
||||||
|
|
||||||
|
remoteObject = (RemoteObject) Proxy.newProxyInstance(RmiBridge.class.getClassLoader(), temp, proxyObject);
|
||||||
|
|
||||||
proxyIdCache.put(objectID, remoteObject);
|
proxyIdCache.put(objectID, remoteObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,7 +15,6 @@
|
|||||||
*/
|
*/
|
||||||
package dorkbox.network.connection;
|
package dorkbox.network.connection;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
|
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
|
||||||
@ -38,54 +37,52 @@ import dorkbox.util.generics.TypeResolver;
|
|||||||
// .equals() compares the identity on purpose,this because we cannot create two separate objects that are somehow equal to each other.
|
// .equals() compares the identity on purpose,this because we cannot create two separate objects that are somehow equal to each other.
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public
|
public
|
||||||
class ConnectionManager<C extends Connection> implements Listeners, ISessionManager<C>, ConnectionPoint, ConnectionBridgeServer<C>,
|
class ConnectionManager<C extends Connection> implements Listeners, ISessionManager, ConnectionPoint, ConnectionBridgeServer,
|
||||||
ConnectionExceptSpecifiedBridgeServer<C> {
|
ConnectionExceptSpecifiedBridgeServer {
|
||||||
/**
|
/**
|
||||||
* Specifies the load-factor for the IdentityMap used to manage keeping track of the number of connections + listeners
|
* Specifies the load-factor for the IdentityMap used to manage keeping track of the number of connections + listeners
|
||||||
*/
|
*/
|
||||||
@Property
|
@Property
|
||||||
public static final float LOAD_FACTOR = 0.8F;
|
public static final float LOAD_FACTOR = 0.8F;
|
||||||
|
|
||||||
|
// Recommended for best performance while adhering to the "single writer principle". Must be static-final
|
||||||
|
private static final AtomicReferenceFieldUpdater<ConnectionManager, IdentityMap> localManagersREF = AtomicReferenceFieldUpdater.newUpdater(
|
||||||
|
ConnectionManager.class,
|
||||||
|
IdentityMap.class,
|
||||||
|
"localManagers");
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Recommended for best performance while adhering to the "single writer principle". Must be static-final
|
||||||
|
private static final AtomicReferenceFieldUpdater<ConnectionManager, ConcurrentEntry> connectionsREF = AtomicReferenceFieldUpdater.newUpdater(
|
||||||
|
ConnectionManager.class,
|
||||||
|
ConcurrentEntry.class,
|
||||||
|
"connectionsHead");
|
||||||
|
|
||||||
|
|
||||||
private final String loggerName;
|
private final String loggerName;
|
||||||
|
|
||||||
private final OnConnectedManager<C> onConnectedManager;
|
private final OnConnectedManager onConnectedManager;
|
||||||
private final OnDisconnectedManager<C> onDisconnectedManager;
|
private final OnDisconnectedManager onDisconnectedManager;
|
||||||
private final OnIdleManager<C> onIdleManager;
|
private final OnIdleManager onIdleManager;
|
||||||
private final OnMessageReceivedManager<C> onMessageReceivedManager;
|
private final OnMessageReceivedManager onMessageReceivedManager;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@SuppressWarnings({"FieldCanBeLocal", "unused"})
|
@SuppressWarnings({"FieldCanBeLocal", "unused"})
|
||||||
private volatile ConcurrentEntry<C> connectionsHead = null; // reference to the first element
|
private volatile ConcurrentEntry<Connection> connectionsHead = null; // reference to the first element
|
||||||
|
|
||||||
// This is ONLY touched by a single thread, maintains a map of entries for FAST lookup during connection remove.
|
// This is ONLY touched by a single thread, maintains a map of entries for FAST lookup during connection remove.
|
||||||
private final IdentityMap<C, ConcurrentEntry> connectionEntries = new IdentityMap<C, ConcurrentEntry>(32, ConnectionManager.LOAD_FACTOR);
|
private final IdentityMap<Connection, ConcurrentEntry> connectionEntries = new IdentityMap<Connection, ConcurrentEntry>(32, ConnectionManager.LOAD_FACTOR);
|
||||||
|
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
private volatile IdentityMap<Connection, ConnectionManager<C>> localManagers = new IdentityMap<Connection, ConnectionManager<C>>(8, ConnectionManager.LOAD_FACTOR);
|
private volatile IdentityMap<Connection, ConnectionManager> localManagers = new IdentityMap<Connection, ConnectionManager>(8, ConnectionManager.LOAD_FACTOR);
|
||||||
|
|
||||||
|
|
||||||
// synchronized is used here to ensure the "single writer principle", and make sure that ONLY one thread at a time can enter this
|
// synchronized is used here to ensure the "single writer principle", and make sure that ONLY one thread at a time can enter this
|
||||||
// section. Because of this, we can have unlimited reader threads all going at the same time, without contention (which is our
|
// section. Because of this, we can have unlimited reader threads all going at the same time, without contention (which is our
|
||||||
// use-case 99% of the time)
|
// use-case 99% of the time)
|
||||||
private final Object singleWriterLock2 = new Object();
|
private final Object singleWriterConnectionsLock = new Object();
|
||||||
private final Object singleWriterLock3 = new Object();
|
private final Object singleWriterLocalManagerLock = new Object();
|
||||||
|
|
||||||
|
|
||||||
// Recommended for best performance while adhering to the "single writer principle". Must be static-final
|
|
||||||
private static final AtomicReferenceFieldUpdater<ConnectionManager, IdentityMap> localManagersREF =
|
|
||||||
AtomicReferenceFieldUpdater.newUpdater(ConnectionManager.class,
|
|
||||||
IdentityMap.class,
|
|
||||||
"localManagers");
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Recommended for best performance while adhering to the "single writer principle". Must be static-final
|
|
||||||
private static final AtomicReferenceFieldUpdater<ConnectionManager, ConcurrentEntry> connectionsREF =
|
|
||||||
AtomicReferenceFieldUpdater.newUpdater(ConnectionManager.class,
|
|
||||||
ConcurrentEntry.class,
|
|
||||||
"connectionsHead");
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -102,10 +99,10 @@ class ConnectionManager<C extends Connection> implements Listeners, ISessionMana
|
|||||||
this.logger = org.slf4j.LoggerFactory.getLogger(loggerName);
|
this.logger = org.slf4j.LoggerFactory.getLogger(loggerName);
|
||||||
this.baseClass = baseClass;
|
this.baseClass = baseClass;
|
||||||
|
|
||||||
onConnectedManager = new OnConnectedManager<C>(logger);
|
onConnectedManager = new OnConnectedManager(logger);
|
||||||
onDisconnectedManager = new OnDisconnectedManager<C>(logger);
|
onDisconnectedManager = new OnDisconnectedManager(logger);
|
||||||
onIdleManager = new OnIdleManager<C>(logger);
|
onIdleManager = new OnIdleManager(logger);
|
||||||
onMessageReceivedManager = new OnMessageReceivedManager<C>(logger);
|
onMessageReceivedManager = new OnMessageReceivedManager(logger);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -117,7 +114,6 @@ class ConnectionManager<C extends Connection> implements Listeners, ISessionMana
|
|||||||
* It is POSSIBLE to add a server connection ONLY (ie, not global) listener (via connection.addListener), meaning that ONLY that
|
* It is POSSIBLE to add a server connection ONLY (ie, not global) listener (via connection.addListener), meaning that ONLY that
|
||||||
* listener attached to the connection is notified on that event (ie, admin type listeners)
|
* listener attached to the connection is notified on that event (ie, admin type listeners)
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("rawtypes")
|
|
||||||
@Override
|
@Override
|
||||||
public final
|
public final
|
||||||
Listeners add(final Listener listener) {
|
Listeners add(final Listener listener) {
|
||||||
@ -149,25 +145,24 @@ class ConnectionManager<C extends Connection> implements Listeners, ISessionMana
|
|||||||
/**
|
/**
|
||||||
* INTERNAL USE ONLY
|
* INTERNAL USE ONLY
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
|
||||||
private
|
private
|
||||||
void addListener0(final Listener listener) {
|
void addListener0(final Listener listener) {
|
||||||
boolean found = false;
|
boolean found = false;
|
||||||
if (listener instanceof Listener.OnConnected) {
|
if (listener instanceof Listener.OnConnected) {
|
||||||
onConnectedManager.add((Listener.OnConnected<C>) listener);
|
onConnectedManager.add((Listener.OnConnected) listener);
|
||||||
found = true;
|
found = true;
|
||||||
}
|
}
|
||||||
if (listener instanceof Listener.OnDisconnected) {
|
if (listener instanceof Listener.OnDisconnected) {
|
||||||
onDisconnectedManager.add((Listener.OnDisconnected<C>) listener);
|
onDisconnectedManager.add((Listener.OnDisconnected) listener);
|
||||||
found = true;
|
found = true;
|
||||||
}
|
}
|
||||||
if (listener instanceof Listener.OnIdle) {
|
if (listener instanceof Listener.OnIdle) {
|
||||||
onIdleManager.add((Listener.OnIdle<C>) listener);
|
onIdleManager.add((Listener.OnIdle) listener);
|
||||||
found = true;
|
found = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (listener instanceof Listener.OnMessageReceived) {
|
if (listener instanceof Listener.OnMessageReceived) {
|
||||||
onMessageReceivedManager.add((Listener.OnMessageReceived<C, Object>) listener);
|
onMessageReceivedManager.add((Listener.OnMessageReceived) listener);
|
||||||
found = true;
|
found = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -196,7 +191,6 @@ class ConnectionManager<C extends Connection> implements Listeners, ISessionMana
|
|||||||
* It is POSSIBLE to remove a server-connection 'non-global' listener (via connection.removeListener), meaning that ONLY that listener
|
* It is POSSIBLE to remove a server-connection 'non-global' listener (via connection.removeListener), meaning that ONLY that listener
|
||||||
* attached to the connection is removed
|
* attached to the connection is removed
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("rawtypes")
|
|
||||||
@Override
|
@Override
|
||||||
public final
|
public final
|
||||||
Listeners remove(final Listener listener) {
|
Listeners remove(final Listener listener) {
|
||||||
@ -206,16 +200,16 @@ class ConnectionManager<C extends Connection> implements Listeners, ISessionMana
|
|||||||
|
|
||||||
boolean found = false;
|
boolean found = false;
|
||||||
if (listener instanceof Listener.OnConnected) {
|
if (listener instanceof Listener.OnConnected) {
|
||||||
found = onConnectedManager.remove((Listener.OnConnected<C>) listener);
|
found = onConnectedManager.remove((Listener.OnConnected) listener);
|
||||||
}
|
}
|
||||||
if (listener instanceof Listener.OnDisconnected) {
|
if (listener instanceof Listener.OnDisconnected) {
|
||||||
found |= onDisconnectedManager.remove((Listener.OnDisconnected<C>) listener);
|
found |= onDisconnectedManager.remove((Listener.OnDisconnected) listener);
|
||||||
}
|
}
|
||||||
if (listener instanceof Listener.OnIdle) {
|
if (listener instanceof Listener.OnIdle) {
|
||||||
found |= onIdleManager.remove((Listener.OnIdle<C>) listener);
|
found |= onIdleManager.remove((Listener.OnIdle) listener);
|
||||||
}
|
}
|
||||||
if (listener instanceof Listener.OnMessageReceived) {
|
if (listener instanceof Listener.OnMessageReceived) {
|
||||||
found |= onMessageReceivedManager.remove((Listener.OnMessageReceived<C, Object>) listener);
|
found |= onMessageReceivedManager.remove((Listener.OnMessageReceived) listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
final Logger logger2 = this.logger;
|
final Logger logger2 = this.logger;
|
||||||
@ -290,13 +284,22 @@ class ConnectionManager<C extends Connection> implements Listeners, ISessionMana
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public final
|
public final
|
||||||
void onMessage(final C connection, final Object message) {
|
void onMessage(final ConnectionImpl connection, final Object message) {
|
||||||
notifyOnMessage0(connection, message, false);
|
notifyOnMessage0(connection, message, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("Duplicates")
|
@SuppressWarnings("Duplicates")
|
||||||
private
|
private
|
||||||
boolean notifyOnMessage0(final C connection, final Object message, boolean foundListener) {
|
boolean notifyOnMessage0(final ConnectionImpl connection, Object message, boolean foundListener) {
|
||||||
|
if (connection.manageRmi(message)) {
|
||||||
|
// if we are an RMI message/registration, we have very specific, defined behavior. We do not use the "normal" listener callback pattern
|
||||||
|
// because these methods are rare, and require special functionality
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
message = connection.fixupRmi(message);
|
||||||
|
|
||||||
|
|
||||||
foundListener |= onMessageReceivedManager.notifyReceived(connection, message, shutdown);
|
foundListener |= onMessageReceivedManager.notifyReceived(connection, message, shutdown);
|
||||||
|
|
||||||
// now have to account for additional connection listener managers (non-global).
|
// now have to account for additional connection listener managers (non-global).
|
||||||
@ -316,23 +319,19 @@ class ConnectionManager<C extends Connection> implements Listeners, ISessionMana
|
|||||||
.flush();
|
.flush();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if (this.logger.isErrorEnabled()) {
|
this.logger.warn("----------- LISTENER NOT REGISTERED FOR TYPE: {}",
|
||||||
this.logger.warn("----------- LISTENER NOT REGISTERED FOR TYPE: {}",
|
message.getClass()
|
||||||
message.getClass()
|
.getSimpleName());
|
||||||
.getSimpleName());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return foundListener;
|
return foundListener;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Invoked when a Connection has been idle for a while.
|
* Invoked when a Connection has been idle for a while.
|
||||||
* <p/>
|
|
||||||
* {@link ISessionManager}
|
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public final
|
public final
|
||||||
void onIdle(final C connection) {
|
void onIdle(final Connection connection) {
|
||||||
boolean foundListener = onIdleManager.notifyIdle(connection, shutdown);
|
boolean foundListener = onIdleManager.notifyIdle(connection, shutdown);
|
||||||
|
|
||||||
if (foundListener) {
|
if (foundListener) {
|
||||||
@ -342,8 +341,8 @@ class ConnectionManager<C extends Connection> implements Listeners, ISessionMana
|
|||||||
|
|
||||||
// now have to account for additional (local) listener managers.
|
// now have to account for additional (local) listener managers.
|
||||||
// access a snapshot of the managers (single-writer-principle)
|
// access a snapshot of the managers (single-writer-principle)
|
||||||
final IdentityMap<Connection, ConnectionManager<C>> localManagers = localManagersREF.get(this);
|
final IdentityMap<Connection, ConnectionManager> localManagers = localManagersREF.get(this);
|
||||||
ConnectionManager<C> localManager = localManagers.get(connection);
|
ConnectionManager localManager = localManagers.get(connection);
|
||||||
if (localManager != null) {
|
if (localManager != null) {
|
||||||
localManager.onIdle(connection);
|
localManager.onIdle(connection);
|
||||||
}
|
}
|
||||||
@ -351,13 +350,10 @@ class ConnectionManager<C extends Connection> implements Listeners, ISessionMana
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Invoked when a Channel is open, bound to a local address, and connected to a remote address.
|
* Invoked when a Channel is open, bound to a local address, and connected to a remote address.
|
||||||
* <p/>
|
|
||||||
* {@link ISessionManager}
|
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("Duplicates")
|
|
||||||
@Override
|
@Override
|
||||||
public
|
public
|
||||||
void onConnected(final C connection) {
|
void onConnected(final Connection connection) {
|
||||||
addConnection(connection);
|
addConnection(connection);
|
||||||
|
|
||||||
boolean foundListener = onConnectedManager.notifyConnected(connection, shutdown);
|
boolean foundListener = onConnectedManager.notifyConnected(connection, shutdown);
|
||||||
@ -369,8 +365,8 @@ class ConnectionManager<C extends Connection> implements Listeners, ISessionMana
|
|||||||
|
|
||||||
// now have to account for additional (local) listener managers.
|
// now have to account for additional (local) listener managers.
|
||||||
// access a snapshot of the managers (single-writer-principle)
|
// access a snapshot of the managers (single-writer-principle)
|
||||||
final IdentityMap<Connection, ConnectionManager<C>> localManagers = localManagersREF.get(this);
|
final IdentityMap<Connection, ConnectionManager> localManagers = localManagersREF.get(this);
|
||||||
ConnectionManager<C> localManager = localManagers.get(connection);
|
ConnectionManager localManager = localManagers.get(connection);
|
||||||
if (localManager != null) {
|
if (localManager != null) {
|
||||||
localManager.onConnected(connection);
|
localManager.onConnected(connection);
|
||||||
}
|
}
|
||||||
@ -378,13 +374,10 @@ class ConnectionManager<C extends Connection> implements Listeners, ISessionMana
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Invoked when a Channel was disconnected from its remote peer.
|
* Invoked when a Channel was disconnected from its remote peer.
|
||||||
* <p/>
|
|
||||||
* {@link ISessionManager}
|
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("Duplicates")
|
|
||||||
@Override
|
@Override
|
||||||
public
|
public
|
||||||
void onDisconnected(final C connection) {
|
void onDisconnected(final Connection connection) {
|
||||||
boolean foundListener = onDisconnectedManager.notifyDisconnected(connection, shutdown);
|
boolean foundListener = onDisconnectedManager.notifyDisconnected(connection, shutdown);
|
||||||
|
|
||||||
if (foundListener) {
|
if (foundListener) {
|
||||||
@ -395,8 +388,8 @@ class ConnectionManager<C extends Connection> implements Listeners, ISessionMana
|
|||||||
// now have to account for additional (local) listener managers.
|
// now have to account for additional (local) listener managers.
|
||||||
|
|
||||||
// access a snapshot of the managers (single-writer-principle)
|
// access a snapshot of the managers (single-writer-principle)
|
||||||
final IdentityMap<Connection, ConnectionManager<C>> localManagers = localManagersREF.get(this);
|
final IdentityMap<Connection, ConnectionManager> localManagers = localManagersREF.get(this);
|
||||||
ConnectionManager<C> localManager = localManagers.get(connection);
|
ConnectionManager localManager = localManagers.get(connection);
|
||||||
if (localManager != null) {
|
if (localManager != null) {
|
||||||
localManager.onDisconnected(connection);
|
localManager.onDisconnected(connection);
|
||||||
|
|
||||||
@ -415,11 +408,11 @@ class ConnectionManager<C extends Connection> implements Listeners, ISessionMana
|
|||||||
*
|
*
|
||||||
* @param connection the connection to add
|
* @param connection the connection to add
|
||||||
*/
|
*/
|
||||||
void addConnection(final C connection) {
|
void addConnection(final Connection connection) {
|
||||||
// synchronized is used here to ensure the "single writer principle", and make sure that ONLY one thread at a time can enter this
|
// synchronized is used here to ensure the "single writer principle", and make sure that ONLY one thread at a time can enter this
|
||||||
// section. Because of this, we can have unlimited reader threads all going at the same time, without contention (which is our
|
// section. Because of this, we can have unlimited reader threads all going at the same time, without contention (which is our
|
||||||
// use-case 99% of the time)
|
// use-case 99% of the time)
|
||||||
synchronized (singleWriterLock2) {
|
synchronized (singleWriterConnectionsLock) {
|
||||||
// access a snapshot of the connections (single-writer-principle)
|
// access a snapshot of the connections (single-writer-principle)
|
||||||
ConcurrentEntry head = connectionsREF.get(this);
|
ConcurrentEntry head = connectionsREF.get(this);
|
||||||
|
|
||||||
@ -442,12 +435,12 @@ class ConnectionManager<C extends Connection> implements Listeners, ISessionMana
|
|||||||
*
|
*
|
||||||
* @param connection the connection to remove
|
* @param connection the connection to remove
|
||||||
*/
|
*/
|
||||||
private
|
public
|
||||||
void removeConnection(C connection) {
|
void removeConnection(Connection connection) {
|
||||||
// synchronized is used here to ensure the "single writer principle", and make sure that ONLY one thread at a time can enter this
|
// synchronized is used here to ensure the "single writer principle", and make sure that ONLY one thread at a time can enter this
|
||||||
// section. Because of this, we can have unlimited reader threads all going at the same time, without contention (which is our
|
// section. Because of this, we can have unlimited reader threads all going at the same time, without contention (which is our
|
||||||
// use-case 99% of the time)
|
// use-case 99% of the time)
|
||||||
synchronized (singleWriterLock2) {
|
synchronized (singleWriterConnectionsLock) {
|
||||||
// access a snapshot of the connections (single-writer-principle)
|
// access a snapshot of the connections (single-writer-principle)
|
||||||
ConcurrentEntry concurrentEntry = connectionEntries.get(connection);
|
ConcurrentEntry concurrentEntry = connectionEntries.get(connection);
|
||||||
|
|
||||||
@ -477,36 +470,34 @@ class ConnectionManager<C extends Connection> implements Listeners, ISessionMana
|
|||||||
@Override
|
@Override
|
||||||
public
|
public
|
||||||
List<C> getConnections() {
|
List<C> getConnections() {
|
||||||
synchronized (singleWriterLock2) {
|
synchronized (singleWriterConnectionsLock) {
|
||||||
final IdentityMap.Keys<C> keys = this.connectionEntries.keys();
|
final IdentityMap.Keys<Connection> keys = this.connectionEntries.keys();
|
||||||
return keys.toArray();
|
return (List<C>) keys.toArray();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
final
|
final
|
||||||
ConnectionManager<C> addListenerManager(final Connection connection) {
|
ConnectionManager addListenerManager(final Connection connection) {
|
||||||
// when we are a server, NORMALLY listeners are added at the GLOBAL level (meaning, I add one listener, and ALL connections
|
// when we are a server, NORMALLY listeners are added at the GLOBAL level (meaning, I add one listener, and ALL connections
|
||||||
// are notified of that listener.
|
// are notified of that listener.
|
||||||
// it is POSSIBLE to add a connection-specific listener (via connection.addListener), meaning that ONLY
|
// it is POSSIBLE to add a connection-specific listener (via connection.addListener), meaning that ONLY
|
||||||
// that listener is notified on that event (ie, admin type listeners)
|
// that listener is notified on that event (ie, admin type listeners)
|
||||||
|
|
||||||
ConnectionManager<C> manager;
|
ConnectionManager manager;
|
||||||
boolean created = false;
|
boolean created = false;
|
||||||
|
|
||||||
|
|
||||||
// synchronized is used here to ensure the "single writer principle", and make sure that ONLY one thread at a time can enter this
|
// synchronized is used here to ensure the "single writer principle", and make sure that ONLY one thread at a time can enter this
|
||||||
// section. Because of this, we can have unlimited reader threads all going at the same time, without contention (which is our
|
// section. Because of this, we can have unlimited reader threads all going at the same time, without contention (which is our
|
||||||
// use-case 99% of the time)
|
// use-case 99% of the time)
|
||||||
synchronized (singleWriterLock3) {
|
synchronized (singleWriterLocalManagerLock) {
|
||||||
// access a snapshot of the managers (single-writer-principle)
|
// access a snapshot of the managers (single-writer-principle)
|
||||||
final IdentityMap<Connection, ConnectionManager<C>> localManagers = localManagersREF.get(this);
|
final IdentityMap<Connection, ConnectionManager> localManagers = localManagersREF.get(this);
|
||||||
|
|
||||||
manager = localManagers.get(connection);
|
manager = localManagers.get(connection);
|
||||||
if (manager == null) {
|
if (manager == null) {
|
||||||
created = true;
|
created = true;
|
||||||
manager = new ConnectionManager<C>(loggerName + "-" + connection.toString() + " Specific",
|
manager = new ConnectionManager(loggerName + "-" + connection.toString() + " Specific", ConnectionManager.this.baseClass);
|
||||||
ConnectionManager.this.baseClass);
|
|
||||||
localManagers.put(connection, manager);
|
localManagers.put(connection, manager);
|
||||||
|
|
||||||
// save this snapshot back to the original (single writer principle)
|
// save this snapshot back to the original (single writer principle)
|
||||||
@ -531,11 +522,11 @@ class ConnectionManager<C extends Connection> implements Listeners, ISessionMana
|
|||||||
// synchronized is used here to ensure the "single writer principle", and make sure that ONLY one thread at a time can enter this
|
// synchronized is used here to ensure the "single writer principle", and make sure that ONLY one thread at a time can enter this
|
||||||
// section. Because of this, we can have unlimited reader threads all going at the same time, without contention (which is our
|
// section. Because of this, we can have unlimited reader threads all going at the same time, without contention (which is our
|
||||||
// use-case 99% of the time)
|
// use-case 99% of the time)
|
||||||
synchronized (singleWriterLock3) {
|
synchronized (singleWriterLocalManagerLock) {
|
||||||
// access a snapshot of the managers (single-writer-principle)
|
// access a snapshot of the managers (single-writer-principle)
|
||||||
final IdentityMap<Connection, ConnectionManager<C>> localManagers = localManagersREF.get(this);
|
final IdentityMap<Connection, ConnectionManager> localManagers = localManagersREF.get(this);
|
||||||
|
|
||||||
final ConnectionManager<C> removed = localManagers.remove(connection);
|
final ConnectionManager removed = localManagers.remove(connection);
|
||||||
if (removed != null) {
|
if (removed != null) {
|
||||||
wasRemoved = true;
|
wasRemoved = true;
|
||||||
|
|
||||||
@ -552,23 +543,6 @@ class ConnectionManager<C extends Connection> implements Listeners, ISessionMana
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* BE CAREFUL! Only for internal use!
|
|
||||||
*
|
|
||||||
* @return Returns a FAST first connection (for client!).
|
|
||||||
*/
|
|
||||||
public final
|
|
||||||
C getConnection0() throws IOException {
|
|
||||||
ConcurrentEntry<C> head1 = connectionsREF.get(this);
|
|
||||||
if (head1 != null) {
|
|
||||||
return head1.getValue();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
throw new IOException("Not connected to a remote computer. Unable to continue!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* BE CAREFUL! Only for internal use!
|
* BE CAREFUL! Only for internal use!
|
||||||
*
|
*
|
||||||
@ -587,7 +561,7 @@ class ConnectionManager<C extends Connection> implements Listeners, ISessionMana
|
|||||||
this.shutdown.set(true);
|
this.shutdown.set(true);
|
||||||
|
|
||||||
// disconnect the sessions
|
// disconnect the sessions
|
||||||
closeConnections();
|
closeConnections(false);
|
||||||
|
|
||||||
onConnectedManager.clear();
|
onConnectedManager.clear();
|
||||||
onDisconnectedManager.clear();
|
onDisconnectedManager.clear();
|
||||||
@ -596,22 +570,31 @@ class ConnectionManager<C extends Connection> implements Listeners, ISessionMana
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Close all connections ONLY
|
* Close all connections ONLY.
|
||||||
|
*
|
||||||
|
* Only keep the listeners for connections IF we are the client. If we remove listeners as a client, ALL of the client logic will
|
||||||
|
* be lost. The server is reactive, so listeners are added to connections as needed (instead of before startup, which is what the client does).
|
||||||
*/
|
*/
|
||||||
final
|
final
|
||||||
void closeConnections() {
|
void closeConnections(boolean keepListeners) {
|
||||||
|
|
||||||
// synchronized is used here to ensure the "single writer principle", and make sure that ONLY one thread at a time can enter this
|
// synchronized is used here to ensure the "single writer principle", and make sure that ONLY one thread at a time can enter this
|
||||||
// section. Because of this, we can have unlimited reader threads all going at the same time, without contention (which is our
|
// section. Because of this, we can have unlimited reader threads all going at the same time, without contention (which is our
|
||||||
// use-case 99% of the time)
|
// use-case 99% of the time)
|
||||||
synchronized (singleWriterLock2) {
|
synchronized (singleWriterConnectionsLock) {
|
||||||
// don't need anything fast or fancy here, because this method will only be called once
|
// don't need anything fast or fancy here, because this method will only be called once
|
||||||
final IdentityMap.Keys<C> keys = connectionEntries.keys();
|
final IdentityMap.Keys<Connection> keys = connectionEntries.keys();
|
||||||
for (C connection : keys) {
|
for (Connection connection : keys) {
|
||||||
// Close the connection. Make sure the close operation ends because
|
// Close the connection. Make sure the close operation ends because
|
||||||
// all I/O operations are asynchronous in Netty.
|
// all I/O operations are asynchronous in Netty.
|
||||||
// Also necessary otherwise workers won't close.
|
// Also necessary otherwise workers won't close.
|
||||||
connection.close();
|
|
||||||
|
|
||||||
|
if (keepListeners && connection instanceof ConnectionImpl) {
|
||||||
|
((ConnectionImpl) connection).close(true);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
connection.close();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.connectionEntries.clear();
|
this.connectionEntries.clear();
|
||||||
@ -636,7 +619,7 @@ class ConnectionManager<C extends Connection> implements Listeners, ISessionMana
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public
|
public
|
||||||
ConnectionExceptSpecifiedBridgeServer<C> except() {
|
ConnectionExceptSpecifiedBridgeServer except() {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -650,8 +633,8 @@ class ConnectionManager<C extends Connection> implements Listeners, ISessionMana
|
|||||||
@Override
|
@Override
|
||||||
public
|
public
|
||||||
void flush() {
|
void flush() {
|
||||||
ConcurrentEntry<C> current = connectionsREF.get(this);
|
ConcurrentEntry<Connection> current = connectionsREF.get(this);
|
||||||
C c;
|
Connection c;
|
||||||
while (current != null) {
|
while (current != null) {
|
||||||
c = current.getValue();
|
c = current.getValue();
|
||||||
current = current.next();
|
current = current.next();
|
||||||
@ -667,9 +650,9 @@ class ConnectionManager<C extends Connection> implements Listeners, ISessionMana
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public
|
public
|
||||||
ConnectionPoint TCP(final C connection, final Object message) {
|
ConnectionPoint TCP(final Connection connection, final Object message) {
|
||||||
ConcurrentEntry<C> current = connectionsREF.get(this);
|
ConcurrentEntry<Connection> current = connectionsREF.get(this);
|
||||||
C c;
|
Connection c;
|
||||||
while (current != null) {
|
while (current != null) {
|
||||||
c = current.getValue();
|
c = current.getValue();
|
||||||
current = current.next();
|
current = current.next();
|
||||||
@ -688,9 +671,9 @@ class ConnectionManager<C extends Connection> implements Listeners, ISessionMana
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public
|
public
|
||||||
ConnectionPoint UDP(final C connection, final Object message) {
|
ConnectionPoint UDP(final Connection connection, final Object message) {
|
||||||
ConcurrentEntry<C> current = connectionsREF.get(this);
|
ConcurrentEntry<Connection> current = connectionsREF.get(this);
|
||||||
C c;
|
Connection c;
|
||||||
while (current != null) {
|
while (current != null) {
|
||||||
c = current.getValue();
|
c = current.getValue();
|
||||||
current = current.next();
|
current = current.next();
|
||||||
@ -709,8 +692,8 @@ class ConnectionManager<C extends Connection> implements Listeners, ISessionMana
|
|||||||
@Override
|
@Override
|
||||||
public
|
public
|
||||||
void self(final Object message) {
|
void self(final Object message) {
|
||||||
ConcurrentEntry<C> current = connectionsREF.get(this);
|
ConcurrentEntry<ConnectionImpl> current = connectionsREF.get(this);
|
||||||
C c;
|
ConnectionImpl c;
|
||||||
while (current != null) {
|
while (current != null) {
|
||||||
c = current.getValue();
|
c = current.getValue();
|
||||||
current = current.next();
|
current = current.next();
|
||||||
@ -725,8 +708,8 @@ class ConnectionManager<C extends Connection> implements Listeners, ISessionMana
|
|||||||
@Override
|
@Override
|
||||||
public
|
public
|
||||||
ConnectionPoint TCP(final Object message) {
|
ConnectionPoint TCP(final Object message) {
|
||||||
ConcurrentEntry<C> current = connectionsREF.get(this);
|
ConcurrentEntry<Connection> current = connectionsREF.get(this);
|
||||||
C c;
|
Connection c;
|
||||||
while (current != null) {
|
while (current != null) {
|
||||||
c = current.getValue();
|
c = current.getValue();
|
||||||
current = current.next();
|
current = current.next();
|
||||||
@ -743,8 +726,8 @@ class ConnectionManager<C extends Connection> implements Listeners, ISessionMana
|
|||||||
@Override
|
@Override
|
||||||
public
|
public
|
||||||
ConnectionPoint UDP(final Object message) {
|
ConnectionPoint UDP(final Object message) {
|
||||||
ConcurrentEntry<C> current = connectionsREF.get(this);
|
ConcurrentEntry<Connection> current = connectionsREF.get(this);
|
||||||
C c;
|
Connection c;
|
||||||
while (current != null) {
|
while (current != null) {
|
||||||
c = current.getValue();
|
c = current.getValue();
|
||||||
current = current.next();
|
current = current.next();
|
||||||
|
@ -15,7 +15,6 @@
|
|||||||
*/
|
*/
|
||||||
package dorkbox.network.connection;
|
package dorkbox.network.connection;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
@ -36,13 +35,15 @@ import dorkbox.network.connection.wrapper.ChannelWrapper;
|
|||||||
import dorkbox.network.pipeline.KryoEncoder;
|
import dorkbox.network.pipeline.KryoEncoder;
|
||||||
import dorkbox.network.pipeline.KryoEncoderCrypto;
|
import dorkbox.network.pipeline.KryoEncoderCrypto;
|
||||||
import dorkbox.network.rmi.RmiBridge;
|
import dorkbox.network.rmi.RmiBridge;
|
||||||
|
import dorkbox.network.rmi.RmiObjectHandler;
|
||||||
|
import dorkbox.network.rmi.RmiObjectLocalHandler;
|
||||||
|
import dorkbox.network.rmi.RmiObjectNetworkHandler;
|
||||||
import dorkbox.network.serialization.Serialization;
|
import dorkbox.network.serialization.Serialization;
|
||||||
import dorkbox.network.store.NullSettingsStore;
|
import dorkbox.network.store.NullSettingsStore;
|
||||||
import dorkbox.network.store.SettingsStore;
|
import dorkbox.network.store.SettingsStore;
|
||||||
import dorkbox.util.Property;
|
import dorkbox.util.Property;
|
||||||
import dorkbox.util.crypto.CryptoECC;
|
import dorkbox.util.crypto.CryptoECC;
|
||||||
import dorkbox.util.entropy.Entropy;
|
import dorkbox.util.entropy.Entropy;
|
||||||
import dorkbox.util.exceptions.InitializationException;
|
|
||||||
import dorkbox.util.exceptions.SecurityException;
|
import dorkbox.util.exceptions.SecurityException;
|
||||||
import io.netty.util.NetUtil;
|
import io.netty.util.NetUtil;
|
||||||
|
|
||||||
@ -50,7 +51,7 @@ import io.netty.util.NetUtil;
|
|||||||
* represents the base of a client/server end point
|
* represents the base of a client/server end point
|
||||||
*/
|
*/
|
||||||
public abstract
|
public abstract
|
||||||
class EndPointBase<C extends Connection> extends EndPoint {
|
class EndPointBase extends EndPoint {
|
||||||
// If TCP and UDP both fill the pipe, THERE WILL BE FRAGMENTATION and dropped UDP packets!
|
// If TCP and UDP both fill the pipe, THERE WILL BE FRAGMENTATION and dropped UDP packets!
|
||||||
// it results in severe UDP packet loss and contention.
|
// it results in severe UDP packet loss and contention.
|
||||||
//
|
//
|
||||||
@ -89,14 +90,19 @@ class EndPointBase<C extends Connection> extends EndPoint {
|
|||||||
@Property
|
@Property
|
||||||
public static int udpMaxSize = 508;
|
public static int udpMaxSize = 508;
|
||||||
|
|
||||||
protected final ConnectionManager<C> connectionManager;
|
protected final ConnectionManager connectionManager;
|
||||||
protected final dorkbox.network.serialization.CryptoSerializationManager serializationManager;
|
protected final dorkbox.network.serialization.CryptoSerializationManager serializationManager;
|
||||||
protected final RegistrationWrapper<C> registrationWrapper;
|
protected final RegistrationWrapper registrationWrapper;
|
||||||
|
|
||||||
final ECPrivateKeyParameters privateKey;
|
final ECPrivateKeyParameters privateKey;
|
||||||
final ECPublicKeyParameters publicKey;
|
final ECPublicKeyParameters publicKey;
|
||||||
|
|
||||||
final SecureRandom secureRandom;
|
final SecureRandom secureRandom;
|
||||||
|
|
||||||
|
// we only want one instance of these created. These will be called appropriately
|
||||||
|
private final RmiObjectHandler rmiHandler;
|
||||||
|
private final RmiObjectLocalHandler localRmiHandler;
|
||||||
|
private final RmiObjectNetworkHandler networkRmiHandler;
|
||||||
final RmiBridge globalRmiBridge;
|
final RmiBridge globalRmiBridge;
|
||||||
|
|
||||||
private final Executor rmiExecutor;
|
private final Executor rmiExecutor;
|
||||||
@ -117,12 +123,10 @@ class EndPointBase<C extends Connection> extends EndPoint {
|
|||||||
* @param type this is either "Client" or "Server", depending on who is creating this endpoint.
|
* @param type this is either "Client" or "Server", depending on who is creating this endpoint.
|
||||||
* @param config these are the specific connection options
|
* @param config these are the specific connection options
|
||||||
*
|
*
|
||||||
* @throws InitializationException
|
* @throws SecurityException if unable to initialize/generate ECC keys
|
||||||
* @throws SecurityException
|
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
|
||||||
public
|
public
|
||||||
EndPointBase(Class<? extends EndPointBase> type, final Configuration config) throws InitializationException, SecurityException, IOException {
|
EndPointBase(Class<? extends EndPointBase> type, final Configuration config) throws SecurityException {
|
||||||
super(type);
|
super(type);
|
||||||
|
|
||||||
// make sure that 'localhost' is ALWAYS our specific loopback IP address
|
// make sure that 'localhost' is ALWAYS our specific loopback IP address
|
||||||
@ -193,7 +197,7 @@ class EndPointBase<C extends Connection> extends EndPoint {
|
|||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
String message = "Unable to initialize/generate ECC keys. FORCED SHUTDOWN.";
|
String message = "Unable to initialize/generate ECC keys. FORCED SHUTDOWN.";
|
||||||
logger.error(message);
|
logger.error(message);
|
||||||
throw new InitializationException(message);
|
throw new SecurityException(message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -209,17 +213,22 @@ class EndPointBase<C extends Connection> extends EndPoint {
|
|||||||
secureRandom = new SecureRandom(propertyStore.getSalt());
|
secureRandom = new SecureRandom(propertyStore.getSalt());
|
||||||
|
|
||||||
// we don't care about un-instantiated/constructed members, since the class type is the only interest.
|
// we don't care about un-instantiated/constructed members, since the class type is the only interest.
|
||||||
connectionManager = new ConnectionManager<C>(type.getSimpleName(), connection0(null).getClass());
|
//noinspection unchecked
|
||||||
|
connectionManager = new ConnectionManager(type.getSimpleName(), connection0(null).getClass());
|
||||||
|
|
||||||
// add the ping listener (internal use only!)
|
// add the ping listener (internal use only!)
|
||||||
connectionManager.add(new PingSystemListener());
|
connectionManager.add(new PingSystemListener());
|
||||||
|
|
||||||
if (rmiEnabled) {
|
if (rmiEnabled) {
|
||||||
// these register the listener for registering a class implementation for RMI (internal use only)
|
rmiHandler = null;
|
||||||
connectionManager.add(new RegisterRmiNetworkHandler());
|
localRmiHandler = new RmiObjectLocalHandler();
|
||||||
|
networkRmiHandler = new RmiObjectNetworkHandler();
|
||||||
globalRmiBridge = new RmiBridge(logger, config.rmiExecutor, true);
|
globalRmiBridge = new RmiBridge(logger, config.rmiExecutor, true);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
rmiHandler = new RmiObjectHandler();
|
||||||
|
localRmiHandler = null;
|
||||||
|
networkRmiHandler = null;
|
||||||
globalRmiBridge = null;
|
globalRmiBridge = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -318,7 +327,6 @@ class EndPointBase<C extends Connection> extends EndPoint {
|
|||||||
*
|
*
|
||||||
* @param metaChannel can be NULL (when getting the baseClass)
|
* @param metaChannel can be NULL (when getting the baseClass)
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
protected final
|
protected final
|
||||||
Connection connection0(MetaChannel metaChannel) {
|
Connection connection0(MetaChannel metaChannel) {
|
||||||
ConnectionImpl connection;
|
ConnectionImpl connection;
|
||||||
@ -332,31 +340,35 @@ class EndPointBase<C extends Connection> extends EndPoint {
|
|||||||
// These properties are ASSIGNED in the same thread that CREATED the object. Only the AES info needs to be
|
// These properties are ASSIGNED in the same thread that CREATED the object. Only the AES info needs to be
|
||||||
// volatile since it is the only thing that changes.
|
// volatile since it is the only thing that changes.
|
||||||
if (metaChannel != null) {
|
if (metaChannel != null) {
|
||||||
ChannelWrapper<C> wrapper;
|
ChannelWrapper wrapper;
|
||||||
|
|
||||||
connection = newConnection(logger, this, rmiBridge);
|
connection = newConnection(logger, this, rmiBridge);
|
||||||
metaChannel.connection = connection;
|
metaChannel.connection = connection;
|
||||||
|
|
||||||
if (metaChannel.localChannel != null) {
|
if (metaChannel.localChannel != null) {
|
||||||
wrapper = new ChannelLocalWrapper(metaChannel);
|
if (rmiEnabled) {
|
||||||
}
|
wrapper = new ChannelLocalWrapper(metaChannel, localRmiHandler);
|
||||||
else {
|
|
||||||
if (this instanceof EndPointServer) {
|
|
||||||
wrapper = new ChannelNetworkWrapper(metaChannel, registrationWrapper);
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
wrapper = new ChannelNetworkWrapper(metaChannel, null);
|
wrapper = new ChannelLocalWrapper(metaChannel, rmiHandler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
RmiObjectHandler rmiObjectHandler = rmiHandler;
|
||||||
|
if (rmiEnabled) {
|
||||||
|
rmiObjectHandler = networkRmiHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this instanceof EndPointServer) {
|
||||||
|
wrapper = new ChannelNetworkWrapper(metaChannel, registrationWrapper, rmiObjectHandler);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
wrapper = new ChannelNetworkWrapper(metaChannel, null, rmiObjectHandler);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// now initialize the connection channels with whatever extra info they might need.
|
// now initialize the connection channels with whatever extra info they might need.
|
||||||
connection.init(wrapper, (ConnectionManager<Connection>) connectionManager);
|
connection.init(wrapper, connectionManager);
|
||||||
|
|
||||||
if (rmiBridge != null) {
|
|
||||||
// notify our remote object space that it is able to receive method calls.
|
|
||||||
connection.listeners()
|
|
||||||
.add(rmiBridge.getListener());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// getting the connection baseClass
|
// getting the connection baseClass
|
||||||
@ -374,14 +386,13 @@ class EndPointBase<C extends Connection> extends EndPoint {
|
|||||||
* <p/>
|
* <p/>
|
||||||
* Only the CLIENT injects in front of this)
|
* Only the CLIENT injects in front of this)
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
void connectionConnected0(ConnectionImpl connection) {
|
void connectionConnected0(ConnectionImpl connection) {
|
||||||
isConnected.set(true);
|
isConnected.set(true);
|
||||||
|
|
||||||
// prep the channel wrapper
|
// prep the channel wrapper
|
||||||
connection.prep();
|
connection.prep();
|
||||||
|
|
||||||
connectionManager.onConnected((C) connection);
|
connectionManager.onConnected(connection);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -396,7 +407,7 @@ class EndPointBase<C extends Connection> extends EndPoint {
|
|||||||
* Returns a non-modifiable list of active connections
|
* Returns a non-modifiable list of active connections
|
||||||
*/
|
*/
|
||||||
public
|
public
|
||||||
List<C> getConnections() {
|
<C extends Connection> List<C> getConnections() {
|
||||||
return connectionManager.getConnections();
|
return connectionManager.getConnections();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -413,13 +424,13 @@ class EndPointBase<C extends Connection> extends EndPoint {
|
|||||||
* <p/>
|
* <p/>
|
||||||
* The server should ALWAYS use STOP.
|
* The server should ALWAYS use STOP.
|
||||||
*/
|
*/
|
||||||
public
|
void closeConnections(boolean shouldKeepListeners) {
|
||||||
void closeConnections() {
|
|
||||||
// give a chance to other threads.
|
// give a chance to other threads.
|
||||||
Thread.yield();
|
Thread.yield();
|
||||||
|
|
||||||
// stop does the same as this + more
|
// stop does the same as this + more. Only keep the listeners for connections IF we are the client. If we remove listeners as a client,
|
||||||
connectionManager.closeConnections();
|
// ALL of the client logic will be lost. The server is reactive, so listeners are added to connections as needed (instead of before startup)
|
||||||
|
connectionManager.closeConnections(shouldKeepListeners);
|
||||||
|
|
||||||
// Sometimes there might be "lingering" connections (ie, halfway though registration) that need to be closed.
|
// Sometimes there might be "lingering" connections (ie, halfway though registration) that need to be closed.
|
||||||
registrationWrapper.closeChannels(maxShutdownWaitTimeInMilliSeconds);
|
registrationWrapper.closeChannels(maxShutdownWaitTimeInMilliSeconds);
|
||||||
@ -442,7 +453,7 @@ class EndPointBase<C extends Connection> extends EndPoint {
|
|||||||
@Override
|
@Override
|
||||||
protected
|
protected
|
||||||
void shutdownChannelsPre() {
|
void shutdownChannelsPre() {
|
||||||
closeConnections();
|
closeConnections(false);
|
||||||
|
|
||||||
// this does a closeConnections + clear_listeners
|
// this does a closeConnections + clear_listeners
|
||||||
connectionManager.stop();
|
connectionManager.stop();
|
||||||
@ -465,7 +476,6 @@ class EndPointBase<C extends Connection> extends EndPoint {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("rawtypes")
|
|
||||||
@Override
|
@Override
|
||||||
public
|
public
|
||||||
boolean equals(Object obj) {
|
boolean equals(Object obj) {
|
||||||
|
@ -25,7 +25,6 @@ import java.util.concurrent.TimeUnit;
|
|||||||
import dorkbox.network.Client;
|
import dorkbox.network.Client;
|
||||||
import dorkbox.network.Configuration;
|
import dorkbox.network.Configuration;
|
||||||
import dorkbox.network.connection.bridge.ConnectionBridge;
|
import dorkbox.network.connection.bridge.ConnectionBridge;
|
||||||
import dorkbox.util.exceptions.InitializationException;
|
|
||||||
import dorkbox.util.exceptions.SecurityException;
|
import dorkbox.util.exceptions.SecurityException;
|
||||||
import io.netty.channel.ChannelFuture;
|
import io.netty.channel.ChannelFuture;
|
||||||
import io.netty.channel.ChannelOption;
|
import io.netty.channel.ChannelOption;
|
||||||
@ -34,9 +33,10 @@ import io.netty.channel.ChannelOption;
|
|||||||
* This serves the purpose of making sure that specific methods are not available to the end user.
|
* This serves the purpose of making sure that specific methods are not available to the end user.
|
||||||
*/
|
*/
|
||||||
public
|
public
|
||||||
class EndPointClient<C extends Connection> extends EndPointBase<C> {
|
class EndPointClient extends EndPointBase {
|
||||||
|
|
||||||
protected C connection;
|
// is valid when there is a connection to the server, otherwise it is null
|
||||||
|
protected volatile Connection connection;
|
||||||
|
|
||||||
private CountDownLatch registration;
|
private CountDownLatch registration;
|
||||||
|
|
||||||
@ -51,7 +51,7 @@ class EndPointClient<C extends Connection> extends EndPointBase<C> {
|
|||||||
|
|
||||||
|
|
||||||
public
|
public
|
||||||
EndPointClient(Configuration config) throws InitializationException, SecurityException, IOException {
|
EndPointClient(Configuration config) throws SecurityException {
|
||||||
super(Client.class, config);
|
super(Client.class, config);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -211,7 +211,7 @@ class EndPointClient<C extends Connection> extends EndPointBase<C> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
//noinspection unchecked
|
//noinspection unchecked
|
||||||
this.connection = (C) connection;
|
this.connection = connection;
|
||||||
|
|
||||||
synchronized (bootstrapLock) {
|
synchronized (bootstrapLock) {
|
||||||
// we're done with registration, so no need to keep this around
|
// we're done with registration, so no need to keep this around
|
||||||
@ -251,16 +251,19 @@ class EndPointClient<C extends Connection> extends EndPointBase<C> {
|
|||||||
* <p/>
|
* <p/>
|
||||||
* This is used, for example, when reconnecting to a server.
|
* This is used, for example, when reconnecting to a server.
|
||||||
*/
|
*/
|
||||||
@Override
|
|
||||||
public
|
public
|
||||||
void closeConnections() {
|
void closeConnections() {
|
||||||
super.closeConnections();
|
// Only keep the listeners for connections IF we are the client. If we remove listeners as a client,
|
||||||
|
// ALL of the client logic will be lost. The server is reactive, so listeners are added to connections as needed (instead of before startup)
|
||||||
|
closeConnections(true);
|
||||||
|
|
||||||
// make sure we're not waiting on registration
|
// make sure we're not waiting on registration
|
||||||
registrationCompleted();
|
registrationCompleted();
|
||||||
|
|
||||||
// for the CLIENT only, we clear these connections! (the server only clears them on shutdown)
|
// for the CLIENT only, we clear these connections! (the server only clears them on shutdown)
|
||||||
shutdownChannels();
|
shutdownChannels();
|
||||||
|
|
||||||
|
connection = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -15,22 +15,19 @@
|
|||||||
*/
|
*/
|
||||||
package dorkbox.network.connection;
|
package dorkbox.network.connection;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
import dorkbox.network.Configuration;
|
import dorkbox.network.Configuration;
|
||||||
import dorkbox.network.Server;
|
import dorkbox.network.Server;
|
||||||
import dorkbox.network.connection.bridge.ConnectionBridgeServer;
|
import dorkbox.network.connection.bridge.ConnectionBridgeServer;
|
||||||
import dorkbox.util.exceptions.InitializationException;
|
|
||||||
import dorkbox.util.exceptions.SecurityException;
|
import dorkbox.util.exceptions.SecurityException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This serves the purpose of making sure that specific methods are not available to the end user.
|
* This serves the purpose of making sure that specific methods are not available to the end user.
|
||||||
*/
|
*/
|
||||||
public
|
public
|
||||||
class EndPointServer<C extends Connection> extends EndPointBase<C> {
|
class EndPointServer extends EndPointBase {
|
||||||
|
|
||||||
public
|
public
|
||||||
EndPointServer(final Configuration config) throws InitializationException, SecurityException, IOException {
|
EndPointServer(final Configuration config) throws SecurityException {
|
||||||
super(Server.class, config);
|
super(Server.class, config);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -39,7 +36,7 @@ class EndPointServer<C extends Connection> extends EndPointBase<C> {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public
|
public
|
||||||
ConnectionBridgeServer<C> send() {
|
ConnectionBridgeServer send() {
|
||||||
return this.connectionManager;
|
return this.connectionManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -53,7 +50,7 @@ class EndPointServer<C extends Connection> extends EndPointBase<C> {
|
|||||||
* @return a newly created listener manager for the connection
|
* @return a newly created listener manager for the connection
|
||||||
*/
|
*/
|
||||||
final
|
final
|
||||||
ConnectionManager<C> addListenerManager(final C connection) {
|
ConnectionManager addListenerManager(final Connection connection) {
|
||||||
return this.connectionManager.addListenerManager(connection);
|
return this.connectionManager.addListenerManager(connection);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -67,7 +64,7 @@ class EndPointServer<C extends Connection> extends EndPointBase<C> {
|
|||||||
* This removes the listener manager for that specific connection
|
* This removes the listener manager for that specific connection
|
||||||
*/
|
*/
|
||||||
final
|
final
|
||||||
void removeListenerManager(final C connection) {
|
void removeListenerManager(final Connection connection) {
|
||||||
this.connectionManager.removeListenerManager(connection);
|
this.connectionManager.removeListenerManager(connection);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -80,7 +77,7 @@ class EndPointServer<C extends Connection> extends EndPointBase<C> {
|
|||||||
* @param connection the connection to add
|
* @param connection the connection to add
|
||||||
*/
|
*/
|
||||||
public
|
public
|
||||||
void add(C connection) {
|
void add(Connection connection) {
|
||||||
connectionManager.addConnection(connection);
|
connectionManager.addConnection(connection);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -93,7 +90,7 @@ class EndPointServer<C extends Connection> extends EndPointBase<C> {
|
|||||||
* @param connection the connection to remove
|
* @param connection the connection to remove
|
||||||
*/
|
*/
|
||||||
public
|
public
|
||||||
void remove(C connection) {
|
void remove(Connection connection) {
|
||||||
connectionManager.addConnection(connection);
|
connectionManager.removeConnection(connection);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,24 +18,29 @@ package dorkbox.network.connection;
|
|||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
|
||||||
public
|
public
|
||||||
interface ISessionManager<C extends Connection> {
|
interface ISessionManager {
|
||||||
/**
|
/**
|
||||||
* Called when a message is received
|
* Called when a message is received
|
||||||
*/
|
*/
|
||||||
void onMessage(C connection, Object message);
|
void onMessage(ConnectionImpl connection, Object message);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the connection has been idle (read & write) for 2 seconds
|
* Called when the connection has been idle (read & write) for 2 seconds
|
||||||
*/
|
*/
|
||||||
void onIdle(C connection);
|
void onIdle(Connection connection);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoked when a Channel is open, bound to a local address, and connected to a remote address.
|
||||||
|
*/
|
||||||
|
void onConnected(Connection connection);
|
||||||
|
|
||||||
void onConnected(C connection);
|
/**
|
||||||
|
* Invoked when a Channel was disconnected from its remote peer.
|
||||||
void onDisconnected(C connection);
|
*/
|
||||||
|
void onDisconnected(Connection connection);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a non-modifiable list of active connections. This is extremely slow, and not recommended!
|
* Returns a non-modifiable list of active connections. This is extremely slow, and not recommended!
|
||||||
*/
|
*/
|
||||||
Collection<C> getConnections();
|
<C extends Connection> Collection<C> getConnections();
|
||||||
}
|
}
|
||||||
|
@ -47,13 +47,13 @@ import io.netty.channel.ChannelPipeline;
|
|||||||
* This is in the connection package, so it can access the endpoint methods that it needs to (without having to publicly expose them)
|
* This is in the connection package, so it can access the endpoint methods that it needs to (without having to publicly expose them)
|
||||||
*/
|
*/
|
||||||
public
|
public
|
||||||
class RegistrationWrapper<C extends Connection> implements UdpServer {
|
class RegistrationWrapper implements UdpServer {
|
||||||
private final org.slf4j.Logger logger;
|
private final org.slf4j.Logger logger;
|
||||||
|
|
||||||
private final KryoEncoder kryoEncoder;
|
private final KryoEncoder kryoEncoder;
|
||||||
private final KryoEncoderCrypto kryoEncoderCrypto;
|
private final KryoEncoderCrypto kryoEncoderCrypto;
|
||||||
|
|
||||||
private final EndPointBase<C> endPointBaseConnection;
|
private final EndPointBase endPointBaseConnection;
|
||||||
|
|
||||||
// keeps track of connections (TCP/UDP-client)
|
// keeps track of connections (TCP/UDP-client)
|
||||||
private final ReentrantLock channelMapLock = new ReentrantLock();
|
private final ReentrantLock channelMapLock = new ReentrantLock();
|
||||||
@ -77,7 +77,7 @@ class RegistrationWrapper<C extends Connection> implements UdpServer {
|
|||||||
|
|
||||||
|
|
||||||
public
|
public
|
||||||
RegistrationWrapper(final EndPointBase<C> endPointBaseConnection,
|
RegistrationWrapper(final EndPointBase endPointBaseConnection,
|
||||||
final Logger logger,
|
final Logger logger,
|
||||||
final KryoEncoder kryoEncoder,
|
final KryoEncoder kryoEncoder,
|
||||||
final KryoEncoderCrypto kryoEncoderCrypto) {
|
final KryoEncoderCrypto kryoEncoderCrypto) {
|
||||||
@ -94,14 +94,6 @@ class RegistrationWrapper<C extends Connection> implements UdpServer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @return true if RMI is enabled
|
|
||||||
*/
|
|
||||||
public
|
|
||||||
boolean rmiEnabled() {
|
|
||||||
return endPointBaseConnection.globalRmiBridge != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public
|
public
|
||||||
KryoEncoder getKryoEncoder() {
|
KryoEncoder getKryoEncoder() {
|
||||||
return this.kryoEncoder;
|
return this.kryoEncoder;
|
||||||
@ -161,8 +153,7 @@ class RegistrationWrapper<C extends Connection> implements UdpServer {
|
|||||||
/**
|
/**
|
||||||
* Internal call by the pipeline when: - creating a new network connection - when determining the baseClass for generics
|
* Internal call by the pipeline when: - creating a new network connection - when determining the baseClass for generics
|
||||||
*
|
*
|
||||||
* @param metaChannel
|
* @param metaChannel can be NULL (when getting the baseClass)
|
||||||
* can be NULL (when getting the baseClass)
|
|
||||||
*/
|
*/
|
||||||
public
|
public
|
||||||
Connection connection0(MetaChannel metaChannel) {
|
Connection connection0(MetaChannel metaChannel) {
|
||||||
@ -317,7 +308,7 @@ class RegistrationWrapper<C extends Connection> implements UdpServer {
|
|||||||
public
|
public
|
||||||
void abortRegistrationIfClient() {
|
void abortRegistrationIfClient() {
|
||||||
if (this.endPointBaseConnection instanceof EndPointClient) {
|
if (this.endPointBaseConnection instanceof EndPointClient) {
|
||||||
((EndPointClient<C>) this.endPointBaseConnection).abortRegistration();
|
((EndPointClient) this.endPointBaseConnection).abortRegistration();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -411,7 +402,7 @@ class RegistrationWrapper<C extends Connection> implements UdpServer {
|
|||||||
* they will be ADDED in another map, in the followup handler!!
|
* they will be ADDED in another map, in the followup handler!!
|
||||||
*/
|
*/
|
||||||
public
|
public
|
||||||
boolean setupChannels(final RegistrationRemoteHandler<C> handler, final MetaChannel metaChannel) {
|
boolean setupChannels(final RegistrationRemoteHandler handler, final MetaChannel metaChannel) {
|
||||||
boolean registerServer = false;
|
boolean registerServer = false;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -15,14 +15,12 @@
|
|||||||
*/
|
*/
|
||||||
package dorkbox.network.connection.bridge;
|
package dorkbox.network.connection.bridge;
|
||||||
|
|
||||||
import dorkbox.network.connection.Connection;
|
|
||||||
|
|
||||||
public
|
public
|
||||||
interface ConnectionBridgeServer<C extends Connection> extends ConnectionBridgeBase {
|
interface ConnectionBridgeServer extends ConnectionBridgeBase {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Exposes methods to send the object to all server connections (except the specified one) over the network. (or via LOCAL when it's a
|
* Exposes methods to send the object to all server connections (except the specified one) over the network. (or via LOCAL when it's a
|
||||||
* local channel).
|
* local channel).
|
||||||
*/
|
*/
|
||||||
ConnectionExceptSpecifiedBridgeServer<C> except();
|
ConnectionExceptSpecifiedBridgeServer except();
|
||||||
}
|
}
|
||||||
|
@ -19,17 +19,17 @@ import dorkbox.network.connection.Connection;
|
|||||||
import dorkbox.network.connection.ConnectionPoint;
|
import dorkbox.network.connection.ConnectionPoint;
|
||||||
|
|
||||||
public
|
public
|
||||||
interface ConnectionExceptSpecifiedBridgeServer<C extends Connection> {
|
interface ConnectionExceptSpecifiedBridgeServer {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sends the object to all server connections (except the specified one) over the network using TCP. (or via LOCAL when it's a local
|
* Sends the object to all server connections (except the specified one) over the network using TCP. (or via LOCAL when it's a local
|
||||||
* channel).
|
* channel).
|
||||||
*/
|
*/
|
||||||
ConnectionPoint TCP(C connection, Object message);
|
ConnectionPoint TCP(Connection connection, Object message);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sends the object to all server connections (except the specified one) over the network using UDP (or via LOCAL when it's a local
|
* Sends the object to all server connections (except the specified one) over the network using UDP (or via LOCAL when it's a local
|
||||||
* channel).
|
* channel).
|
||||||
*/
|
*/
|
||||||
ConnectionPoint UDP(C connection, Object message);
|
ConnectionPoint UDP(Connection connection, Object message);
|
||||||
}
|
}
|
||||||
|
@ -15,16 +15,18 @@
|
|||||||
*/
|
*/
|
||||||
package dorkbox.network.connection.listenerManagement;
|
package dorkbox.network.connection.listenerManagement;
|
||||||
|
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
|
||||||
import com.esotericsoftware.kryo.util.IdentityMap;
|
import com.esotericsoftware.kryo.util.IdentityMap;
|
||||||
|
|
||||||
import dorkbox.network.connection.Connection;
|
import dorkbox.network.connection.Connection;
|
||||||
import dorkbox.network.connection.ConnectionManager;
|
import dorkbox.network.connection.ConnectionManager;
|
||||||
import dorkbox.network.connection.Listener.OnConnected;
|
import dorkbox.network.connection.Listener.OnConnected;
|
||||||
import dorkbox.network.connection.Listener.OnError;
|
import dorkbox.network.connection.Listener.OnError;
|
||||||
import dorkbox.util.collections.ConcurrentEntry;
|
import dorkbox.util.collections.ConcurrentEntry;
|
||||||
import org.slf4j.Logger;
|
|
||||||
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
|
||||||
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the remote end has been connected. This will be invoked before any objects are received by the network.
|
* Called when the remote end has been connected. This will be invoked before any objects are received by the network.
|
||||||
@ -33,93 +35,84 @@ import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
|
|||||||
*/
|
*/
|
||||||
@SuppressWarnings("Duplicates")
|
@SuppressWarnings("Duplicates")
|
||||||
public final
|
public final
|
||||||
class OnConnectedManager<C extends Connection> {
|
class OnConnectedManager {
|
||||||
|
// Recommended for best performance while adhering to the "single writer principle". Must be static-final
|
||||||
|
private static final AtomicReferenceFieldUpdater<OnConnectedManager, ConcurrentEntry> REF = AtomicReferenceFieldUpdater.newUpdater(
|
||||||
|
OnConnectedManager.class,
|
||||||
|
ConcurrentEntry.class,
|
||||||
|
"head_");
|
||||||
|
|
||||||
private final Logger logger;
|
private final Logger logger;
|
||||||
|
|
||||||
//
|
//
|
||||||
// The iterators for IdentityMap are NOT THREAD SAFE!
|
// The iterators for IdentityMap are NOT THREAD SAFE!
|
||||||
//
|
//
|
||||||
// This is only touched by a single thread, maintains a map of entries for FAST lookup during listener remove.
|
// This is only touched by a single thread, maintains a map of entries for FAST lookup during listener remove.
|
||||||
private final IdentityMap<OnConnected<C>, ConcurrentEntry> entries = new IdentityMap<OnConnected<C>, ConcurrentEntry>(32, ConnectionManager.LOAD_FACTOR);
|
private final IdentityMap<OnConnected, ConcurrentEntry> entries = new IdentityMap<OnConnected, ConcurrentEntry>(32,
|
||||||
private volatile ConcurrentEntry<OnConnected<C>> head = null; // reference to the first element
|
ConnectionManager.LOAD_FACTOR);
|
||||||
|
private volatile ConcurrentEntry<OnConnected> head_ = null; // reference to the first element
|
||||||
|
|
||||||
// synchronized is used here to ensure the "single writer principle", and make sure that ONLY one thread at a time can enter this
|
// synchronized is used here to ensure the "single writer principle", and make sure that ONLY one thread at a time can enter this
|
||||||
// section. Because of this, we can have unlimited reader threads all going at the same time, without contention (which is our
|
// section. Because of this, we can have unlimited reader threads all going at the same time, without contention (which is our
|
||||||
// use-case 99% of the time)
|
// use-case 99% of the time)
|
||||||
private final Object lock = new Object();
|
|
||||||
|
|
||||||
// Recommended for best performance while adhering to the "single writer principle". Must be static-final
|
|
||||||
private static final AtomicReferenceFieldUpdater<OnConnectedManager, ConcurrentEntry> REF =
|
|
||||||
AtomicReferenceFieldUpdater.newUpdater(OnConnectedManager.class,
|
|
||||||
ConcurrentEntry.class,
|
|
||||||
"head");
|
|
||||||
|
|
||||||
public
|
public
|
||||||
OnConnectedManager(final Logger logger) {
|
OnConnectedManager(final Logger logger) {
|
||||||
this.logger = logger;
|
this.logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void add(final OnConnected<C> listener) {
|
public synchronized
|
||||||
// synchronized is used here to ensure the "single writer principle", and make sure that ONLY one thread at a time can enter this
|
void add(final OnConnected listener) {
|
||||||
// section. Because of this, we can have unlimited reader threads all going at the same time, without contention (which is our
|
// access a snapshot (single-writer-principle)
|
||||||
// use-case 99% of the time)
|
ConcurrentEntry head = REF.get(this);
|
||||||
synchronized (lock) {
|
|
||||||
// access a snapshot (single-writer-principle)
|
|
||||||
ConcurrentEntry head = REF.get(this);
|
|
||||||
|
|
||||||
if (!entries.containsKey(listener)) {
|
if (!entries.containsKey(listener)) {
|
||||||
head = new ConcurrentEntry<Object>(listener, head);
|
head = new ConcurrentEntry<Object>(listener, head);
|
||||||
|
|
||||||
entries.put(listener, head);
|
entries.put(listener, head);
|
||||||
|
|
||||||
// save this snapshot back to the original (single writer principle)
|
// save this snapshot back to the original (single writer principle)
|
||||||
REF.lazySet(this, head);
|
REF.lazySet(this, head);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return true if the listener was removed, false otherwise
|
* @return true if the listener was removed, false otherwise
|
||||||
*/
|
*/
|
||||||
public
|
public synchronized
|
||||||
boolean remove(final OnConnected<C> listener) {
|
boolean remove(final OnConnected listener) {
|
||||||
// synchronized is used here to ensure the "single writer principle", and make sure that ONLY one thread at a time can enter this
|
// access a snapshot (single-writer-principle)
|
||||||
// section. Because of this, we can have unlimited reader threads all going at the same time, without contention (which is our
|
ConcurrentEntry concurrentEntry = entries.get(listener);
|
||||||
// use-case 99% of the time)
|
|
||||||
synchronized (lock) {
|
|
||||||
// access a snapshot (single-writer-principle)
|
|
||||||
ConcurrentEntry concurrentEntry = entries.get(listener);
|
|
||||||
|
|
||||||
if (concurrentEntry != null) {
|
if (concurrentEntry != null) {
|
||||||
ConcurrentEntry head1 = REF.get(this);
|
ConcurrentEntry head = REF.get(this);
|
||||||
|
|
||||||
if (concurrentEntry == head1) {
|
if (concurrentEntry == head) {
|
||||||
// if it was second, now it's first
|
// if it was second, now it's first
|
||||||
head1 = head1.next();
|
head = head.next();
|
||||||
//oldHead.clear(); // optimize for GC not possible because of potentially running iterators
|
//oldHead.clear(); // optimize for GC not possible because of potentially running iterators
|
||||||
}
|
|
||||||
else {
|
|
||||||
concurrentEntry.remove();
|
|
||||||
}
|
|
||||||
|
|
||||||
// save this snapshot back to the original (single writer principle)
|
|
||||||
REF.lazySet(this, head1);
|
|
||||||
entries.remove(listener);
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
concurrentEntry.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
// save this snapshot back to the original (single writer principle)
|
||||||
|
REF.lazySet(this, head);
|
||||||
|
entries.remove(listener);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return true if a listener was found, false otherwise
|
* @return true if a listener was found, false otherwise
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public
|
public
|
||||||
boolean notifyConnected(final C connection, final AtomicBoolean shutdown) {
|
<C extends Connection> boolean notifyConnected(final C connection, final AtomicBoolean shutdown) {
|
||||||
ConcurrentEntry<OnConnected<C>> head = REF.get(this);
|
ConcurrentEntry<OnConnected<C>> head = REF.get(this);
|
||||||
ConcurrentEntry<OnConnected<C>> current = head;
|
ConcurrentEntry<OnConnected<C>> current = head;
|
||||||
|
|
||||||
OnConnected<C> listener;
|
OnConnected<C> listener;
|
||||||
while (current != null && !shutdown.get()) {
|
while (current != null && !shutdown.get()) {
|
||||||
listener = current.getValue();
|
listener = current.getValue();
|
||||||
@ -143,14 +136,9 @@ class OnConnectedManager<C extends Connection> {
|
|||||||
/**
|
/**
|
||||||
* called on shutdown
|
* called on shutdown
|
||||||
*/
|
*/
|
||||||
public
|
public synchronized
|
||||||
void clear() {
|
void clear() {
|
||||||
// synchronized is used here to ensure the "single writer principle", and make sure that ONLY one thread at a time can enter this
|
this.entries.clear();
|
||||||
// section. Because of this, we can have unlimited reader threads all going at the same time, without contention (which is our
|
this.head_ = null;
|
||||||
// use-case 99% of the time)
|
|
||||||
synchronized (lock) {
|
|
||||||
this.entries.clear();
|
|
||||||
this.head = null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,16 +15,18 @@
|
|||||||
*/
|
*/
|
||||||
package dorkbox.network.connection.listenerManagement;
|
package dorkbox.network.connection.listenerManagement;
|
||||||
|
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
|
||||||
import com.esotericsoftware.kryo.util.IdentityMap;
|
import com.esotericsoftware.kryo.util.IdentityMap;
|
||||||
|
|
||||||
import dorkbox.network.connection.Connection;
|
import dorkbox.network.connection.Connection;
|
||||||
import dorkbox.network.connection.ConnectionManager;
|
import dorkbox.network.connection.ConnectionManager;
|
||||||
import dorkbox.network.connection.Listener.OnDisconnected;
|
import dorkbox.network.connection.Listener.OnDisconnected;
|
||||||
import dorkbox.network.connection.Listener.OnError;
|
import dorkbox.network.connection.Listener.OnError;
|
||||||
import dorkbox.util.collections.ConcurrentEntry;
|
import dorkbox.util.collections.ConcurrentEntry;
|
||||||
import org.slf4j.Logger;
|
|
||||||
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
|
||||||
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the remote end has been connected. This will be invoked before any objects are received by the network.
|
* Called when the remote end has been connected. This will be invoked before any objects are received by the network.
|
||||||
@ -33,82 +35,74 @@ import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
|
|||||||
*/
|
*/
|
||||||
@SuppressWarnings("Duplicates")
|
@SuppressWarnings("Duplicates")
|
||||||
public final
|
public final
|
||||||
class OnDisconnectedManager<C extends Connection> {
|
class OnDisconnectedManager {
|
||||||
|
// Recommended for best performance while adhering to the "single writer principle". Must be static-final
|
||||||
|
private static final AtomicReferenceFieldUpdater<OnDisconnectedManager, ConcurrentEntry> REF = AtomicReferenceFieldUpdater.newUpdater(
|
||||||
|
OnDisconnectedManager.class,
|
||||||
|
ConcurrentEntry.class,
|
||||||
|
"head_");
|
||||||
|
|
||||||
private final Logger logger;
|
private final Logger logger;
|
||||||
|
|
||||||
//
|
//
|
||||||
// The iterators for IdentityMap are NOT THREAD SAFE!
|
// The iterators for IdentityMap are NOT THREAD SAFE!
|
||||||
//
|
//
|
||||||
// This is only touched by a single thread, maintains a map of entries for FAST lookup during listener remove.
|
// This is only touched by a single thread, maintains a map of entries for FAST lookup during listener remove.
|
||||||
private final IdentityMap<OnDisconnected<C>, ConcurrentEntry> entries = new IdentityMap<OnDisconnected<C>, ConcurrentEntry>(32, ConnectionManager.LOAD_FACTOR);
|
private final IdentityMap<OnDisconnected, ConcurrentEntry> entries = new IdentityMap<OnDisconnected, ConcurrentEntry>(32,
|
||||||
private volatile ConcurrentEntry<OnDisconnected<C>> head = null; // reference to the first element
|
ConnectionManager.LOAD_FACTOR);
|
||||||
|
|
||||||
|
private volatile ConcurrentEntry<OnDisconnected> head_ = null; // reference to the first element
|
||||||
|
|
||||||
// synchronized is used here to ensure the "single writer principle", and make sure that ONLY one thread at a time can enter this
|
// synchronized is used here to ensure the "single writer principle", and make sure that ONLY one thread at a time can enter this
|
||||||
// section. Because of this, we can have unlimited reader threads all going at the same time, without contention (which is our
|
// section. Because of this, we can have unlimited reader threads all going at the same time, without contention (which is our
|
||||||
// use-case 99% of the time)
|
// use-case 99% of the time)
|
||||||
private final Object lock = new Object();
|
|
||||||
|
|
||||||
// Recommended for best performance while adhering to the "single writer principle". Must be static-final
|
|
||||||
private static final AtomicReferenceFieldUpdater<OnDisconnectedManager, ConcurrentEntry> REF =
|
|
||||||
AtomicReferenceFieldUpdater.newUpdater(OnDisconnectedManager.class,
|
|
||||||
ConcurrentEntry.class,
|
|
||||||
"head");
|
|
||||||
|
|
||||||
public
|
public
|
||||||
OnDisconnectedManager(final Logger logger) {
|
OnDisconnectedManager(final Logger logger) {
|
||||||
this.logger = logger;
|
this.logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void add(final OnDisconnected<C> listener) {
|
public synchronized
|
||||||
// synchronized is used here to ensure the "single writer principle", and make sure that ONLY one thread at a time can enter this
|
void add(final OnDisconnected listener) {
|
||||||
// section. Because of this, we can have unlimited reader threads all going at the same time, without contention (which is our
|
// access a snapshot (single-writer-principle)
|
||||||
// use-case 99% of the time)
|
ConcurrentEntry head = REF.get(this);
|
||||||
synchronized (lock) {
|
|
||||||
// access a snapshot (single-writer-principle)
|
|
||||||
ConcurrentEntry head = REF.get(this);
|
|
||||||
|
|
||||||
if (!entries.containsKey(listener)) {
|
if (!entries.containsKey(listener)) {
|
||||||
head = new ConcurrentEntry<Object>(listener, head);
|
head = new ConcurrentEntry<Object>(listener, head);
|
||||||
|
|
||||||
entries.put(listener, head);
|
entries.put(listener, head);
|
||||||
|
|
||||||
// save this snapshot back to the original (single writer principle)
|
// save this snapshot back to the original (single writer principle)
|
||||||
REF.lazySet(this, head);
|
REF.lazySet(this, head);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return true if the listener was removed, false otherwise
|
* @return true if the listener was removed, false otherwise
|
||||||
*/
|
*/
|
||||||
public
|
public synchronized
|
||||||
boolean remove(final OnDisconnected<C> listener) {
|
boolean remove(final OnDisconnected<Connection> listener) {
|
||||||
// synchronized is used here to ensure the "single writer principle", and make sure that ONLY one thread at a time can enter this
|
// access a snapshot (single-writer-principle)
|
||||||
// section. Because of this, we can have unlimited reader threads all going at the same time, without contention (which is our
|
ConcurrentEntry concurrentEntry = entries.get(listener);
|
||||||
// use-case 99% of the time)
|
|
||||||
synchronized (lock) {
|
|
||||||
// access a snapshot (single-writer-principle)
|
|
||||||
ConcurrentEntry concurrentEntry = entries.get(listener);
|
|
||||||
|
|
||||||
if (concurrentEntry != null) {
|
if (concurrentEntry != null) {
|
||||||
ConcurrentEntry head1 = REF.get(this);
|
ConcurrentEntry head = REF.get(this);
|
||||||
|
|
||||||
if (concurrentEntry == head1) {
|
if (concurrentEntry == head) {
|
||||||
// if it was second, now it's first
|
// if it was second, now it's first
|
||||||
head1 = head1.next();
|
head = head.next();
|
||||||
//oldHead.clear(); // optimize for GC not possible because of potentially running iterators
|
//oldHead.clear(); // optimize for GC not possible because of potentially running iterators
|
||||||
}
|
|
||||||
else {
|
|
||||||
concurrentEntry.remove();
|
|
||||||
}
|
|
||||||
|
|
||||||
// save this snapshot back to the original (single writer principle)
|
|
||||||
REF.lazySet(this, head1);
|
|
||||||
entries.remove(listener);
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
concurrentEntry.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
// save this snapshot back to the original (single writer principle)
|
||||||
|
REF.lazySet(this, head);
|
||||||
|
entries.remove(listener);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -116,11 +110,11 @@ class OnDisconnectedManager<C extends Connection> {
|
|||||||
/**
|
/**
|
||||||
* @return true if a listener was found, false otherwise
|
* @return true if a listener was found, false otherwise
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public
|
public
|
||||||
boolean notifyDisconnected(final C connection, final AtomicBoolean shutdown) {
|
<C extends Connection> boolean notifyDisconnected(final C connection, final AtomicBoolean shutdown) {
|
||||||
ConcurrentEntry<OnDisconnected<C>> head = REF.get(this);
|
ConcurrentEntry<OnDisconnected<C>> head = REF.get(this);
|
||||||
ConcurrentEntry<OnDisconnected<C>> current = head;
|
ConcurrentEntry<OnDisconnected<C>> current = head;
|
||||||
|
|
||||||
OnDisconnected<C> listener;
|
OnDisconnected<C> listener;
|
||||||
while (current != null && !shutdown.get()) {
|
while (current != null && !shutdown.get()) {
|
||||||
listener = current.getValue();
|
listener = current.getValue();
|
||||||
@ -133,7 +127,10 @@ class OnDisconnectedManager<C extends Connection> {
|
|||||||
((OnError<C>) listener).error(connection, e);
|
((OnError<C>) listener).error(connection, e);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
logger.error("Unable to notify listener on 'disconnected' for listener '{}', connection '{}'.", listener, connection, e);
|
logger.error("Unable to notify listener on 'disconnected' for listener '{}', connection '{}'.",
|
||||||
|
listener,
|
||||||
|
connection,
|
||||||
|
e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -144,14 +141,9 @@ class OnDisconnectedManager<C extends Connection> {
|
|||||||
/**
|
/**
|
||||||
* called on shutdown
|
* called on shutdown
|
||||||
*/
|
*/
|
||||||
public
|
public synchronized
|
||||||
void clear() {
|
void clear() {
|
||||||
// synchronized is used here to ensure the "single writer principle", and make sure that ONLY one thread at a time can enter this
|
this.entries.clear();
|
||||||
// section. Because of this, we can have unlimited reader threads all going at the same time, without contention (which is our
|
this.head_ = null;
|
||||||
// use-case 99% of the time)
|
|
||||||
synchronized (lock) {
|
|
||||||
this.entries.clear();
|
|
||||||
this.head = null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,16 +15,18 @@
|
|||||||
*/
|
*/
|
||||||
package dorkbox.network.connection.listenerManagement;
|
package dorkbox.network.connection.listenerManagement;
|
||||||
|
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
|
||||||
import com.esotericsoftware.kryo.util.IdentityMap;
|
import com.esotericsoftware.kryo.util.IdentityMap;
|
||||||
|
|
||||||
import dorkbox.network.connection.Connection;
|
import dorkbox.network.connection.Connection;
|
||||||
import dorkbox.network.connection.ConnectionManager;
|
import dorkbox.network.connection.ConnectionManager;
|
||||||
import dorkbox.network.connection.Listener.OnError;
|
import dorkbox.network.connection.Listener.OnError;
|
||||||
import dorkbox.network.connection.Listener.OnIdle;
|
import dorkbox.network.connection.Listener.OnIdle;
|
||||||
import dorkbox.util.collections.ConcurrentEntry;
|
import dorkbox.util.collections.ConcurrentEntry;
|
||||||
import org.slf4j.Logger;
|
|
||||||
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
|
||||||
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the remote end has been connected. This will be invoked before any objects are received by the network.
|
* Called when the remote end has been connected. This will be invoked before any objects are received by the network.
|
||||||
@ -33,94 +35,85 @@ import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
|
|||||||
*/
|
*/
|
||||||
@SuppressWarnings("Duplicates")
|
@SuppressWarnings("Duplicates")
|
||||||
public final
|
public final
|
||||||
class OnIdleManager<C extends Connection> {
|
class OnIdleManager {
|
||||||
|
// Recommended for best performance while adhering to the "single writer principle". Must be static-final
|
||||||
|
private static final AtomicReferenceFieldUpdater<OnIdleManager, ConcurrentEntry> REF = AtomicReferenceFieldUpdater.newUpdater(
|
||||||
|
OnIdleManager.class,
|
||||||
|
ConcurrentEntry.class,
|
||||||
|
"head_");
|
||||||
|
|
||||||
private final Logger logger;
|
private final Logger logger;
|
||||||
|
|
||||||
//
|
//
|
||||||
// The iterators for IdentityMap are NOT THREAD SAFE!
|
// The iterators for IdentityMap are NOT THREAD SAFE!
|
||||||
//
|
//
|
||||||
// This is only touched by a single thread, maintains a map of entries for FAST lookup during listener remove.
|
// This is only touched by a single thread, maintains a map of entries for FAST lookup during listener remove.
|
||||||
private final IdentityMap<OnIdle<C>, ConcurrentEntry> entries = new IdentityMap<OnIdle<C>, ConcurrentEntry>(32, ConnectionManager.LOAD_FACTOR);
|
private final IdentityMap<OnIdle, ConcurrentEntry<OnIdle>> entries = new IdentityMap<OnIdle, ConcurrentEntry<OnIdle>>(32,
|
||||||
private volatile ConcurrentEntry<OnIdle<C>> head = null; // reference to the first element
|
ConnectionManager.LOAD_FACTOR);
|
||||||
|
|
||||||
|
private volatile ConcurrentEntry<OnIdle> head_ = null; // reference to the first element
|
||||||
|
|
||||||
// synchronized is used here to ensure the "single writer principle", and make sure that ONLY one thread at a time can enter this
|
// synchronized is used here to ensure the "single writer principle", and make sure that ONLY one thread at a time can enter this
|
||||||
// section. Because of this, we can have unlimited reader threads all going at the same time, without contention (which is our
|
// section. Because of this, we can have unlimited reader threads all going at the same time, without contention (which is our
|
||||||
// use-case 99% of the time)
|
// use-case 99% of the time)
|
||||||
private final Object lock = new Object();
|
|
||||||
|
|
||||||
// Recommended for best performance while adhering to the "single writer principle". Must be static-final
|
|
||||||
private static final AtomicReferenceFieldUpdater<OnIdleManager, ConcurrentEntry> REF =
|
|
||||||
AtomicReferenceFieldUpdater.newUpdater(OnIdleManager.class,
|
|
||||||
ConcurrentEntry.class,
|
|
||||||
"head");
|
|
||||||
|
|
||||||
public
|
public
|
||||||
OnIdleManager(final Logger logger) {
|
OnIdleManager(final Logger logger) {
|
||||||
this.logger = logger;
|
this.logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void add(final OnIdle<C> listener) {
|
public synchronized
|
||||||
// synchronized is used here to ensure the "single writer principle", and make sure that ONLY one thread at a time can enter this
|
void add(final OnIdle listener) {
|
||||||
// section. Because of this, we can have unlimited reader threads all going at the same time, without contention (which is our
|
// access a snapshot (single-writer-principle)
|
||||||
// use-case 99% of the time)
|
ConcurrentEntry head = REF.get(this);
|
||||||
synchronized (lock) {
|
|
||||||
// access a snapshot (single-writer-principle)
|
|
||||||
ConcurrentEntry head = REF.get(this);
|
|
||||||
|
|
||||||
if (!entries.containsKey(listener)) {
|
if (!entries.containsKey(listener)) {
|
||||||
head = new ConcurrentEntry<Object>(listener, head);
|
head = new ConcurrentEntry<Object>(listener, head);
|
||||||
|
|
||||||
entries.put(listener, head);
|
entries.put(listener, head);
|
||||||
|
|
||||||
// save this snapshot back to the original (single writer principle)
|
// save this snapshot back to the original (single writer principle)
|
||||||
REF.lazySet(this, head);
|
REF.lazySet(this, head);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return true if the listener was removed, false otherwise
|
* @return true if the listener was removed, false otherwise
|
||||||
*/
|
*/
|
||||||
public
|
public synchronized
|
||||||
boolean remove(final OnIdle<C> listener) {
|
boolean remove(final OnIdle listener) {
|
||||||
// synchronized is used here to ensure the "single writer principle", and make sure that ONLY one thread at a time can enter this
|
// access a snapshot (single-writer-principle)
|
||||||
// section. Because of this, we can have unlimited reader threads all going at the same time, without contention (which is our
|
ConcurrentEntry concurrentEntry = entries.get(listener);
|
||||||
// use-case 99% of the time)
|
|
||||||
synchronized (lock) {
|
|
||||||
// access a snapshot (single-writer-principle)
|
|
||||||
ConcurrentEntry concurrentEntry = entries.get(listener);
|
|
||||||
|
|
||||||
if (concurrentEntry != null) {
|
if (concurrentEntry != null) {
|
||||||
ConcurrentEntry head1 = REF.get(this);
|
ConcurrentEntry head = REF.get(this);
|
||||||
|
|
||||||
if (concurrentEntry == head1) {
|
if (concurrentEntry == head) {
|
||||||
// if it was second, now it's first
|
// if it was second, now it's first
|
||||||
head1 = head1.next();
|
head = head.next();
|
||||||
//oldHead.clear(); // optimize for GC not possible because of potentially running iterators
|
//oldHead.clear(); // optimize for GC not possible because of potentially running iterators
|
||||||
}
|
|
||||||
else {
|
|
||||||
concurrentEntry.remove();
|
|
||||||
}
|
|
||||||
|
|
||||||
// save this snapshot back to the original (single writer principle)
|
|
||||||
REF.lazySet(this, head1);
|
|
||||||
entries.remove(listener);
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return false;
|
concurrentEntry.remove();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// save this snapshot back to the original (single writer principle)
|
||||||
|
REF.lazySet(this, head);
|
||||||
|
entries.remove(listener);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return true if a listener was found, false otherwise
|
* @return true if a listener was found, false otherwise
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public
|
public
|
||||||
boolean notifyIdle(final C connection, final AtomicBoolean shutdown) {
|
<C extends Connection> boolean notifyIdle(final C connection, final AtomicBoolean shutdown) {
|
||||||
ConcurrentEntry<OnIdle<C>> head = REF.get(this);
|
ConcurrentEntry<OnIdle<C>> head = REF.get(this);
|
||||||
ConcurrentEntry<OnIdle<C>> current = head;
|
ConcurrentEntry<OnIdle<C>> current = head;
|
||||||
|
|
||||||
OnIdle<C> listener;
|
OnIdle<C> listener;
|
||||||
while (current != null && !shutdown.get()) {
|
while (current != null && !shutdown.get()) {
|
||||||
listener = current.getValue();
|
listener = current.getValue();
|
||||||
@ -146,14 +139,9 @@ class OnIdleManager<C extends Connection> {
|
|||||||
/**
|
/**
|
||||||
* called on shutdown
|
* called on shutdown
|
||||||
*/
|
*/
|
||||||
public
|
public synchronized
|
||||||
void clear() {
|
void clear() {
|
||||||
// synchronized is used here to ensure the "single writer principle", and make sure that ONLY one thread at a time can enter this
|
this.entries.clear();
|
||||||
// section. Because of this, we can have unlimited reader threads all going at the same time, without contention (which is our
|
this.head_ = null;
|
||||||
// use-case 99% of the time)
|
|
||||||
synchronized (lock) {
|
|
||||||
this.entries.clear();
|
|
||||||
this.head = null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,7 +31,6 @@ import dorkbox.network.connection.Listener;
|
|||||||
import dorkbox.network.connection.Listener.OnError;
|
import dorkbox.network.connection.Listener.OnError;
|
||||||
import dorkbox.network.connection.Listener.OnMessageReceived;
|
import dorkbox.network.connection.Listener.OnMessageReceived;
|
||||||
import dorkbox.network.connection.Listener.SelfDefinedType;
|
import dorkbox.network.connection.Listener.SelfDefinedType;
|
||||||
import dorkbox.network.rmi.RmiMessages;
|
|
||||||
import dorkbox.util.collections.ConcurrentEntry;
|
import dorkbox.util.collections.ConcurrentEntry;
|
||||||
import dorkbox.util.collections.ConcurrentIterator;
|
import dorkbox.util.collections.ConcurrentIterator;
|
||||||
import dorkbox.util.generics.ClassHelper;
|
import dorkbox.util.generics.ClassHelper;
|
||||||
@ -43,256 +42,12 @@ import dorkbox.util.generics.ClassHelper;
|
|||||||
*/
|
*/
|
||||||
@SuppressWarnings("Duplicates")
|
@SuppressWarnings("Duplicates")
|
||||||
public final
|
public final
|
||||||
class OnMessageReceivedManager<C extends Connection> {
|
class OnMessageReceivedManager {
|
||||||
private final Logger logger;
|
|
||||||
|
|
||||||
//
|
|
||||||
// The iterators for IdentityMap are NOT THREAD SAFE!
|
|
||||||
//
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
private volatile IdentityMap<Type, ConcurrentIterator> listeners = new IdentityMap<Type, ConcurrentIterator>(32, ConnectionManager.LOAD_FACTOR);
|
|
||||||
|
|
||||||
// Recommended for best performance while adhering to the "single writer principle". Must be static-final
|
// Recommended for best performance while adhering to the "single writer principle". Must be static-final
|
||||||
private static final AtomicReferenceFieldUpdater<OnMessageReceivedManager, IdentityMap> REF =
|
private static final AtomicReferenceFieldUpdater<OnMessageReceivedManager, IdentityMap> REF = AtomicReferenceFieldUpdater.newUpdater(
|
||||||
AtomicReferenceFieldUpdater.newUpdater(OnMessageReceivedManager.class,
|
OnMessageReceivedManager.class,
|
||||||
IdentityMap.class,
|
IdentityMap.class,
|
||||||
"listeners");
|
"listeners");
|
||||||
|
|
||||||
|
|
||||||
// synchronized is used here to ensure the "single writer principle", and make sure that ONLY one thread at a time can enter this
|
|
||||||
// section. Because of this, we can have unlimited reader threads all going at the same time, without contention (which is our
|
|
||||||
// use-case 99% of the time)
|
|
||||||
private final Object lock = new Object();
|
|
||||||
|
|
||||||
|
|
||||||
public
|
|
||||||
OnMessageReceivedManager(final Logger logger) {
|
|
||||||
this.logger = logger;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void add(final OnMessageReceived<C, Object> listener) {
|
|
||||||
final Class<?> type;
|
|
||||||
if (listener instanceof SelfDefinedType) {
|
|
||||||
type = ((SelfDefinedType) listener).getType();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
type = identifyType(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
// synchronized is used here to ensure the "single writer principle", and make sure that ONLY one thread at a time can enter this
|
|
||||||
// section. Because of this, we can have unlimited reader threads all going at the same time, without contention (which is our
|
|
||||||
// use-case 99% of the time)
|
|
||||||
synchronized (lock) {
|
|
||||||
// access a snapshot of the listeners (single-writer-principle)
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
final IdentityMap<Type, ConcurrentIterator> listeners = REF.get(this);
|
|
||||||
|
|
||||||
ConcurrentIterator subscribedListeners = listeners.get(type);
|
|
||||||
if (subscribedListeners == null) {
|
|
||||||
subscribedListeners = new ConcurrentIterator();
|
|
||||||
listeners.put(type, subscribedListeners);
|
|
||||||
}
|
|
||||||
|
|
||||||
subscribedListeners.add(listener);
|
|
||||||
|
|
||||||
// save this snapshot back to the original (single writer principle)
|
|
||||||
REF.lazySet(this, listeners);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return true if the listener was removed, false otherwise
|
|
||||||
*/
|
|
||||||
public
|
|
||||||
boolean remove(final OnMessageReceived<C, Object> listener) {
|
|
||||||
final Class<?> type;
|
|
||||||
if (listener instanceof SelfDefinedType) {
|
|
||||||
type = ((SelfDefinedType) listener).getType();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
type = identifyType(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean found = false;
|
|
||||||
// synchronized is used here to ensure the "single writer principle", and make sure that ONLY one thread at a time can enter this
|
|
||||||
// section. Because of this, we can have unlimited reader threads all going at the same time, without contention (which is our
|
|
||||||
// use-case 99% of the time)
|
|
||||||
synchronized (lock) {
|
|
||||||
// access a snapshot of the listeners (single-writer-principle)
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
final IdentityMap<Type, ConcurrentIterator> listeners = REF.get(this);
|
|
||||||
final ConcurrentIterator concurrentIterator = listeners.get(type);
|
|
||||||
if (concurrentIterator != null) {
|
|
||||||
concurrentIterator.remove(listener);
|
|
||||||
found = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// save this snapshot back to the original (single writer principle)
|
|
||||||
REF.lazySet(this, listeners);
|
|
||||||
}
|
|
||||||
|
|
||||||
return found;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return true if a listener was found, false otherwise
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public
|
|
||||||
boolean notifyReceived(final C connection, final Object message, final AtomicBoolean shutdown) {
|
|
||||||
boolean found = false;
|
|
||||||
Class<?> objectType = message.getClass();
|
|
||||||
|
|
||||||
|
|
||||||
// this is the GLOBAL version (unless it's the call from below, then it's the connection scoped version)
|
|
||||||
final IdentityMap<Type, ConcurrentIterator> listeners = REF.get(this);
|
|
||||||
ConcurrentIterator concurrentIterator = listeners.get(objectType);
|
|
||||||
|
|
||||||
if (concurrentIterator != null) {
|
|
||||||
ConcurrentEntry<OnMessageReceived<C, Object>> head = headREF.get(concurrentIterator);
|
|
||||||
ConcurrentEntry<OnMessageReceived<C, Object>> current = head;
|
|
||||||
OnMessageReceived<C, Object> listener;
|
|
||||||
while (current != null && !shutdown.get()) {
|
|
||||||
listener = current.getValue();
|
|
||||||
current = current.next();
|
|
||||||
|
|
||||||
try {
|
|
||||||
listener.received(connection, message);
|
|
||||||
} catch (Exception e) {
|
|
||||||
if (listener instanceof OnError) {
|
|
||||||
((OnError<C>) listener).error(connection, e);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
logger.error("Unable to notify on message '{}' for listener '{}', connection '{}'.",
|
|
||||||
objectType,
|
|
||||||
listener,
|
|
||||||
connection,
|
|
||||||
e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
found = head != null; // true if we have something to publish to, otherwise false
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!(message instanceof RmiMessages)) {
|
|
||||||
// we march through all super types of the object, and find the FIRST set
|
|
||||||
// of listeners that are registered and cast it as that, and notify the method.
|
|
||||||
// NOTICE: we do NOT call ALL TYPE -- meaning, if we have Object->Foo->Bar
|
|
||||||
// and have listeners for Object and Foo
|
|
||||||
// we will call Bar (from the above code)
|
|
||||||
// we will call Foo (from this code)
|
|
||||||
// we will NOT call Object (since we called Foo). If Foo was not registered, THEN we would call object!
|
|
||||||
|
|
||||||
objectType = objectType.getSuperclass();
|
|
||||||
while (objectType != null) {
|
|
||||||
// check to see if we have what we are looking for in our CURRENT class
|
|
||||||
concurrentIterator = listeners.get(objectType);
|
|
||||||
if (concurrentIterator != null) {
|
|
||||||
ConcurrentEntry<OnMessageReceived<C, Object>> head = headREF.get(concurrentIterator);
|
|
||||||
ConcurrentEntry<OnMessageReceived<C, Object>> current = head;
|
|
||||||
OnMessageReceived<C, Object> listener;
|
|
||||||
while (current != null && !shutdown.get()) {
|
|
||||||
listener = current.getValue();
|
|
||||||
current = current.next();
|
|
||||||
|
|
||||||
try {
|
|
||||||
listener.received(connection, message);
|
|
||||||
} catch (Exception e) {
|
|
||||||
if (listener instanceof OnError) {
|
|
||||||
((OnError<C>) listener).error(connection, e);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
logger.error("Unable to notify on message '{}' for listener '{}', connection '{}'.",
|
|
||||||
objectType,
|
|
||||||
listener,
|
|
||||||
connection,
|
|
||||||
e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
found = head != null; // true if we have something to publish to, otherwise false
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// NO MATCH, so walk up.
|
|
||||||
objectType = objectType.getSuperclass();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return found;
|
|
||||||
}
|
|
||||||
|
|
||||||
public
|
|
||||||
void removeAll() {
|
|
||||||
// synchronized is used here to ensure the "single writer principle", and make sure that ONLY one thread at a time can enter this
|
|
||||||
// section. Because of this, we can have unlimited reader threads all going at the same time, without contention (which is our
|
|
||||||
// use-case 99% of the time)
|
|
||||||
synchronized (lock) {
|
|
||||||
// access a snapshot of the listeners (single-writer-principle)
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
final IdentityMap<Type, ConcurrentIterator> listeners = REF.get(this);
|
|
||||||
|
|
||||||
listeners.clear();
|
|
||||||
|
|
||||||
// save this snapshot back to the original (single writer principle)
|
|
||||||
REF.lazySet(this, listeners);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return true if the listener was removed, false otherwise
|
|
||||||
*/
|
|
||||||
public
|
|
||||||
boolean removeAll(final Class<?> classType) {
|
|
||||||
boolean found;
|
|
||||||
|
|
||||||
// synchronized is used here to ensure the "single writer principle", and make sure that ONLY one thread at a time can enter this
|
|
||||||
// section. Because of this, we can have unlimited reader threads all going at the same time, without contention (which is our
|
|
||||||
// use-case 99% of the time)
|
|
||||||
synchronized (lock) {
|
|
||||||
// access a snapshot of the listeners (single-writer-principle)
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
final IdentityMap<Type, ConcurrentIterator> listeners = REF.get(this);
|
|
||||||
|
|
||||||
found = listeners.remove(classType) != null;
|
|
||||||
|
|
||||||
// save this snapshot back to the original (single writer principle)
|
|
||||||
REF.lazySet(this, listeners);
|
|
||||||
}
|
|
||||||
|
|
||||||
return found;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* called on shutdown
|
|
||||||
*/
|
|
||||||
public
|
|
||||||
void clear() {
|
|
||||||
// synchronized is used here to ensure the "single writer principle", and make sure that ONLY one thread at a time can enter this
|
|
||||||
// section. Because of this, we can have unlimited reader threads all going at the same time, without contention (which is our
|
|
||||||
// use-case 99% of the time)
|
|
||||||
synchronized (lock) {
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
final IdentityMap<Type, ConcurrentIterator> listeners = REF.get(this);
|
|
||||||
|
|
||||||
// The iterators for this map are NOT THREAD SAFE!
|
|
||||||
// using .entries() is what we are supposed to use!
|
|
||||||
final IdentityMap.Entries<Type, ConcurrentIterator> entries = listeners.entries();
|
|
||||||
for (final IdentityMap.Entry<Type, ConcurrentIterator> next : entries) {
|
|
||||||
if (next.value != null) {
|
|
||||||
next.value.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
listeners.clear();
|
|
||||||
|
|
||||||
// save this snapshot back to the original (single writer principle)
|
|
||||||
REF.lazySet(this, listeners);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the referenced object type for a specific listener, but ONLY necessary for listeners that receive messages
|
* Gets the referenced object type for a specific listener, but ONLY necessary for listeners that receive messages
|
||||||
@ -321,4 +76,211 @@ class OnMessageReceivedManager<C extends Connection> {
|
|||||||
return Object.class;
|
return Object.class;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private final Logger logger;
|
||||||
|
|
||||||
|
//
|
||||||
|
// The iterators for IdentityMap are NOT THREAD SAFE!
|
||||||
|
//
|
||||||
|
private volatile IdentityMap<Type, ConcurrentIterator> listeners = new IdentityMap<Type, ConcurrentIterator>(32, ConnectionManager.LOAD_FACTOR);
|
||||||
|
|
||||||
|
// synchronized is used here to ensure the "single writer principle", and make sure that ONLY one thread at a time can enter this
|
||||||
|
// section. Because of this, we can have unlimited reader threads all going at the same time, without contention (which is our
|
||||||
|
// use-case 99% of the time)
|
||||||
|
public
|
||||||
|
OnMessageReceivedManager(final Logger logger) {
|
||||||
|
this.logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public
|
||||||
|
void add(final OnMessageReceived listener) {
|
||||||
|
final Class<?> type;
|
||||||
|
if (listener instanceof SelfDefinedType) {
|
||||||
|
type = ((SelfDefinedType) listener).getType();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
type = identifyType(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronized (this) {
|
||||||
|
// access a snapshot of the listeners (single-writer-principle)
|
||||||
|
final IdentityMap<Type, ConcurrentIterator> listeners = REF.get(this);
|
||||||
|
|
||||||
|
ConcurrentIterator subscribedListeners = listeners.get(type);
|
||||||
|
if (subscribedListeners == null) {
|
||||||
|
subscribedListeners = new ConcurrentIterator();
|
||||||
|
listeners.put(type, subscribedListeners);
|
||||||
|
}
|
||||||
|
|
||||||
|
subscribedListeners.add(listener);
|
||||||
|
|
||||||
|
// save this snapshot back to the original (single writer principle)
|
||||||
|
REF.lazySet(this, listeners);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return true if the listener was removed, false otherwise
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
boolean remove(final OnMessageReceived listener) {
|
||||||
|
final Class<?> type;
|
||||||
|
if (listener instanceof SelfDefinedType) {
|
||||||
|
type = ((SelfDefinedType) listener).getType();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
type = identifyType(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean found = false;
|
||||||
|
synchronized (this) {
|
||||||
|
// access a snapshot of the listeners (single-writer-principle)
|
||||||
|
final IdentityMap<Type, ConcurrentIterator> listeners = REF.get(this);
|
||||||
|
|
||||||
|
final ConcurrentIterator concurrentIterator = listeners.get(type);
|
||||||
|
if (concurrentIterator != null) {
|
||||||
|
concurrentIterator.remove(listener);
|
||||||
|
found = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// save this snapshot back to the original (single writer principle)
|
||||||
|
REF.lazySet(this, listeners);
|
||||||
|
}
|
||||||
|
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return true if a listener was found, false otherwise
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
<C extends Connection> boolean notifyReceived(final C connection, final Object message, final AtomicBoolean shutdown) {
|
||||||
|
boolean found = false;
|
||||||
|
Class<?> objectType = message.getClass();
|
||||||
|
|
||||||
|
|
||||||
|
// this is the GLOBAL version (unless it's the call from below, then it's the connection scoped version)
|
||||||
|
final IdentityMap<Type, ConcurrentIterator> listeners = REF.get(this);
|
||||||
|
ConcurrentIterator concurrentIterator = listeners.get(objectType);
|
||||||
|
|
||||||
|
if (concurrentIterator != null) {
|
||||||
|
ConcurrentEntry<OnMessageReceived<C, Object>> head = headREF.get(concurrentIterator);
|
||||||
|
ConcurrentEntry<OnMessageReceived<C, Object>> current = head;
|
||||||
|
|
||||||
|
OnMessageReceived<C, Object> listener;
|
||||||
|
while (current != null && !shutdown.get()) {
|
||||||
|
listener = current.getValue();
|
||||||
|
current = current.next();
|
||||||
|
|
||||||
|
try {
|
||||||
|
listener.received(connection, message);
|
||||||
|
} catch (Exception e) {
|
||||||
|
if (listener instanceof OnError) {
|
||||||
|
((OnError<C>) listener).error(connection, e);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
logger.error("Unable to notify on message '{}' for listener '{}', connection '{}'.",
|
||||||
|
objectType,
|
||||||
|
listener,
|
||||||
|
connection,
|
||||||
|
e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
found = head != null; // true if we have something to publish to, otherwise false
|
||||||
|
}
|
||||||
|
|
||||||
|
// we march through all super types of the object, and find the FIRST set
|
||||||
|
// of listeners that are registered and cast it as that, and notify the method.
|
||||||
|
// NOTICE: we do NOT call ALL TYPES -- meaning, if we have Object->Foo->Bar
|
||||||
|
// and have listeners for Object and Foo
|
||||||
|
// we will call Bar (from the above code)
|
||||||
|
// we will call Foo (from this code)
|
||||||
|
// we will NOT call Object (since we called Foo). If Foo was not registered, THEN we would call object!
|
||||||
|
|
||||||
|
objectType = objectType.getSuperclass();
|
||||||
|
while (objectType != null) {
|
||||||
|
// check to see if we have what we are looking for in our CURRENT class
|
||||||
|
concurrentIterator = listeners.get(objectType);
|
||||||
|
if (concurrentIterator != null) {
|
||||||
|
ConcurrentEntry<OnMessageReceived<C, Object>> head = headREF.get(concurrentIterator);
|
||||||
|
ConcurrentEntry<OnMessageReceived<C, Object>> current = head;
|
||||||
|
|
||||||
|
OnMessageReceived<C, Object> listener;
|
||||||
|
while (current != null && !shutdown.get()) {
|
||||||
|
listener = current.getValue();
|
||||||
|
current = current.next();
|
||||||
|
|
||||||
|
try {
|
||||||
|
listener.received(connection, message);
|
||||||
|
} catch (Exception e) {
|
||||||
|
if (listener instanceof OnError) {
|
||||||
|
((OnError<C>) listener).error(connection, e);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
logger.error("Unable to notify on message '{}' for listener '{}', connection '{}'.",
|
||||||
|
objectType,
|
||||||
|
listener,
|
||||||
|
connection,
|
||||||
|
e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
found = head != null; // true if we have something to publish to, otherwise false
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// NO MATCH, so walk up.
|
||||||
|
objectType = objectType.getSuperclass();
|
||||||
|
}
|
||||||
|
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized
|
||||||
|
void removeAll() {
|
||||||
|
listeners.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return true if the listener was removed, false otherwise
|
||||||
|
*/
|
||||||
|
public synchronized
|
||||||
|
boolean removeAll(final Class<?> classType) {
|
||||||
|
boolean found;
|
||||||
|
|
||||||
|
// access a snapshot of the listeners (single-writer-principle)
|
||||||
|
final IdentityMap<Type, ConcurrentIterator> listeners = REF.get(this);
|
||||||
|
|
||||||
|
found = listeners.remove(classType) != null;
|
||||||
|
|
||||||
|
// save this snapshot back to the original (single writer principle)
|
||||||
|
REF.lazySet(this, listeners);
|
||||||
|
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* called on shutdown
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
void clear() {
|
||||||
|
final IdentityMap<Type, ConcurrentIterator> listeners = REF.get(this);
|
||||||
|
|
||||||
|
// The iterators for this map are NOT THREAD SAFE!
|
||||||
|
// using .entries() is what we are supposed to use!
|
||||||
|
final IdentityMap.Entries<Type, ConcurrentIterator> entries = listeners.entries();
|
||||||
|
for (final IdentityMap.Entry<Type, ConcurrentIterator> next : entries) {
|
||||||
|
if (next.value != null) {
|
||||||
|
next.value.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
listeners.clear();
|
||||||
|
|
||||||
|
// save this snapshot back to the original (single writer principle)
|
||||||
|
REF.lazySet(this, listeners);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,6 @@
|
|||||||
*/
|
*/
|
||||||
package dorkbox.network.connection.registration;
|
package dorkbox.network.connection.registration;
|
||||||
|
|
||||||
import dorkbox.network.connection.Connection;
|
|
||||||
import dorkbox.network.connection.EndPointBase;
|
import dorkbox.network.connection.EndPointBase;
|
||||||
import dorkbox.network.connection.RegistrationWrapper;
|
import dorkbox.network.connection.RegistrationWrapper;
|
||||||
import io.netty.channel.Channel;
|
import io.netty.channel.Channel;
|
||||||
@ -25,16 +24,16 @@ import io.netty.channel.ChannelInboundHandlerAdapter;
|
|||||||
|
|
||||||
@Sharable
|
@Sharable
|
||||||
public abstract
|
public abstract
|
||||||
class RegistrationHandler<C extends Connection> extends ChannelInboundHandlerAdapter {
|
class RegistrationHandler extends ChannelInboundHandlerAdapter {
|
||||||
protected static final String CONNECTION_HANDLER = "connectionHandler";
|
protected static final String CONNECTION_HANDLER = "connectionHandler";
|
||||||
|
|
||||||
protected final RegistrationWrapper<C> registrationWrapper;
|
protected final RegistrationWrapper registrationWrapper;
|
||||||
protected final org.slf4j.Logger logger;
|
protected final org.slf4j.Logger logger;
|
||||||
protected final String name;
|
protected final String name;
|
||||||
|
|
||||||
|
|
||||||
public
|
public
|
||||||
RegistrationHandler(final String name, RegistrationWrapper<C> registrationWrapper) {
|
RegistrationHandler(final String name, RegistrationWrapper registrationWrapper) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.logger = org.slf4j.LoggerFactory.getLogger(this.name);
|
this.logger = org.slf4j.LoggerFactory.getLogger(this.name);
|
||||||
this.registrationWrapper = registrationWrapper;
|
this.registrationWrapper = registrationWrapper;
|
||||||
@ -85,7 +84,7 @@ class RegistrationHandler<C extends Connection> extends ChannelInboundHandlerAda
|
|||||||
void exceptionCaught(final ChannelHandlerContext context, final Throwable cause) throws Exception;
|
void exceptionCaught(final ChannelHandlerContext context, final Throwable cause) throws Exception;
|
||||||
|
|
||||||
public
|
public
|
||||||
MetaChannel shutdown(final RegistrationWrapper<C> registrationWrapper, final Channel channel) {
|
MetaChannel shutdown(final RegistrationWrapper registrationWrapper, final Channel channel) {
|
||||||
// shutdown. Something messed up or was incorrect
|
// shutdown. Something messed up or was incorrect
|
||||||
// properly shutdown the TCP/UDP channels.
|
// properly shutdown the TCP/UDP channels.
|
||||||
if (channel.isOpen()) {
|
if (channel.isOpen()) {
|
||||||
|
@ -17,8 +17,6 @@ package dorkbox.network.connection.registration.local;
|
|||||||
|
|
||||||
import static dorkbox.network.connection.EndPointBase.maxShutdownWaitTimeInMilliSeconds;
|
import static dorkbox.network.connection.EndPointBase.maxShutdownWaitTimeInMilliSeconds;
|
||||||
|
|
||||||
import dorkbox.network.connection.Connection;
|
|
||||||
import dorkbox.network.connection.RegisterRmiLocalHandler;
|
|
||||||
import dorkbox.network.connection.RegistrationWrapper;
|
import dorkbox.network.connection.RegistrationWrapper;
|
||||||
import dorkbox.network.connection.registration.MetaChannel;
|
import dorkbox.network.connection.registration.MetaChannel;
|
||||||
import dorkbox.network.connection.registration.RegistrationHandler;
|
import dorkbox.network.connection.registration.RegistrationHandler;
|
||||||
@ -26,11 +24,9 @@ import io.netty.channel.Channel;
|
|||||||
import io.netty.channel.ChannelHandlerContext;
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
|
|
||||||
public abstract
|
public abstract
|
||||||
class RegistrationLocalHandler<C extends Connection> extends RegistrationHandler<C> {
|
class RegistrationLocalHandler extends RegistrationHandler {
|
||||||
static final String LOCAL_RMI_HANDLER = "localRmiHandler";
|
|
||||||
final RegisterRmiLocalHandler rmiLocalHandler = new RegisterRmiLocalHandler();
|
|
||||||
|
|
||||||
RegistrationLocalHandler(String name, RegistrationWrapper<C> registrationWrapper) {
|
RegistrationLocalHandler(String name, RegistrationWrapper registrationWrapper) {
|
||||||
super(name, registrationWrapper);
|
super(name, registrationWrapper);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,7 +15,6 @@
|
|||||||
*/
|
*/
|
||||||
package dorkbox.network.connection.registration.local;
|
package dorkbox.network.connection.registration.local;
|
||||||
|
|
||||||
import dorkbox.network.connection.Connection;
|
|
||||||
import dorkbox.network.connection.ConnectionImpl;
|
import dorkbox.network.connection.ConnectionImpl;
|
||||||
import dorkbox.network.connection.RegistrationWrapper;
|
import dorkbox.network.connection.RegistrationWrapper;
|
||||||
import dorkbox.network.connection.registration.MetaChannel;
|
import dorkbox.network.connection.registration.MetaChannel;
|
||||||
@ -26,10 +25,10 @@ import io.netty.channel.ChannelPipeline;
|
|||||||
import io.netty.util.ReferenceCountUtil;
|
import io.netty.util.ReferenceCountUtil;
|
||||||
|
|
||||||
public
|
public
|
||||||
class RegistrationLocalHandlerClient<C extends Connection> extends RegistrationLocalHandler<C> {
|
class RegistrationLocalHandlerClient extends RegistrationLocalHandler {
|
||||||
|
|
||||||
public
|
public
|
||||||
RegistrationLocalHandlerClient(String name, RegistrationWrapper<C> registrationWrapper) {
|
RegistrationLocalHandlerClient(String name, RegistrationWrapper registrationWrapper) {
|
||||||
super(name, registrationWrapper);
|
super(name, registrationWrapper);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -78,15 +77,6 @@ class RegistrationLocalHandlerClient<C extends Connection> extends RegistrationL
|
|||||||
|
|
||||||
ConnectionImpl connection = metaChannel.connection;
|
ConnectionImpl connection = metaChannel.connection;
|
||||||
|
|
||||||
|
|
||||||
// add our RMI handlers
|
|
||||||
if (registrationWrapper.rmiEnabled()) {
|
|
||||||
///////////////////////
|
|
||||||
// DECODE (or upstream)
|
|
||||||
///////////////////////
|
|
||||||
pipeline.addFirst(LOCAL_RMI_HANDLER, rmiLocalHandler);
|
|
||||||
}
|
|
||||||
|
|
||||||
// have to setup connection handler
|
// have to setup connection handler
|
||||||
pipeline.addLast(CONNECTION_HANDLER, connection);
|
pipeline.addLast(CONNECTION_HANDLER, connection);
|
||||||
|
|
||||||
|
@ -15,7 +15,6 @@
|
|||||||
*/
|
*/
|
||||||
package dorkbox.network.connection.registration.local;
|
package dorkbox.network.connection.registration.local;
|
||||||
|
|
||||||
import dorkbox.network.connection.Connection;
|
|
||||||
import dorkbox.network.connection.ConnectionImpl;
|
import dorkbox.network.connection.ConnectionImpl;
|
||||||
import dorkbox.network.connection.RegistrationWrapper;
|
import dorkbox.network.connection.RegistrationWrapper;
|
||||||
import dorkbox.network.connection.registration.MetaChannel;
|
import dorkbox.network.connection.registration.MetaChannel;
|
||||||
@ -25,10 +24,10 @@ import io.netty.channel.ChannelPipeline;
|
|||||||
import io.netty.util.ReferenceCountUtil;
|
import io.netty.util.ReferenceCountUtil;
|
||||||
|
|
||||||
public
|
public
|
||||||
class RegistrationLocalHandlerServer<C extends Connection> extends RegistrationLocalHandler<C> {
|
class RegistrationLocalHandlerServer extends RegistrationLocalHandler {
|
||||||
|
|
||||||
public
|
public
|
||||||
RegistrationLocalHandlerServer(String name, RegistrationWrapper<C> registrationWrapper) {
|
RegistrationLocalHandlerServer(String name, RegistrationWrapper registrationWrapper) {
|
||||||
super(name, registrationWrapper);
|
super(name, registrationWrapper);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -78,14 +77,6 @@ class RegistrationLocalHandlerServer<C extends Connection> extends RegistrationL
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (connection != null) {
|
if (connection != null) {
|
||||||
// add our RMI handlers
|
|
||||||
if (registrationWrapper.rmiEnabled()) {
|
|
||||||
///////////////////////
|
|
||||||
// DECODE (or upstream)
|
|
||||||
///////////////////////
|
|
||||||
pipeline.addFirst(LOCAL_RMI_HANDLER, rmiLocalHandler);
|
|
||||||
}
|
|
||||||
|
|
||||||
// have to setup connection handler
|
// have to setup connection handler
|
||||||
pipeline.addLast(CONNECTION_HANDLER, connection);
|
pipeline.addLast(CONNECTION_HANDLER, connection);
|
||||||
|
|
||||||
|
@ -27,7 +27,6 @@ import org.bouncycastle.crypto.engines.IESEngine;
|
|||||||
import org.bouncycastle.crypto.modes.GCMBlockCipher;
|
import org.bouncycastle.crypto.modes.GCMBlockCipher;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
|
|
||||||
import dorkbox.network.connection.Connection;
|
|
||||||
import dorkbox.network.connection.ConnectionImpl;
|
import dorkbox.network.connection.ConnectionImpl;
|
||||||
import dorkbox.network.connection.RegistrationWrapper;
|
import dorkbox.network.connection.RegistrationWrapper;
|
||||||
import dorkbox.network.connection.registration.MetaChannel;
|
import dorkbox.network.connection.registration.MetaChannel;
|
||||||
@ -42,15 +41,11 @@ import dorkbox.util.crypto.CryptoECC;
|
|||||||
import io.netty.channel.Channel;
|
import io.netty.channel.Channel;
|
||||||
import io.netty.channel.ChannelHandlerContext;
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
import io.netty.channel.ChannelPipeline;
|
import io.netty.channel.ChannelPipeline;
|
||||||
import io.netty.channel.epoll.EpollDatagramChannel;
|
|
||||||
import io.netty.channel.epoll.EpollSocketChannel;
|
|
||||||
import io.netty.channel.socket.nio.NioDatagramChannel;
|
|
||||||
import io.netty.channel.socket.nio.NioSocketChannel;
|
|
||||||
import io.netty.handler.timeout.IdleStateHandler;
|
import io.netty.handler.timeout.IdleStateHandler;
|
||||||
import io.netty.util.ReferenceCountUtil;
|
import io.netty.util.ReferenceCountUtil;
|
||||||
|
|
||||||
public abstract
|
public abstract
|
||||||
class RegistrationRemoteHandler<C extends Connection> extends RegistrationHandler<C> {
|
class RegistrationRemoteHandler extends RegistrationHandler {
|
||||||
static final String KRYO_ENCODER = "kryoEncoder";
|
static final String KRYO_ENCODER = "kryoEncoder";
|
||||||
static final String KRYO_DECODER = "kryoDecoder";
|
static final String KRYO_DECODER = "kryoDecoder";
|
||||||
|
|
||||||
@ -100,7 +95,7 @@ class RegistrationRemoteHandler<C extends Connection> extends RegistrationHandle
|
|||||||
protected final CryptoSerializationManager serializationManager;
|
protected final CryptoSerializationManager serializationManager;
|
||||||
|
|
||||||
RegistrationRemoteHandler(final String name,
|
RegistrationRemoteHandler(final String name,
|
||||||
final RegistrationWrapper<C> registrationWrapper,
|
final RegistrationWrapper registrationWrapper,
|
||||||
final CryptoSerializationManager serializationManager) {
|
final CryptoSerializationManager serializationManager) {
|
||||||
super(name, registrationWrapper);
|
super(name, registrationWrapper);
|
||||||
|
|
||||||
@ -143,46 +138,50 @@ class RegistrationRemoteHandler<C extends Connection> extends RegistrationHandle
|
|||||||
// add the channel so we can access it later.
|
// add the channel so we can access it later.
|
||||||
// do NOT want to add UDP channels, since they are tracked differently.
|
// do NOT want to add UDP channels, since they are tracked differently.
|
||||||
|
|
||||||
|
if (this.logger.isInfoEnabled()) {
|
||||||
|
Channel channel = context.channel();
|
||||||
|
Class<? extends Channel> channelClass = channel.getClass();
|
||||||
|
boolean isUdp = ConnectionImpl.isUdp(channelClass);
|
||||||
|
|
||||||
// this whole bit is inside a if (logger.isDebugEnabled()) section.
|
StringBuilder stringBuilder = new StringBuilder(96);
|
||||||
Channel channel = context.channel();
|
|
||||||
Class<? extends Channel> channelClass = channel.getClass();
|
|
||||||
|
|
||||||
|
stringBuilder.append("Connected to remote ");
|
||||||
StringBuilder stringBuilder = new StringBuilder(96);
|
if (ConnectionImpl.isTcp(channelClass)) {
|
||||||
|
stringBuilder.append("TCP");
|
||||||
stringBuilder.append("Connected to remote ");
|
}
|
||||||
if (channelClass == NioSocketChannel.class || channelClass == EpollSocketChannel.class) {
|
else if (isUdp) {
|
||||||
stringBuilder.append("TCP");
|
stringBuilder.append("UDP");
|
||||||
}
|
}
|
||||||
else if (channelClass == NioDatagramChannel.class || channelClass == EpollDatagramChannel.class) {
|
else if (ConnectionImpl.isLocal(channelClass)) {
|
||||||
stringBuilder.append("UDP");
|
stringBuilder.append("LOCAL");
|
||||||
}
|
|
||||||
else {
|
|
||||||
stringBuilder.append("UNKNOWN");
|
|
||||||
}
|
|
||||||
stringBuilder.append(" connection. [");
|
|
||||||
stringBuilder.append(channel.localAddress());
|
|
||||||
|
|
||||||
boolean isSessionless = channel instanceof NioDatagramChannel;
|
|
||||||
if (isSessionless) {
|
|
||||||
if (channel.remoteAddress() != null) {
|
|
||||||
stringBuilder.append(" ==> ");
|
|
||||||
stringBuilder.append(channel.remoteAddress());
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// this means we are LISTENING.
|
stringBuilder.append("UNKNOWN");
|
||||||
stringBuilder.append(" <== ");
|
|
||||||
stringBuilder.append("?????");
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
else {
|
|
||||||
stringBuilder.append(getConnectionDirection());
|
|
||||||
stringBuilder.append(channel.remoteAddress());
|
|
||||||
}
|
|
||||||
stringBuilder.append("]");
|
|
||||||
|
|
||||||
this.logger.info(stringBuilder.toString());
|
stringBuilder.append(" connection. [");
|
||||||
|
stringBuilder.append(channel.localAddress());
|
||||||
|
|
||||||
|
// this means we are "Sessionless"
|
||||||
|
if (isUdp) {
|
||||||
|
if (channel.remoteAddress() != null) {
|
||||||
|
stringBuilder.append(" ==> ");
|
||||||
|
stringBuilder.append(channel.remoteAddress());
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// this means we are LISTENING.
|
||||||
|
stringBuilder.append(" <== ");
|
||||||
|
stringBuilder.append("?????");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
stringBuilder.append(getConnectionDirection());
|
||||||
|
stringBuilder.append(channel.remoteAddress());
|
||||||
|
}
|
||||||
|
stringBuilder.append("]");
|
||||||
|
|
||||||
|
this.logger.info(stringBuilder.toString());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -276,7 +275,7 @@ class RegistrationRemoteHandler<C extends Connection> extends RegistrationHandle
|
|||||||
final
|
final
|
||||||
boolean verifyAesInfo(final Object message,
|
boolean verifyAesInfo(final Object message,
|
||||||
final Channel channel,
|
final Channel channel,
|
||||||
final RegistrationWrapper<C> registrationWrapper,
|
final RegistrationWrapper registrationWrapper,
|
||||||
final MetaChannel metaChannel,
|
final MetaChannel metaChannel,
|
||||||
final Logger logger) {
|
final Logger logger) {
|
||||||
|
|
||||||
|
@ -15,16 +15,14 @@
|
|||||||
*/
|
*/
|
||||||
package dorkbox.network.connection.registration.remote;
|
package dorkbox.network.connection.registration.remote;
|
||||||
|
|
||||||
import dorkbox.network.connection.Connection;
|
|
||||||
import dorkbox.network.connection.RegistrationWrapper;
|
import dorkbox.network.connection.RegistrationWrapper;
|
||||||
import dorkbox.network.serialization.CryptoSerializationManager;
|
import dorkbox.network.serialization.CryptoSerializationManager;
|
||||||
|
|
||||||
public
|
public
|
||||||
class RegistrationRemoteHandlerClient<C extends Connection> extends RegistrationRemoteHandler<C> {
|
class RegistrationRemoteHandlerClient extends RegistrationRemoteHandler {
|
||||||
|
|
||||||
public
|
|
||||||
RegistrationRemoteHandlerClient(final String name,
|
RegistrationRemoteHandlerClient(final String name,
|
||||||
final RegistrationWrapper<C> registrationWrapper,
|
final RegistrationWrapper registrationWrapper,
|
||||||
final CryptoSerializationManager serializationManager) {
|
final CryptoSerializationManager serializationManager) {
|
||||||
super(name, registrationWrapper, serializationManager);
|
super(name, registrationWrapper, serializationManager);
|
||||||
}
|
}
|
||||||
|
@ -31,10 +31,10 @@ import org.bouncycastle.jce.ECNamedCurveTable;
|
|||||||
import org.bouncycastle.jce.spec.ECParameterSpec;
|
import org.bouncycastle.jce.spec.ECParameterSpec;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
|
|
||||||
|
import com.esotericsoftware.kryo.KryoException;
|
||||||
import com.esotericsoftware.kryo.io.Input;
|
import com.esotericsoftware.kryo.io.Input;
|
||||||
import com.esotericsoftware.kryo.io.Output;
|
import com.esotericsoftware.kryo.io.Output;
|
||||||
|
|
||||||
import dorkbox.network.connection.Connection;
|
|
||||||
import dorkbox.network.connection.RegistrationWrapper;
|
import dorkbox.network.connection.RegistrationWrapper;
|
||||||
import dorkbox.network.connection.registration.MetaChannel;
|
import dorkbox.network.connection.registration.MetaChannel;
|
||||||
import dorkbox.network.connection.registration.Registration;
|
import dorkbox.network.connection.registration.Registration;
|
||||||
@ -49,14 +49,14 @@ import io.netty.channel.ChannelHandlerContext;
|
|||||||
import io.netty.util.ReferenceCountUtil;
|
import io.netty.util.ReferenceCountUtil;
|
||||||
|
|
||||||
public
|
public
|
||||||
class RegistrationRemoteHandlerClientTCP<C extends Connection> extends RegistrationRemoteHandlerClient<C> {
|
class RegistrationRemoteHandlerClientTCP extends RegistrationRemoteHandlerClient {
|
||||||
|
|
||||||
private static final String DELETE_IP = "eleteIP"; // purposefully missing the "D", since that is a system parameter, which starts with "-D"
|
private static final String DELETE_IP = "eleteIP"; // purposefully missing the "D", since that is a system parameter, which starts with "-D"
|
||||||
private static final ECParameterSpec eccSpec = ECNamedCurveTable.getParameterSpec(CryptoECC.curve25519);
|
private static final ECParameterSpec eccSpec = ECNamedCurveTable.getParameterSpec(CryptoECC.curve25519);
|
||||||
|
|
||||||
public
|
public
|
||||||
RegistrationRemoteHandlerClientTCP(final String name,
|
RegistrationRemoteHandlerClientTCP(final String name,
|
||||||
final RegistrationWrapper<C> registrationWrapper,
|
final RegistrationWrapper registrationWrapper,
|
||||||
final CryptoSerializationManager serializationManager) {
|
final CryptoSerializationManager serializationManager) {
|
||||||
super(name, registrationWrapper, serializationManager);
|
super(name, registrationWrapper, serializationManager);
|
||||||
|
|
||||||
@ -151,7 +151,7 @@ class RegistrationRemoteHandlerClientTCP<C extends Connection> extends Registrat
|
|||||||
void channelRead(final ChannelHandlerContext context, final Object message) throws Exception {
|
void channelRead(final ChannelHandlerContext context, final Object message) throws Exception {
|
||||||
Channel channel = context.channel();
|
Channel channel = context.channel();
|
||||||
|
|
||||||
RegistrationWrapper<C> registrationWrapper2 = this.registrationWrapper;
|
RegistrationWrapper registrationWrapper2 = this.registrationWrapper;
|
||||||
Logger logger2 = this.logger;
|
Logger logger2 = this.logger;
|
||||||
if (message instanceof Registration) {
|
if (message instanceof Registration) {
|
||||||
// make sure this connection was properly registered in the map. (IT SHOULD BE)
|
// make sure this connection was properly registered in the map. (IT SHOULD BE)
|
||||||
@ -232,8 +232,10 @@ class RegistrationRemoteHandlerClientTCP<C extends Connection> extends Registrat
|
|||||||
* see http://en.wikipedia.org/wiki/Diffie%E2%80%93Hellman_key_exchange
|
* see http://en.wikipedia.org/wiki/Diffie%E2%80%93Hellman_key_exchange
|
||||||
*/
|
*/
|
||||||
byte[] ecdhPubKeyBytes = Arrays.copyOfRange(payload, intLength, payload.length);
|
byte[] ecdhPubKeyBytes = Arrays.copyOfRange(payload, intLength, payload.length);
|
||||||
ECPublicKeyParameters ecdhPubKey = EccPublicKeySerializer.read(new Input(ecdhPubKeyBytes));
|
ECPublicKeyParameters ecdhPubKey;
|
||||||
if (ecdhPubKey == null) {
|
try {
|
||||||
|
ecdhPubKey = EccPublicKeySerializer.read(new Input(ecdhPubKeyBytes));
|
||||||
|
} catch (KryoException e) {
|
||||||
logger2.error("Invalid decode of ecdh public key. Aborting.");
|
logger2.error("Invalid decode of ecdh public key. Aborting.");
|
||||||
shutdown(registrationWrapper2, channel);
|
shutdown(registrationWrapper2, channel);
|
||||||
|
|
||||||
|
@ -21,7 +21,6 @@ import java.net.InetSocketAddress;
|
|||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
|
|
||||||
import dorkbox.network.connection.Connection;
|
|
||||||
import dorkbox.network.connection.EndPointBase;
|
import dorkbox.network.connection.EndPointBase;
|
||||||
import dorkbox.network.connection.RegistrationWrapper;
|
import dorkbox.network.connection.RegistrationWrapper;
|
||||||
import dorkbox.network.connection.registration.MetaChannel;
|
import dorkbox.network.connection.registration.MetaChannel;
|
||||||
@ -37,11 +36,11 @@ import io.netty.channel.ChannelPipeline;
|
|||||||
import io.netty.channel.FixedRecvByteBufAllocator;
|
import io.netty.channel.FixedRecvByteBufAllocator;
|
||||||
|
|
||||||
public
|
public
|
||||||
class RegistrationRemoteHandlerClientUDP<C extends Connection> extends RegistrationRemoteHandlerClient<C> {
|
class RegistrationRemoteHandlerClientUDP extends RegistrationRemoteHandlerClient {
|
||||||
|
|
||||||
public
|
public
|
||||||
RegistrationRemoteHandlerClientUDP(final String name,
|
RegistrationRemoteHandlerClientUDP(final String name,
|
||||||
final RegistrationWrapper<C> registrationWrapper,
|
final RegistrationWrapper registrationWrapper,
|
||||||
final CryptoSerializationManager serializationManager) {
|
final CryptoSerializationManager serializationManager) {
|
||||||
super(name, registrationWrapper, serializationManager);
|
super(name, registrationWrapper, serializationManager);
|
||||||
}
|
}
|
||||||
@ -118,7 +117,7 @@ class RegistrationRemoteHandlerClientUDP<C extends Connection> extends Registrat
|
|||||||
|
|
||||||
// if we also have a UDP channel, we will receive the "connected" message on UDP (otherwise it will be on TCP)
|
// if we also have a UDP channel, we will receive the "connected" message on UDP (otherwise it will be on TCP)
|
||||||
|
|
||||||
RegistrationWrapper<C> registrationWrapper2 = this.registrationWrapper;
|
RegistrationWrapper registrationWrapper2 = this.registrationWrapper;
|
||||||
MetaChannel metaChannel = registrationWrapper2.getChannel(channel.hashCode());
|
MetaChannel metaChannel = registrationWrapper2.getChannel(channel.hashCode());
|
||||||
|
|
||||||
if (metaChannel != null) {
|
if (metaChannel != null) {
|
||||||
|
@ -15,17 +15,15 @@
|
|||||||
*/
|
*/
|
||||||
package dorkbox.network.connection.registration.remote;
|
package dorkbox.network.connection.registration.remote;
|
||||||
|
|
||||||
import dorkbox.network.connection.Connection;
|
|
||||||
import dorkbox.network.connection.RegistrationWrapper;
|
import dorkbox.network.connection.RegistrationWrapper;
|
||||||
import dorkbox.network.connection.registration.MetaChannel;
|
import dorkbox.network.connection.registration.MetaChannel;
|
||||||
import dorkbox.network.serialization.CryptoSerializationManager;
|
import dorkbox.network.serialization.CryptoSerializationManager;
|
||||||
|
|
||||||
public
|
public
|
||||||
class RegistrationRemoteHandlerServer<C extends Connection> extends RegistrationRemoteHandler<C> {
|
class RegistrationRemoteHandlerServer extends RegistrationRemoteHandler {
|
||||||
|
|
||||||
public
|
|
||||||
RegistrationRemoteHandlerServer(final String name,
|
RegistrationRemoteHandlerServer(final String name,
|
||||||
final RegistrationWrapper<C> registrationWrapper,
|
final RegistrationWrapper registrationWrapper,
|
||||||
final CryptoSerializationManager serializationManager) {
|
final CryptoSerializationManager serializationManager) {
|
||||||
super(name, registrationWrapper, serializationManager);
|
super(name, registrationWrapper, serializationManager);
|
||||||
}
|
}
|
||||||
|
@ -32,10 +32,10 @@ import org.bouncycastle.jce.spec.ECParameterSpec;
|
|||||||
import org.bouncycastle.util.Arrays;
|
import org.bouncycastle.util.Arrays;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
|
|
||||||
|
import com.esotericsoftware.kryo.KryoException;
|
||||||
import com.esotericsoftware.kryo.io.Input;
|
import com.esotericsoftware.kryo.io.Input;
|
||||||
import com.esotericsoftware.kryo.io.Output;
|
import com.esotericsoftware.kryo.io.Output;
|
||||||
|
|
||||||
import dorkbox.network.connection.Connection;
|
|
||||||
import dorkbox.network.connection.RegistrationWrapper;
|
import dorkbox.network.connection.RegistrationWrapper;
|
||||||
import dorkbox.network.connection.registration.MetaChannel;
|
import dorkbox.network.connection.registration.MetaChannel;
|
||||||
import dorkbox.network.connection.registration.Registration;
|
import dorkbox.network.connection.registration.Registration;
|
||||||
@ -49,7 +49,7 @@ import io.netty.channel.ChannelHandlerContext;
|
|||||||
import io.netty.util.ReferenceCountUtil;
|
import io.netty.util.ReferenceCountUtil;
|
||||||
|
|
||||||
public
|
public
|
||||||
class RegistrationRemoteHandlerServerTCP<C extends Connection> extends RegistrationRemoteHandlerServer<C> {
|
class RegistrationRemoteHandlerServerTCP extends RegistrationRemoteHandlerServer {
|
||||||
|
|
||||||
private static final long ECDH_TIMEOUT = 10L * 60L * 60L * 1000L * 1000L * 1000L; // 10 minutes in nanoseconds
|
private static final long ECDH_TIMEOUT = 10L * 60L * 60L * 1000L * 1000L * 1000L; // 10 minutes in nanoseconds
|
||||||
|
|
||||||
@ -61,7 +61,7 @@ class RegistrationRemoteHandlerServerTCP<C extends Connection> extends Registrat
|
|||||||
|
|
||||||
public
|
public
|
||||||
RegistrationRemoteHandlerServerTCP(final String name,
|
RegistrationRemoteHandlerServerTCP(final String name,
|
||||||
final RegistrationWrapper<C> registrationWrapper,
|
final RegistrationWrapper registrationWrapper,
|
||||||
final CryptoSerializationManager serializationManager) {
|
final CryptoSerializationManager serializationManager) {
|
||||||
super(name, registrationWrapper, serializationManager);
|
super(name, registrationWrapper, serializationManager);
|
||||||
}
|
}
|
||||||
@ -264,8 +264,10 @@ class RegistrationRemoteHandlerServerTCP<C extends Connection> extends Registrat
|
|||||||
* Diffie-Hellman-Merkle key exchange for the AES key
|
* Diffie-Hellman-Merkle key exchange for the AES key
|
||||||
* see http://en.wikipedia.org/wiki/Diffie%E2%80%93Hellman_key_exchange
|
* see http://en.wikipedia.org/wiki/Diffie%E2%80%93Hellman_key_exchange
|
||||||
*/
|
*/
|
||||||
ECPublicKeyParameters ecdhPubKey = EccPublicKeySerializer.read(new Input(payload));
|
ECPublicKeyParameters ecdhPubKey;
|
||||||
if (ecdhPubKey == null) {
|
try {
|
||||||
|
ecdhPubKey = EccPublicKeySerializer.read(new Input(payload));
|
||||||
|
} catch (KryoException e) {
|
||||||
logger2.error("Invalid decode of ecdh public key. Aborting.");
|
logger2.error("Invalid decode of ecdh public key. Aborting.");
|
||||||
shutdown(registrationWrapper2, channel);
|
shutdown(registrationWrapper2, channel);
|
||||||
|
|
||||||
|
@ -23,7 +23,6 @@ import java.util.List;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
|
|
||||||
import dorkbox.network.Broadcast;
|
import dorkbox.network.Broadcast;
|
||||||
import dorkbox.network.connection.Connection;
|
|
||||||
import dorkbox.network.connection.ConnectionImpl;
|
import dorkbox.network.connection.ConnectionImpl;
|
||||||
import dorkbox.network.connection.EndPointBase;
|
import dorkbox.network.connection.EndPointBase;
|
||||||
import dorkbox.network.connection.KryoExtra;
|
import dorkbox.network.connection.KryoExtra;
|
||||||
@ -45,19 +44,18 @@ import io.netty.handler.codec.MessageToMessageCodec;
|
|||||||
|
|
||||||
@Sharable
|
@Sharable
|
||||||
public
|
public
|
||||||
class RegistrationRemoteHandlerServerUDP<C extends Connection> extends MessageToMessageCodec<DatagramPacket, UdpWrapper> {
|
class RegistrationRemoteHandlerServerUDP extends MessageToMessageCodec<DatagramPacket, UdpWrapper> {
|
||||||
|
|
||||||
// this is for the SERVER only. UDP channel is ALWAYS the SAME channel (it's the server's listening channel).
|
// this is for the SERVER only. UDP channel is ALWAYS the SAME channel (it's the server's listening channel).
|
||||||
|
|
||||||
private final org.slf4j.Logger logger;
|
private final org.slf4j.Logger logger;
|
||||||
private final ByteBuf discoverResponseBuffer;
|
private final ByteBuf discoverResponseBuffer;
|
||||||
private final RegistrationWrapper<C> registrationWrapper;
|
private final RegistrationWrapper registrationWrapper;
|
||||||
private final CryptoSerializationManager serializationManager;
|
private final CryptoSerializationManager serializationManager;
|
||||||
|
|
||||||
|
|
||||||
public
|
public
|
||||||
RegistrationRemoteHandlerServerUDP(final String name,
|
RegistrationRemoteHandlerServerUDP(final String name,
|
||||||
final RegistrationWrapper<C> registrationWrapper,
|
final RegistrationWrapper registrationWrapper,
|
||||||
final CryptoSerializationManager serializationManager) {
|
final CryptoSerializationManager serializationManager) {
|
||||||
final String name1 = name + " Registration-UDP-Server";
|
final String name1 = name + " Registration-UDP-Server";
|
||||||
this.logger = org.slf4j.LoggerFactory.getLogger(name1);
|
this.logger = org.slf4j.LoggerFactory.getLogger(name1);
|
||||||
@ -176,7 +174,7 @@ class RegistrationRemoteHandlerServerUDP<C extends Connection> extends MessageTo
|
|||||||
|
|
||||||
// registration is the ONLY thing NOT encrypted
|
// registration is the ONLY thing NOT encrypted
|
||||||
Logger logger2 = this.logger;
|
Logger logger2 = this.logger;
|
||||||
RegistrationWrapper<C> registrationWrapper2 = this.registrationWrapper;
|
RegistrationWrapper registrationWrapper2 = this.registrationWrapper;
|
||||||
CryptoSerializationManager serializationManager2 = this.serializationManager;
|
CryptoSerializationManager serializationManager2 = this.serializationManager;
|
||||||
|
|
||||||
if (KryoExtra.isEncrypted(message)) {
|
if (KryoExtra.isEncrypted(message)) {
|
||||||
@ -262,7 +260,7 @@ class RegistrationRemoteHandlerServerUDP<C extends Connection> extends MessageTo
|
|||||||
* Copied from RegistrationHandler. There were issues accessing it as static with generics.
|
* Copied from RegistrationHandler. There were issues accessing it as static with generics.
|
||||||
*/
|
*/
|
||||||
public
|
public
|
||||||
MetaChannel shutdown(final RegistrationWrapper<C> registrationWrapper, final Channel channel) {
|
MetaChannel shutdown(final RegistrationWrapper registrationWrapper, final Channel channel) {
|
||||||
this.logger.error("SHUTDOWN HANDLER REACHED! SOMETHING MESSED UP! TRYING TO ABORT");
|
this.logger.error("SHUTDOWN HANDLER REACHED! SOMETHING MESSED UP! TRYING TO ABORT");
|
||||||
|
|
||||||
// shutdown. Something messed up. Only reach this is something messed up.
|
// shutdown. Something messed up. Only reach this is something messed up.
|
||||||
|
@ -19,28 +19,31 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
|
|
||||||
import org.bouncycastle.crypto.params.ParametersWithIV;
|
import org.bouncycastle.crypto.params.ParametersWithIV;
|
||||||
|
|
||||||
import dorkbox.network.connection.Connection;
|
import dorkbox.network.connection.ConnectionImpl;
|
||||||
import dorkbox.network.connection.ConnectionPointWriter;
|
import dorkbox.network.connection.ConnectionPointWriter;
|
||||||
import dorkbox.network.connection.EndPointBase;
|
import dorkbox.network.connection.EndPointBase;
|
||||||
import dorkbox.network.connection.ISessionManager;
|
import dorkbox.network.connection.ISessionManager;
|
||||||
import dorkbox.network.connection.registration.MetaChannel;
|
import dorkbox.network.connection.registration.MetaChannel;
|
||||||
|
import dorkbox.network.rmi.RmiObjectHandler;
|
||||||
import io.netty.channel.Channel;
|
import io.netty.channel.Channel;
|
||||||
import io.netty.channel.EventLoop;
|
import io.netty.channel.EventLoop;
|
||||||
import io.netty.channel.local.LocalAddress;
|
import io.netty.channel.local.LocalAddress;
|
||||||
|
|
||||||
public
|
public
|
||||||
class ChannelLocalWrapper<C extends Connection> implements ChannelWrapper<C>, ConnectionPointWriter {
|
class ChannelLocalWrapper implements ChannelWrapper, ConnectionPointWriter {
|
||||||
|
|
||||||
private final Channel channel;
|
private final Channel channel;
|
||||||
|
private final RmiObjectHandler rmiObjectHandler;
|
||||||
|
|
||||||
private final AtomicBoolean shouldFlush = new AtomicBoolean(false);
|
private final AtomicBoolean shouldFlush = new AtomicBoolean(false);
|
||||||
private String remoteAddress;
|
private String remoteAddress;
|
||||||
|
|
||||||
public
|
public
|
||||||
ChannelLocalWrapper(MetaChannel metaChannel) {
|
ChannelLocalWrapper(MetaChannel metaChannel, final RmiObjectHandler rmiObjectHandler) {
|
||||||
this.channel = metaChannel.localChannel;
|
this.channel = metaChannel.localChannel;
|
||||||
|
this.rmiObjectHandler = rmiObjectHandler;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Write an object to the underlying channel
|
* Write an object to the underlying channel
|
||||||
*/
|
*/
|
||||||
@ -111,6 +114,12 @@ class ChannelLocalWrapper<C extends Connection> implements ChannelWrapper<C>, Co
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
RmiObjectHandler manageRmi() {
|
||||||
|
return rmiObjectHandler;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final
|
public final
|
||||||
String getRemoteHost() {
|
String getRemoteHost() {
|
||||||
@ -119,7 +128,7 @@ class ChannelLocalWrapper<C extends Connection> implements ChannelWrapper<C>, Co
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public
|
public
|
||||||
void close(Connection connection, ISessionManager<C> sessionManager) {
|
void close(ConnectionImpl connection, ISessionManager sessionManager) {
|
||||||
long maxShutdownWaitTimeInMilliSeconds = EndPointBase.maxShutdownWaitTimeInMilliSeconds;
|
long maxShutdownWaitTimeInMilliSeconds = EndPointBase.maxShutdownWaitTimeInMilliSeconds;
|
||||||
|
|
||||||
this.shouldFlush.set(false);
|
this.shouldFlush.set(false);
|
||||||
@ -153,7 +162,7 @@ class ChannelLocalWrapper<C extends Connection> implements ChannelWrapper<C>, Co
|
|||||||
if (getClass() != obj.getClass()) {
|
if (getClass() != obj.getClass()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
ChannelLocalWrapper<C> other = (ChannelLocalWrapper<C>) obj;
|
ChannelLocalWrapper other = (ChannelLocalWrapper) obj;
|
||||||
if (this.remoteAddress == null) {
|
if (this.remoteAddress == null) {
|
||||||
if (other.remoteAddress != null) {
|
if (other.remoteAddress != null) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -20,19 +20,20 @@ import java.net.InetSocketAddress;
|
|||||||
import org.bouncycastle.crypto.params.KeyParameter;
|
import org.bouncycastle.crypto.params.KeyParameter;
|
||||||
import org.bouncycastle.crypto.params.ParametersWithIV;
|
import org.bouncycastle.crypto.params.ParametersWithIV;
|
||||||
|
|
||||||
import dorkbox.network.connection.Connection;
|
import dorkbox.network.connection.ConnectionImpl;
|
||||||
import dorkbox.network.connection.ConnectionPointWriter;
|
import dorkbox.network.connection.ConnectionPointWriter;
|
||||||
import dorkbox.network.connection.EndPointBase;
|
import dorkbox.network.connection.EndPointBase;
|
||||||
import dorkbox.network.connection.ISessionManager;
|
import dorkbox.network.connection.ISessionManager;
|
||||||
import dorkbox.network.connection.UdpServer;
|
import dorkbox.network.connection.UdpServer;
|
||||||
import dorkbox.network.connection.registration.MetaChannel;
|
import dorkbox.network.connection.registration.MetaChannel;
|
||||||
|
import dorkbox.network.rmi.RmiObjectHandler;
|
||||||
import dorkbox.util.FastThreadLocal;
|
import dorkbox.util.FastThreadLocal;
|
||||||
import io.netty.channel.Channel;
|
import io.netty.channel.Channel;
|
||||||
import io.netty.channel.EventLoop;
|
import io.netty.channel.EventLoop;
|
||||||
import io.netty.util.NetUtil;
|
import io.netty.util.NetUtil;
|
||||||
|
|
||||||
public
|
public
|
||||||
class ChannelNetworkWrapper<C extends Connection> implements ChannelWrapper<C> {
|
class ChannelNetworkWrapper implements ChannelWrapper {
|
||||||
|
|
||||||
private final ChannelNetwork tcp;
|
private final ChannelNetwork tcp;
|
||||||
private final ChannelNetwork udp;
|
private final ChannelNetwork udp;
|
||||||
@ -49,12 +50,16 @@ class ChannelNetworkWrapper<C extends Connection> implements ChannelWrapper<C> {
|
|||||||
private final byte[] aesIV; // AES-GCM requires 12 bytes
|
private final byte[] aesIV; // AES-GCM requires 12 bytes
|
||||||
|
|
||||||
private final FastThreadLocal<ParametersWithIV> cryptoParameters;
|
private final FastThreadLocal<ParametersWithIV> cryptoParameters;
|
||||||
|
private final RmiObjectHandler rmiObjectHandler;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param udpServer is null when created by the client, non-null when created by the server
|
* @param udpServer is null when created by the client, non-null when created by the server
|
||||||
|
* @param rmiObjectHandler is a no-op handler if RMI is disabled, otherwise handles RMI object registration
|
||||||
*/
|
*/
|
||||||
public
|
public
|
||||||
ChannelNetworkWrapper(MetaChannel metaChannel, UdpServer udpServer) {
|
ChannelNetworkWrapper(MetaChannel metaChannel, UdpServer udpServer, final RmiObjectHandler rmiObjectHandler) {
|
||||||
|
|
||||||
|
this.rmiObjectHandler = rmiObjectHandler;
|
||||||
|
|
||||||
Channel tcpChannel = metaChannel.tcpChannel;
|
Channel tcpChannel = metaChannel.tcpChannel;
|
||||||
this.eventLoop = tcpChannel.eventLoop();
|
this.eventLoop = tcpChannel.eventLoop();
|
||||||
@ -138,7 +143,6 @@ class ChannelNetworkWrapper<C extends Connection> implements ChannelWrapper<C> {
|
|||||||
return this.eventLoop;
|
return this.eventLoop;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return a threadlocal AES key + IV. key=32 byte, iv=12 bytes (AES-GCM implementation). This is a threadlocal
|
* @return a threadlocal AES key + IV. key=32 byte, iv=12 bytes (AES-GCM implementation). This is a threadlocal
|
||||||
* because multiple protocols can be performing crypto AT THE SAME TIME, and so we have to make sure that operations don't
|
* because multiple protocols can be performing crypto AT THE SAME TIME, and so we have to make sure that operations don't
|
||||||
@ -156,16 +160,21 @@ class ChannelNetworkWrapper<C extends Connection> implements ChannelWrapper<C> {
|
|||||||
return isLoopback;
|
return isLoopback;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
RmiObjectHandler manageRmi() {
|
||||||
|
return rmiObjectHandler;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public
|
public
|
||||||
String getRemoteHost() {
|
String getRemoteHost() {
|
||||||
return this.remoteAddress;
|
return this.remoteAddress;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public
|
public
|
||||||
void close(final Connection connection, final ISessionManager<C> sessionManager) {
|
void close(final ConnectionImpl connection, final ISessionManager sessionManager) {
|
||||||
long maxShutdownWaitTimeInMilliSeconds = EndPointBase.maxShutdownWaitTimeInMilliSeconds;
|
long maxShutdownWaitTimeInMilliSeconds = EndPointBase.maxShutdownWaitTimeInMilliSeconds;
|
||||||
|
|
||||||
this.tcp.close(maxShutdownWaitTimeInMilliSeconds);
|
this.tcp.close(maxShutdownWaitTimeInMilliSeconds);
|
||||||
@ -203,7 +212,6 @@ class ChannelNetworkWrapper<C extends Connection> implements ChannelWrapper<C> {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("rawtypes")
|
|
||||||
ChannelNetworkWrapper other = (ChannelNetworkWrapper) obj;
|
ChannelNetworkWrapper other = (ChannelNetworkWrapper) obj;
|
||||||
if (this.remoteAddress == null) {
|
if (this.remoteAddress == null) {
|
||||||
if (other.remoteAddress != null) {
|
if (other.remoteAddress != null) {
|
||||||
|
@ -17,16 +17,16 @@ package dorkbox.network.connection.wrapper;
|
|||||||
|
|
||||||
import org.bouncycastle.crypto.params.ParametersWithIV;
|
import org.bouncycastle.crypto.params.ParametersWithIV;
|
||||||
|
|
||||||
import dorkbox.network.connection.Connection;
|
import dorkbox.network.connection.ConnectionImpl;
|
||||||
import dorkbox.network.connection.ConnectionPointWriter;
|
import dorkbox.network.connection.ConnectionPointWriter;
|
||||||
import dorkbox.network.connection.ISessionManager;
|
import dorkbox.network.connection.ISessionManager;
|
||||||
|
import dorkbox.network.rmi.RmiObjectHandler;
|
||||||
import io.netty.channel.EventLoop;
|
import io.netty.channel.EventLoop;
|
||||||
|
|
||||||
public
|
public
|
||||||
interface ChannelWrapper<C extends Connection> {
|
interface ChannelWrapper {
|
||||||
|
|
||||||
ConnectionPointWriter tcp();
|
ConnectionPointWriter tcp();
|
||||||
|
|
||||||
ConnectionPointWriter udp();
|
ConnectionPointWriter udp();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -54,12 +54,14 @@ interface ChannelWrapper<C extends Connection> {
|
|||||||
*/
|
*/
|
||||||
boolean isLoopback();
|
boolean isLoopback();
|
||||||
|
|
||||||
|
RmiObjectHandler manageRmi();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return the remote host (can be local, tcp, udp)
|
* @return the remote host (can be local, tcp, udp)
|
||||||
*/
|
*/
|
||||||
String getRemoteHost();
|
String getRemoteHost();
|
||||||
|
|
||||||
void close(final Connection connection, final ISessionManager<C> sessionManager);
|
void close(ConnectionImpl connection, ISessionManager sessionManager);
|
||||||
|
|
||||||
int id();
|
int id();
|
||||||
|
|
||||||
|
@ -39,8 +39,9 @@ package dorkbox.network.rmi;
|
|||||||
* Internal message to invoke methods remotely.
|
* Internal message to invoke methods remotely.
|
||||||
*/
|
*/
|
||||||
public
|
public
|
||||||
class InvokeMethod implements RmiMessages {
|
class InvokeMethod implements RmiMessage {
|
||||||
public int objectID; // the registered kryo ID for the object
|
public int objectID; // the registered kryo ID for the object
|
||||||
|
|
||||||
public CachedMethod cachedMethod;
|
public CachedMethod cachedMethod;
|
||||||
public Object[] args;
|
public Object[] args;
|
||||||
|
|
||||||
@ -49,7 +50,6 @@ class InvokeMethod implements RmiMessages {
|
|||||||
// possible duplicate IDs. A response data of 0 means to not respond.
|
// possible duplicate IDs. A response data of 0 means to not respond.
|
||||||
public byte responseData;
|
public byte responseData;
|
||||||
|
|
||||||
public
|
|
||||||
InvokeMethod() {
|
InvokeMethod() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -38,8 +38,9 @@ package dorkbox.network.rmi;
|
|||||||
* Internal message to return the result of a remotely invoked method.
|
* Internal message to return the result of a remotely invoked method.
|
||||||
*/
|
*/
|
||||||
public
|
public
|
||||||
class InvokeMethodResult implements RmiMessages {
|
class InvokeMethodResult implements RmiMessage {
|
||||||
public int objectID;
|
public int objectID;
|
||||||
|
|
||||||
public byte responseID;
|
public byte responseID;
|
||||||
public Object result;
|
public Object result;
|
||||||
}
|
}
|
||||||
|
@ -1,24 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2010 dorkbox, llc
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package dorkbox.network.rmi;
|
|
||||||
|
|
||||||
import dorkbox.network.connection.Connection;
|
|
||||||
import dorkbox.network.connection.Listener;
|
|
||||||
|
|
||||||
abstract
|
|
||||||
class RemoteInvocationResponse<C extends Connection> implements Listener.OnDisconnected<C>,
|
|
||||||
Listener.OnMessageReceived<C, InvokeMethodResult> {
|
|
||||||
}
|
|
@ -35,7 +35,6 @@
|
|||||||
package dorkbox.network.rmi;
|
package dorkbox.network.rmi;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.lang.reflect.Proxy;
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
@ -188,7 +187,7 @@ class RmiBridge {
|
|||||||
*/
|
*/
|
||||||
@SuppressWarnings("rawtypes")
|
@SuppressWarnings("rawtypes")
|
||||||
public
|
public
|
||||||
Listener getListener() {
|
Listener.OnMessageReceived<ConnectionImpl, InvokeMethod> getListener() {
|
||||||
return this.invokeListener;
|
return this.invokeListener;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -412,49 +411,4 @@ class RmiBridge {
|
|||||||
|
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Warning. This is an advanced method. You should probably be using {@link Connection#createRemoteObject(Class, RemoteObjectCallback)}
|
|
||||||
* <p>
|
|
||||||
* <p>
|
|
||||||
* Returns a proxy object that implements the specified interfaces. Methods invoked on the proxy object will be invoked remotely on the
|
|
||||||
* object with the specified ID in the ObjectSpace for the specified connection. If the remote end of the connection has not {@link
|
|
||||||
* RmiBridge#register(int, Object)} added the connection to the ObjectSpace, the remote method invocations will be ignored.
|
|
||||||
* <p/>
|
|
||||||
* Methods that return a value will throw {@link TimeoutException} if the response is not received with the {@link
|
|
||||||
* RemoteObject#setResponseTimeout(int) response timeout}.
|
|
||||||
* <p/>
|
|
||||||
* If {@link RemoteObject#setAsync(boolean) non-blocking} is false (the default), then methods that return a value must not be
|
|
||||||
* called from the update thread for the connection. An exception will be thrown if this occurs. Methods with a void return value can be
|
|
||||||
* called on the update thread.
|
|
||||||
* <p/>
|
|
||||||
* If a proxy returned from this method is part of an object graph sent over the network, the object graph on the receiving side will
|
|
||||||
* have the proxy object replaced with the registered object.
|
|
||||||
*
|
|
||||||
* @see RemoteObject
|
|
||||||
*
|
|
||||||
* @param connection this is really the network client -- there is ONLY ever 1 connection
|
|
||||||
* @param objectID this is the remote object ID (assigned by RMI). This is NOT the kryo registration ID
|
|
||||||
* @param iFace this is the RMI interface
|
|
||||||
*/
|
|
||||||
public
|
|
||||||
RemoteObject createProxyObject(Connection connection, int objectID, Class<?> iFace) {
|
|
||||||
if (connection == null) {
|
|
||||||
throw new IllegalArgumentException("connection cannot be null.");
|
|
||||||
}
|
|
||||||
if (iFace == null) {
|
|
||||||
throw new IllegalArgumentException("iface cannot be null.");
|
|
||||||
}
|
|
||||||
if (!iFace.isInterface()) {
|
|
||||||
throw new IllegalArgumentException("iface must be an interface.");
|
|
||||||
}
|
|
||||||
|
|
||||||
Class<?>[] temp = new Class<?>[2];
|
|
||||||
temp[0] = RemoteObject.class;
|
|
||||||
temp[1] = iFace;
|
|
||||||
|
|
||||||
return (RemoteObject) Proxy.newProxyInstance(RmiBridge.class.getClassLoader(),
|
|
||||||
temp,
|
|
||||||
new RmiProxyHandler(connection, objectID, iFace));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
22
src/dorkbox/network/rmi/RmiMessage.java
Normal file
22
src/dorkbox/network/rmi/RmiMessage.java
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2018 dorkbox, llc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package dorkbox.network.rmi;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
interface RmiMessage {}
|
@ -1,20 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2010 dorkbox, llc
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package dorkbox.network.rmi;
|
|
||||||
|
|
||||||
// used by RMI. This is to keep track and not throw errors when connections are notified of it's arrival
|
|
||||||
public
|
|
||||||
interface RmiMessages {}
|
|
40
src/dorkbox/network/rmi/RmiObjectHandler.java
Normal file
40
src/dorkbox/network/rmi/RmiObjectHandler.java
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2018 dorkbox, llc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package dorkbox.network.rmi;
|
||||||
|
|
||||||
|
import dorkbox.network.connection.ConnectionImpl;
|
||||||
|
import dorkbox.network.connection.Listener;
|
||||||
|
|
||||||
|
public
|
||||||
|
class RmiObjectHandler {
|
||||||
|
|
||||||
|
public
|
||||||
|
RmiObjectHandler() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public
|
||||||
|
void invoke(final ConnectionImpl connection, final InvokeMethod message, final Listener.OnMessageReceived<ConnectionImpl, InvokeMethod> rmiInvokeListener) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public
|
||||||
|
void registration(final ConnectionImpl connection, final RmiRegistration message) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public
|
||||||
|
Object normalMessages(final ConnectionImpl connection, final Object message) {
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
}
|
@ -13,28 +13,22 @@
|
|||||||
* 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.connection;
|
package dorkbox.network.rmi;
|
||||||
|
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.lang.reflect.Proxy;
|
import java.lang.reflect.Proxy;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
|
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
|
||||||
|
|
||||||
import com.esotericsoftware.kryo.KryoException;
|
import com.esotericsoftware.kryo.KryoException;
|
||||||
import com.esotericsoftware.kryo.Serializer;
|
import com.esotericsoftware.kryo.Serializer;
|
||||||
import com.esotericsoftware.kryo.util.IdentityMap;
|
import com.esotericsoftware.kryo.util.IdentityMap;
|
||||||
|
|
||||||
import dorkbox.network.rmi.CachedMethod;
|
import dorkbox.network.connection.ConnectionImpl;
|
||||||
import dorkbox.network.rmi.InvokeMethod;
|
import dorkbox.network.connection.EndPointBase;
|
||||||
import dorkbox.network.rmi.RemoteObject;
|
import dorkbox.network.connection.KryoExtra;
|
||||||
import dorkbox.network.rmi.RmiBridge;
|
import dorkbox.network.connection.Listener;
|
||||||
import dorkbox.network.rmi.RmiMessages;
|
|
||||||
import dorkbox.network.rmi.RmiProxyHandler;
|
|
||||||
import dorkbox.network.rmi.RmiRegistration;
|
|
||||||
import dorkbox.network.serialization.CryptoSerializationManager;
|
import dorkbox.network.serialization.CryptoSerializationManager;
|
||||||
import io.netty.channel.ChannelHandlerContext;
|
|
||||||
import io.netty.handler.codec.MessageToMessageDecoder;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is for a local-connection (same-JVM) RMI method invocation
|
* This is for a local-connection (same-JVM) RMI method invocation
|
||||||
@ -44,22 +38,22 @@ import io.netty.handler.codec.MessageToMessageDecoder;
|
|||||||
* This is for a LOCAL connection (same-JVM)
|
* This is for a LOCAL connection (same-JVM)
|
||||||
*/
|
*/
|
||||||
public
|
public
|
||||||
class RegisterRmiLocalHandler extends MessageToMessageDecoder<Object> {
|
class RmiObjectLocalHandler extends RmiObjectHandler {
|
||||||
private static final boolean ENABLE_PROXY_OBJECTS = ConnectionImpl.ENABLE_PROXY_OBJECTS;
|
private static final boolean ENABLE_PROXY_OBJECTS = ConnectionImpl.ENABLE_PROXY_OBJECTS;
|
||||||
private static final Field[] NO_REMOTE_FIELDS = new Field[0];
|
private static final Field[] NO_REMOTE_FIELDS = new Field[0];
|
||||||
|
|
||||||
// private static final AtomicReferenceFieldUpdater<RegisterRmiLocalHandler, IdentityMap> rmiFieldsREF = AtomicReferenceFieldUpdater.newUpdater(
|
// private static final AtomicReferenceFieldUpdater<RmiObjectLocalHandler, IdentityMap> rmiFieldsREF = AtomicReferenceFieldUpdater.newUpdater(
|
||||||
// RegisterRmiLocalHandler.class,
|
// RmiObjectLocalHandler.class,
|
||||||
// IdentityMap.class,
|
// IdentityMap.class,
|
||||||
// "fieldCache");
|
// "fieldCache");
|
||||||
|
|
||||||
private static final AtomicReferenceFieldUpdater<RegisterRmiLocalHandler, IdentityMap> implToProxyREF = AtomicReferenceFieldUpdater.newUpdater(
|
private static final AtomicReferenceFieldUpdater<RmiObjectLocalHandler, IdentityMap> implToProxyREF = AtomicReferenceFieldUpdater.newUpdater(
|
||||||
RegisterRmiLocalHandler.class,
|
RmiObjectLocalHandler.class,
|
||||||
IdentityMap.class,
|
IdentityMap.class,
|
||||||
"implToProxy");
|
"implToProxy");
|
||||||
|
|
||||||
private static final AtomicReferenceFieldUpdater<RegisterRmiLocalHandler, IdentityMap> remoteObjectREF = AtomicReferenceFieldUpdater.newUpdater(
|
private static final AtomicReferenceFieldUpdater<RmiObjectLocalHandler, IdentityMap> remoteObjectREF = AtomicReferenceFieldUpdater.newUpdater(
|
||||||
RegisterRmiLocalHandler.class,
|
RmiObjectLocalHandler.class,
|
||||||
IdentityMap.class,
|
IdentityMap.class,
|
||||||
"objectHasRemoteObjects");
|
"objectHasRemoteObjects");
|
||||||
|
|
||||||
@ -69,215 +63,74 @@ class RegisterRmiLocalHandler extends MessageToMessageDecoder<Object> {
|
|||||||
|
|
||||||
|
|
||||||
public
|
public
|
||||||
RegisterRmiLocalHandler() {
|
RmiObjectLocalHandler() {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected
|
public
|
||||||
void decode(final ChannelHandlerContext context, final Object msg, final List<Object> out) throws Exception {
|
void invoke(final ConnectionImpl connection, final InvokeMethod invokeMethod, final Listener.OnMessageReceived<ConnectionImpl, InvokeMethod> rmiInvokeListener) {
|
||||||
ConnectionImpl connection = (ConnectionImpl) context.pipeline()
|
int methodClassID = invokeMethod.cachedMethod.methodClassID;
|
||||||
.last();
|
int methodIndex = invokeMethod.cachedMethod.methodIndex;
|
||||||
|
// have to replace the cached methods with the correct (remote) version, otherwise the wrong methods CAN BE invoked.
|
||||||
|
|
||||||
if (msg instanceof RmiRegistration) {
|
CryptoSerializationManager serialization = connection.getEndPoint()
|
||||||
receivedRegistration(connection, (RmiRegistration) msg);
|
.getSerialization();
|
||||||
|
|
||||||
|
|
||||||
|
CachedMethod cachedMethod;
|
||||||
|
try {
|
||||||
|
cachedMethod = serialization.getMethods(methodClassID)[methodIndex];
|
||||||
|
} catch (Exception ex) {
|
||||||
|
String errorMessage;
|
||||||
|
KryoExtra kryo = null;
|
||||||
|
try {
|
||||||
|
kryo = serialization.takeKryo();
|
||||||
|
|
||||||
|
Class<?> methodClass = kryo.getRegistration(methodClassID)
|
||||||
|
.getType();
|
||||||
|
|
||||||
|
errorMessage = "Invalid method index " + methodIndex + " for class: " + methodClass.getName();
|
||||||
|
} finally {
|
||||||
|
serialization.returnKryo(kryo);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new KryoException(errorMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Object[] args;
|
||||||
|
Serializer<?>[] serializers = cachedMethod.serializers;
|
||||||
|
|
||||||
|
int argStartIndex;
|
||||||
|
|
||||||
|
if (cachedMethod.overriddenMethod) {
|
||||||
|
// did we override our cached method? This is not common.
|
||||||
|
// this is specifically when we override an interface method, with an implementation method + Connection parameter (@ index 0)
|
||||||
|
argStartIndex = 1;
|
||||||
|
|
||||||
|
args = new Object[serializers.length + 1];
|
||||||
|
args[0] = connection;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if (msg instanceof InvokeMethod) {
|
argStartIndex = 0;
|
||||||
InvokeMethod invokeMethod = (InvokeMethod) msg;
|
args = new Object[serializers.length];
|
||||||
int methodClassID = invokeMethod.cachedMethod.methodClassID;
|
|
||||||
int methodIndex = invokeMethod.cachedMethod.methodIndex;
|
|
||||||
// have to replace the cached methods with the correct (remote) version, otherwise the wrong methods CAN BE invoked.
|
|
||||||
|
|
||||||
CryptoSerializationManager serialization = connection.getEndPoint()
|
|
||||||
.getSerialization();
|
|
||||||
|
|
||||||
|
|
||||||
CachedMethod cachedMethod;
|
|
||||||
try {
|
|
||||||
cachedMethod = serialization.getMethods(methodClassID)[methodIndex];
|
|
||||||
} catch (Exception ex) {
|
|
||||||
String errorMessage;
|
|
||||||
KryoExtra kryo = null;
|
|
||||||
try {
|
|
||||||
kryo = serialization.takeKryo();
|
|
||||||
|
|
||||||
Class<?> methodClass = kryo.getRegistration(methodClassID)
|
|
||||||
.getType();
|
|
||||||
|
|
||||||
errorMessage = "Invalid method index " + methodIndex + " for class: " + methodClass.getName();
|
|
||||||
} finally {
|
|
||||||
serialization.returnKryo(kryo);
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new KryoException(errorMessage);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
Object[] args;
|
|
||||||
Serializer<?>[] serializers = cachedMethod.serializers;
|
|
||||||
|
|
||||||
int argStartIndex;
|
|
||||||
|
|
||||||
if (cachedMethod.overriddenMethod) {
|
|
||||||
// did we override our cached method? This is not common.
|
|
||||||
// this is specifically when we override an interface method, with an implementation method + Connection parameter (@ index 0)
|
|
||||||
argStartIndex = 1;
|
|
||||||
|
|
||||||
args = new Object[serializers.length + 1];
|
|
||||||
args[0] = connection;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
argStartIndex = 0;
|
|
||||||
args = new Object[serializers.length];
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0, n = serializers.length, j = argStartIndex; i < n; i++, j++) {
|
|
||||||
args[j] = invokeMethod.args[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
// overwrite the invoke method fields with UPDATED versions that have the correct (remote side) implementation/args
|
|
||||||
invokeMethod.cachedMethod = cachedMethod;
|
|
||||||
invokeMethod.args = args;
|
|
||||||
}
|
|
||||||
|
|
||||||
receivedNormal(connection, msg, out);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (int i = 0, n = serializers.length, j = argStartIndex; i < n; i++, j++) {
|
||||||
|
args[j] = invokeMethod.args[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
// overwrite the invoke method fields with UPDATED versions that have the correct (remote side) implementation/args
|
||||||
|
invokeMethod.cachedMethod = cachedMethod;
|
||||||
|
invokeMethod.args = args;
|
||||||
|
|
||||||
|
// default action, now that we have swapped out things
|
||||||
|
rmiInvokeListener.received(connection, invokeMethod);
|
||||||
}
|
}
|
||||||
|
|
||||||
private
|
@Override
|
||||||
void receivedNormal(final ConnectionImpl connection, final Object msg, final List<Object> out) {
|
public
|
||||||
// else, this was "just a local message"
|
void registration(final ConnectionImpl connection, final RmiRegistration registration) {
|
||||||
|
|
||||||
if (msg instanceof RmiMessages) {
|
|
||||||
// don't even process these message types
|
|
||||||
out.add(msg);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// because we NORMALLY pass around just the object (there is no serialization going on...) we have to explicitly check to see
|
|
||||||
// if this object, or any of it's fields MIGHT HAVE BEEN an RMI Proxy (or should be on), and switcheroo it here.
|
|
||||||
// NORMALLY this is automatic since the kryo IDs on each side point to the "correct object" for serialization, but here we don't do that.
|
|
||||||
|
|
||||||
// maybe this object is supposed to switch to a proxy object?? (note: we cannot send proxy objects over local/network connections)
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
IdentityMap<Object, Object> implToProxy = implToProxyREF.get(this);
|
|
||||||
IdentityMap<Object, Field[]> objectHasRemoteObjects = remoteObjectREF.get(this);
|
|
||||||
|
|
||||||
|
|
||||||
Object proxy = implToProxy.get(msg);
|
|
||||||
if (proxy != null) {
|
|
||||||
// we have a proxy object. nothing left to do.
|
|
||||||
out.add(proxy);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
Class<?> messageClass = msg.getClass();
|
|
||||||
|
|
||||||
// are there any fields of this message class that COULD contain remote object fields? (NOTE: not RMI fields yet...)
|
|
||||||
final Field[] remoteObjectFields = objectHasRemoteObjects.get(messageClass);
|
|
||||||
if (remoteObjectFields == null) {
|
|
||||||
// maybe one of it's fields is a proxy object?
|
|
||||||
|
|
||||||
// we cache the fields that have to be replaced, so subsequent invocations are significantly more preformat
|
|
||||||
final ArrayList<Field> fields = new ArrayList<Field>();
|
|
||||||
|
|
||||||
// we have to walk the hierarchy of this object to check ALL fields, public and private, using getDeclaredFields()
|
|
||||||
while (messageClass != Object.class) {
|
|
||||||
// this will get ALL fields that are
|
|
||||||
for (Field field : messageClass.getDeclaredFields()) {
|
|
||||||
final Class<?> type = field.getType();
|
|
||||||
|
|
||||||
if (type.isInterface()) {
|
|
||||||
boolean prev = field.isAccessible();
|
|
||||||
final Object o;
|
|
||||||
try {
|
|
||||||
field.setAccessible(true);
|
|
||||||
o = field.get(msg);
|
|
||||||
|
|
||||||
if (o instanceof RemoteObject) {
|
|
||||||
RmiProxyHandler handler = (RmiProxyHandler) Proxy.getInvocationHandler(o);
|
|
||||||
|
|
||||||
int id = handler.objectID;
|
|
||||||
field.set(msg, connection.getImplementationObject(id));
|
|
||||||
fields.add(field);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// is a field supposed to be a proxy?
|
|
||||||
proxy = implToProxy.get(o);
|
|
||||||
if (proxy != null) {
|
|
||||||
field.set(msg, proxy);
|
|
||||||
fields.add(field);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (IllegalAccessException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
// logger.error("Error checking RMI fields for: {}.{}", remoteClassObject.getKey(), field.getName(), e);
|
|
||||||
} finally {
|
|
||||||
field.setAccessible(prev);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
messageClass = messageClass.getSuperclass();
|
|
||||||
}
|
|
||||||
|
|
||||||
Field[] array;
|
|
||||||
if (fields.isEmpty()) {
|
|
||||||
// no need to ever process this class again.
|
|
||||||
array = NO_REMOTE_FIELDS;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
array = fields.toArray(new Field[fields.size()]);
|
|
||||||
}
|
|
||||||
|
|
||||||
//noinspection SynchronizeOnNonFinalField
|
|
||||||
synchronized (objectHasRemoteObjects) {
|
|
||||||
// i know what I'm doing. This must be synchronized.
|
|
||||||
objectHasRemoteObjects.put(messageClass, array);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (remoteObjectFields != NO_REMOTE_FIELDS) {
|
|
||||||
// quickly replace objects as necessary
|
|
||||||
|
|
||||||
for (Field field : remoteObjectFields) {
|
|
||||||
boolean prev = field.isAccessible();
|
|
||||||
final Object o;
|
|
||||||
try {
|
|
||||||
field.setAccessible(true);
|
|
||||||
o = field.get(msg);
|
|
||||||
|
|
||||||
if (o instanceof RemoteObject) {
|
|
||||||
RmiProxyHandler handler = (RmiProxyHandler) Proxy.getInvocationHandler(o);
|
|
||||||
|
|
||||||
int id = handler.objectID;
|
|
||||||
field.set(msg, connection.getImplementationObject(id));
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// is a field supposed to be a proxy?
|
|
||||||
proxy = implToProxy.get(o);
|
|
||||||
if (proxy != null) {
|
|
||||||
field.set(msg, proxy);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (IllegalAccessException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
// logger.error("Error checking RMI fields for: {}.{}", remoteClassObject.getKey(), field.getName(), e);
|
|
||||||
} finally {
|
|
||||||
field.setAccessible(prev);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
out.add(msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private
|
|
||||||
void receivedRegistration(final ConnectionImpl connection, final RmiRegistration registration) {
|
|
||||||
// manage creating/getting/notifying this RMI object
|
// manage creating/getting/notifying this RMI object
|
||||||
|
|
||||||
// these fields are ALWAYS present!
|
// these fields are ALWAYS present!
|
||||||
@ -292,7 +145,7 @@ class RegisterRmiLocalHandler extends MessageToMessageDecoder<Object> {
|
|||||||
|
|
||||||
|
|
||||||
// have to convert the iFace -> Impl
|
// have to convert the iFace -> Impl
|
||||||
EndPointBase<Connection> endPoint = connection.getEndPoint();
|
EndPointBase endPoint = connection.getEndPoint();
|
||||||
CryptoSerializationManager serialization = endPoint.getSerialization();
|
CryptoSerializationManager serialization = endPoint.getSerialization();
|
||||||
|
|
||||||
Class<?> rmiImpl = serialization.getRmiImpl(registration.interfaceClass);
|
Class<?> rmiImpl = serialization.getRmiImpl(registration.interfaceClass);
|
||||||
@ -344,6 +197,135 @@ class RegisterRmiLocalHandler extends MessageToMessageDecoder<Object> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
Object normalMessages(final ConnectionImpl connection, final Object message) {
|
||||||
|
// else, this was "just a local message"
|
||||||
|
|
||||||
|
// because we NORMALLY pass around just the object (there is no serialization going on...) we have to explicitly check to see
|
||||||
|
// if this object, or any of it's fields MIGHT HAVE BEEN an RMI Proxy (or should be on), and switcheroo it here.
|
||||||
|
// NORMALLY this is automatic since the kryo IDs on each side point to the "correct object" for serialization, but here we don't do that.
|
||||||
|
|
||||||
|
// maybe this object is supposed to switch to a proxy object?? (note: we cannot send proxy objects over local/network connections)
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
IdentityMap<Object, Object> implToProxy = implToProxyREF.get(this);
|
||||||
|
IdentityMap<Object, Field[]> objectHasRemoteObjects = remoteObjectREF.get(this);
|
||||||
|
|
||||||
|
|
||||||
|
Object proxy = implToProxy.get(message);
|
||||||
|
if (proxy != null) {
|
||||||
|
// we have a proxy object. nothing left to do.
|
||||||
|
return proxy;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// otherwise we MIGHT have to modify the fields in the object...
|
||||||
|
|
||||||
|
|
||||||
|
Class<?> messageClass = message.getClass();
|
||||||
|
|
||||||
|
// are there any fields of this message class that COULD contain remote object fields? (NOTE: not RMI fields yet...)
|
||||||
|
final Field[] remoteObjectFields = objectHasRemoteObjects.get(messageClass);
|
||||||
|
if (remoteObjectFields == null) {
|
||||||
|
// maybe one of it's fields is a proxy object?
|
||||||
|
|
||||||
|
// we cache the fields that have to be replaced, so subsequent invocations are significantly more preformat
|
||||||
|
final ArrayList<Field> fields = new ArrayList<Field>();
|
||||||
|
|
||||||
|
// we have to walk the hierarchy of this object to check ALL fields, public and private, using getDeclaredFields()
|
||||||
|
while (messageClass != Object.class) {
|
||||||
|
// this will get ALL fields that are
|
||||||
|
for (Field field : messageClass.getDeclaredFields()) {
|
||||||
|
final Class<?> type = field.getType();
|
||||||
|
|
||||||
|
if (type.isInterface()) {
|
||||||
|
boolean prev = field.isAccessible();
|
||||||
|
final Object o;
|
||||||
|
try {
|
||||||
|
field.setAccessible(true);
|
||||||
|
o = field.get(message);
|
||||||
|
|
||||||
|
if (o instanceof RemoteObject) {
|
||||||
|
RmiProxyHandler handler = (RmiProxyHandler) Proxy.getInvocationHandler(o);
|
||||||
|
|
||||||
|
int id = handler.objectID;
|
||||||
|
field.set(message, connection.getImplementationObject(id));
|
||||||
|
fields.add(field);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// is a field supposed to be a proxy?
|
||||||
|
proxy = implToProxy.get(o);
|
||||||
|
if (proxy != null) {
|
||||||
|
field.set(message, proxy);
|
||||||
|
fields.add(field);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (IllegalAccessException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
// logger.error("Error checking RMI fields for: {}.{}", remoteClassObject.getKey(), field.getName(), e);
|
||||||
|
} finally {
|
||||||
|
field.setAccessible(prev);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
messageClass = messageClass.getSuperclass();
|
||||||
|
}
|
||||||
|
|
||||||
|
Field[] array;
|
||||||
|
if (fields.isEmpty()) {
|
||||||
|
// no need to ever process this class again.
|
||||||
|
array = NO_REMOTE_FIELDS;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
array = fields.toArray(new Field[fields.size()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
//noinspection SynchronizeOnNonFinalField
|
||||||
|
synchronized (this.objectHasRemoteObjects) {
|
||||||
|
// i know what I'm doing. This must be synchronized.
|
||||||
|
this.objectHasRemoteObjects.put(messageClass, array);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (remoteObjectFields != NO_REMOTE_FIELDS) {
|
||||||
|
// quickly replace objects as necessary
|
||||||
|
|
||||||
|
for (Field field : remoteObjectFields) {
|
||||||
|
boolean prev = field.isAccessible();
|
||||||
|
final Object o;
|
||||||
|
try {
|
||||||
|
field.setAccessible(true);
|
||||||
|
o = field.get(message);
|
||||||
|
|
||||||
|
if (o instanceof RemoteObject) {
|
||||||
|
RmiProxyHandler handler = (RmiProxyHandler) Proxy.getInvocationHandler(o);
|
||||||
|
|
||||||
|
int id = handler.objectID;
|
||||||
|
field.set(message, connection.getImplementationObject(id));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// is a field supposed to be a proxy?
|
||||||
|
proxy = implToProxy.get(o);
|
||||||
|
if (proxy != null) {
|
||||||
|
field.set(message, proxy);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (IllegalAccessException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
// logger.error("Error checking RMI fields for: {}.{}", remoteClassObject.getKey(), field.getName(), e);
|
||||||
|
} finally {
|
||||||
|
field.setAccessible(prev);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// private
|
// private
|
||||||
// LocalRmiClassEncoder replaceFieldObjects(final ConnectionImpl connection, final Object object, final Class<?> implClass) {
|
// LocalRmiClassEncoder replaceFieldObjects(final ConnectionImpl connection, final Object object, final Class<?> implClass) {
|
||||||
// Field[] rmiFields = fieldCache.get(implClass);
|
// Field[] rmiFields = fieldCache.get(implClass);
|
@ -1,31 +1,40 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2010 dorkbox, llc
|
* Copyright 2018 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.connection;
|
package dorkbox.network.rmi;
|
||||||
|
|
||||||
import dorkbox.network.rmi.RmiBridge;
|
import dorkbox.network.connection.ConnectionImpl;
|
||||||
import dorkbox.network.rmi.RmiRegistration;
|
import dorkbox.network.connection.Listener;
|
||||||
|
|
||||||
class RegisterRmiNetworkHandler implements Listener.OnMessageReceived<ConnectionImpl, RmiRegistration> {
|
public
|
||||||
|
class RmiObjectNetworkHandler extends RmiObjectHandler {
|
||||||
|
|
||||||
RegisterRmiNetworkHandler() {
|
public
|
||||||
|
RmiObjectNetworkHandler() {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public
|
public
|
||||||
void received(final ConnectionImpl connection, final RmiRegistration registration) {
|
void invoke(final ConnectionImpl connection, final InvokeMethod message, final Listener.OnMessageReceived<ConnectionImpl, InvokeMethod> rmiInvokeListener) {
|
||||||
|
// default, nothing fancy
|
||||||
|
rmiInvokeListener.received(connection, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
void registration(final ConnectionImpl connection, final RmiRegistration registration) {
|
||||||
// manage creating/getting/notifying this RMI object
|
// manage creating/getting/notifying this RMI object
|
||||||
|
|
||||||
// these fields are ALWAYS present!
|
// these fields are ALWAYS present!
|
||||||
@ -42,7 +51,8 @@ class RegisterRmiNetworkHandler implements Listener.OnMessageReceived<Connection
|
|||||||
|
|
||||||
// For network connections, the interface class kryo ID == implementation class kryo ID, so they switch automatically.
|
// For network connections, the interface class kryo ID == implementation class kryo ID, so they switch automatically.
|
||||||
RmiRegistration registrationResult = connection.createNewRmiObject(interfaceClass, interfaceClass, callbackId);
|
RmiRegistration registrationResult = connection.createNewRmiObject(interfaceClass, interfaceClass, callbackId);
|
||||||
connection.TCP(registrationResult).flush();
|
connection.TCP(registrationResult)
|
||||||
|
.flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if we are getting an already existing REMOTE object. This check is always AFTER the check to create a new object
|
// Check if we are getting an already existing REMOTE object. This check is always AFTER the check to create a new object
|
||||||
@ -51,7 +61,8 @@ class RegisterRmiNetworkHandler implements Listener.OnMessageReceived<Connection
|
|||||||
//
|
//
|
||||||
// GET a LOCAL rmi object, if none get a specific, GLOBAL rmi object (objects that are not bound to a single connection).
|
// GET a LOCAL rmi object, if none get a specific, GLOBAL rmi object (objects that are not bound to a single connection).
|
||||||
RmiRegistration registrationResult = connection.getExistingRmiObject(interfaceClass, registration.rmiId, callbackId);
|
RmiRegistration registrationResult = connection.getExistingRmiObject(interfaceClass, registration.rmiId, callbackId);
|
||||||
connection.TCP(registrationResult).flush();
|
connection.TCP(registrationResult)
|
||||||
|
.flush();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
@ -47,8 +47,10 @@ import org.slf4j.Logger;
|
|||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import dorkbox.network.connection.Connection;
|
import dorkbox.network.connection.Connection;
|
||||||
|
import dorkbox.network.connection.ConnectionImpl;
|
||||||
import dorkbox.network.connection.EndPointBase;
|
import dorkbox.network.connection.EndPointBase;
|
||||||
import dorkbox.network.connection.KryoExtra;
|
import dorkbox.network.connection.KryoExtra;
|
||||||
|
import dorkbox.network.connection.Listener;
|
||||||
import dorkbox.network.serialization.RmiSerializationManager;
|
import dorkbox.network.serialization.RmiSerializationManager;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -66,13 +68,14 @@ class RmiProxyHandler implements InvocationHandler {
|
|||||||
private final InvokeMethodResult[] responseTable = new InvokeMethodResult[64];
|
private final InvokeMethodResult[] responseTable = new InvokeMethodResult[64];
|
||||||
private final boolean[] pendingResponses = new boolean[64];
|
private final boolean[] pendingResponses = new boolean[64];
|
||||||
|
|
||||||
private final Connection connection;
|
private final ConnectionImpl connection;
|
||||||
public final int objectID; // this is the RMI id
|
public final int objectID; // this is the RMI id
|
||||||
public final int ID; // this is the KRYO id
|
public final int ID; // this is the KRYO id
|
||||||
|
|
||||||
|
|
||||||
private final String proxyString;
|
private final String proxyString;
|
||||||
private final RemoteInvocationResponse<Connection> responseListener;
|
private final
|
||||||
|
Listener.OnMessageReceived<Connection, InvokeMethodResult> responseListener;
|
||||||
|
|
||||||
private int timeoutMillis = 3000;
|
private int timeoutMillis = 3000;
|
||||||
private boolean isAsync = false;
|
private boolean isAsync = false;
|
||||||
@ -92,7 +95,8 @@ class RmiProxyHandler implements InvocationHandler {
|
|||||||
* @param objectID this is the remote object ID (assigned by RMI). This is NOT the kryo registration ID
|
* @param objectID this is the remote object ID (assigned by RMI). This is NOT the kryo registration ID
|
||||||
* @param iFace this is the RMI interface
|
* @param iFace this is the RMI interface
|
||||||
*/
|
*/
|
||||||
RmiProxyHandler(final Connection connection, final int objectID, final Class<?> iFace) {
|
public
|
||||||
|
RmiProxyHandler(final ConnectionImpl connection, final int objectID, final Class<?> iFace) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.connection = connection;
|
this.connection = connection;
|
||||||
@ -115,14 +119,7 @@ class RmiProxyHandler implements InvocationHandler {
|
|||||||
|
|
||||||
this.logger = LoggerFactory.getLogger(connection.getEndPoint().getName() + ":" + this.getClass().getSimpleName());
|
this.logger = LoggerFactory.getLogger(connection.getEndPoint().getName() + ":" + this.getClass().getSimpleName());
|
||||||
|
|
||||||
|
this.responseListener = new Listener.OnMessageReceived<Connection, InvokeMethodResult>() {
|
||||||
this.responseListener = new RemoteInvocationResponse<Connection>() {
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
void disconnected(Connection connection) {
|
|
||||||
close();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public
|
public
|
||||||
void received(Connection connection, InvokeMethodResult invokeMethodResult) {
|
void received(Connection connection, InvokeMethodResult invokeMethodResult) {
|
||||||
@ -146,12 +143,12 @@ class RmiProxyHandler implements InvocationHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
connection.listeners()
|
|
||||||
.add(this.responseListener);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public
|
||||||
|
Listener.OnMessageReceived<Connection, InvokeMethodResult> getListener() {
|
||||||
|
return responseListener;
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressWarnings({"AutoUnboxing", "AutoBoxing", "NumericCastThatLosesPrecision", "IfCanBeSwitch"})
|
@SuppressWarnings({"AutoUnboxing", "AutoBoxing", "NumericCastThatLosesPrecision", "IfCanBeSwitch"})
|
||||||
@Override
|
@Override
|
||||||
@ -163,7 +160,7 @@ class RmiProxyHandler implements InvocationHandler {
|
|||||||
|
|
||||||
String name = method.getName();
|
String name = method.getName();
|
||||||
if (name.equals("close")) {
|
if (name.equals("close")) {
|
||||||
close();
|
connection.removeRmiListeners(objectID, getListener());
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
else if (name.equals("setResponseTimeout")) {
|
else if (name.equals("setResponseTimeout")) {
|
||||||
@ -403,12 +400,6 @@ class RmiProxyHandler implements InvocationHandler {
|
|||||||
throw new TimeoutException("Response timed out.");
|
throw new TimeoutException("Response timed out.");
|
||||||
}
|
}
|
||||||
|
|
||||||
private
|
|
||||||
void close() {
|
|
||||||
this.connection.listeners()
|
|
||||||
.remove(this.responseListener);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public
|
public
|
||||||
int hashCode() {
|
int hashCode() {
|
||||||
|
@ -19,7 +19,7 @@ package dorkbox.network.rmi;
|
|||||||
* Message specifically to register a class implementation for RMI
|
* Message specifically to register a class implementation for RMI
|
||||||
*/
|
*/
|
||||||
public
|
public
|
||||||
class RmiRegistration {
|
class RmiRegistration implements RmiMessage {
|
||||||
public boolean isRequest;
|
public boolean isRequest;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -308,7 +308,9 @@ class Serialization implements CryptoSerializationManager, RmiSerializationManag
|
|||||||
final boolean registrationRequired,
|
final boolean registrationRequired,
|
||||||
final boolean implementationRequired,
|
final boolean implementationRequired,
|
||||||
final SerializerFactory factory) {
|
final SerializerFactory factory) {
|
||||||
|
|
||||||
this.forbidInterfaceRegistration = implementationRequired;
|
this.forbidInterfaceRegistration = implementationRequired;
|
||||||
|
|
||||||
this.kryoPool = ObjectPool.NonBlockingSoftReference(new PoolableObject<KryoExtra>() {
|
this.kryoPool = ObjectPool.NonBlockingSoftReference(new PoolableObject<KryoExtra>() {
|
||||||
@Override
|
@Override
|
||||||
public
|
public
|
||||||
|
@ -29,7 +29,6 @@ import org.junit.Test;
|
|||||||
import dorkbox.network.connection.Connection;
|
import dorkbox.network.connection.Connection;
|
||||||
import dorkbox.network.connection.Listener;
|
import dorkbox.network.connection.Listener;
|
||||||
import dorkbox.network.connection.Listeners;
|
import dorkbox.network.connection.Listeners;
|
||||||
import dorkbox.util.exceptions.InitializationException;
|
|
||||||
import dorkbox.util.exceptions.SecurityException;
|
import dorkbox.util.exceptions.SecurityException;
|
||||||
|
|
||||||
public
|
public
|
||||||
@ -39,7 +38,7 @@ class ReuseTest extends BaseTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public
|
public
|
||||||
void socketReuse() throws InitializationException, SecurityException, IOException, InterruptedException {
|
void socketReuse() throws SecurityException, IOException {
|
||||||
this.serverCount = new AtomicInteger(0);
|
this.serverCount = new AtomicInteger(0);
|
||||||
this.clientCount = new AtomicInteger(0);
|
this.clientCount = new AtomicInteger(0);
|
||||||
|
|
||||||
@ -104,7 +103,7 @@ class ReuseTest extends BaseTest {
|
|||||||
System.err.println("Waiting...");
|
System.err.println("Waiting...");
|
||||||
try {
|
try {
|
||||||
Thread.sleep(100);
|
Thread.sleep(100);
|
||||||
} catch (InterruptedException ex) {
|
} catch (InterruptedException ignored) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -119,7 +118,7 @@ class ReuseTest extends BaseTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public
|
public
|
||||||
void localReuse() throws InitializationException, SecurityException, IOException, InterruptedException {
|
void localReuse() throws SecurityException, IOException {
|
||||||
this.serverCount = new AtomicInteger(0);
|
this.serverCount = new AtomicInteger(0);
|
||||||
this.clientCount = new AtomicInteger(0);
|
this.clientCount = new AtomicInteger(0);
|
||||||
|
|
||||||
|
@ -1,13 +1,10 @@
|
|||||||
package dorkbox.network.rmi.multiJVM;
|
package dorkbox.network.rmi.multiJVM;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
import dorkbox.network.Server;
|
import dorkbox.network.Server;
|
||||||
import dorkbox.network.rmi.RmiTest;
|
import dorkbox.network.rmi.RmiTest;
|
||||||
import dorkbox.network.rmi.TestCow;
|
import dorkbox.network.rmi.TestCow;
|
||||||
import dorkbox.network.rmi.TestCowImpl;
|
import dorkbox.network.rmi.TestCowImpl;
|
||||||
import dorkbox.network.serialization.Serialization;
|
import dorkbox.network.serialization.Serialization;
|
||||||
import dorkbox.util.exceptions.InitializationException;
|
|
||||||
import dorkbox.util.exceptions.SecurityException;
|
import dorkbox.util.exceptions.SecurityException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -31,12 +28,8 @@ class TestServer
|
|||||||
Server server = null;
|
Server server = null;
|
||||||
try {
|
try {
|
||||||
server = new Server(configuration);
|
server = new Server(configuration);
|
||||||
} catch (InitializationException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
} catch (SecurityException e) {
|
} catch (SecurityException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// server.setIdleTimeout(0);
|
// server.setIdleTimeout(0);
|
||||||
|
Loading…
Reference in New Issue
Block a user