Fixed issues with UDP loopback. Reworked RMI. Added UDP/UDT options for rmi return values. Added using ASM to RMI.
This commit is contained in:
parent
e4805cd900
commit
d956badcd0
|
@ -98,9 +98,6 @@ public class Broadcast {
|
|||
List<InetAddress> servers = new ArrayList<InetAddress>();
|
||||
|
||||
Logger logger2 = logger;
|
||||
if (logger2.isInfoEnabled()) {
|
||||
logger2.info("Searching for host on port: {}", udpPort);
|
||||
}
|
||||
|
||||
Enumeration<NetworkInterface> networkInterfaces;
|
||||
try {
|
||||
|
@ -127,6 +124,10 @@ public class Broadcast {
|
|||
|
||||
|
||||
try {
|
||||
if (logger2.isInfoEnabled()) {
|
||||
logger2.info("Searching for host on {} : {}", address, udpPort);
|
||||
}
|
||||
|
||||
NioEventLoopGroup group = new NioEventLoopGroup();
|
||||
Bootstrap udpBootstrap = new Bootstrap()
|
||||
.group(group)
|
||||
|
|
|
@ -2,7 +2,6 @@ package dorkbox.network;
|
|||
|
||||
import io.netty.bootstrap.Bootstrap;
|
||||
import io.netty.buffer.PooledByteBufAllocator;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelOption;
|
||||
import io.netty.channel.DefaultEventLoopGroup;
|
||||
import io.netty.channel.EventLoopGroup;
|
||||
|
@ -19,11 +18,10 @@ import io.netty.channel.socket.oio.OioSocketChannel;
|
|||
import io.netty.util.internal.PlatformDependent;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import dorkbox.network.connection.BootstrapWrapper;
|
||||
import dorkbox.network.connection.Connection;
|
||||
import dorkbox.network.connection.EndPointClient;
|
||||
import dorkbox.network.connection.idle.IdleBridge;
|
||||
|
@ -40,15 +38,9 @@ import dorkbox.util.NamedThreadFactory;
|
|||
import dorkbox.util.OS;
|
||||
|
||||
/**
|
||||
* The client is both SYNC and ASYNC, meaning that once the client is connected to the server, you can access it however you want.
|
||||
* <p>
|
||||
* Another way to put this: The client (like the server) can respond to EVENTS (ie, listeners), but you can also use it DIRECTLY, for
|
||||
* example, send data to the server on keyboard input. This is because the client will BLOCK the calling thread until it's ready.
|
||||
* The client is both SYNC and ASYNC. It starts off SYNC (blocks thread until it's done), then once it's connected to the server, it's ASYNC.
|
||||
*/
|
||||
public class Client extends EndPointClient {
|
||||
private List<BootstrapWrapper> bootstraps = new LinkedList<BootstrapWrapper>();
|
||||
|
||||
private volatile int connectionTimeout = 5000; // default
|
||||
|
||||
/**
|
||||
* Starts a LOCAL <b>only</b> client, with the default local channel name and serialization scheme
|
||||
|
@ -90,7 +82,6 @@ public class Client extends EndPointClient {
|
|||
options.udtPort = -1;
|
||||
}
|
||||
|
||||
|
||||
// tcpBootstrap.setOption(SO_SNDBUF, 1048576);
|
||||
// tcpBootstrap.setOption(SO_RCVBUF, 1048576);
|
||||
|
||||
|
@ -130,11 +121,9 @@ public class Client extends EndPointClient {
|
|||
if (OS.isLinux()) {
|
||||
// JNI network stack is MUCH faster (but only on linux)
|
||||
boss = new EpollEventLoopGroup(DEFAULT_THREAD_POOL_SIZE, new NamedThreadFactory(this.name + "-TCP", nettyGroup));
|
||||
|
||||
tcpBootstrap.channel(EpollSocketChannel.class);
|
||||
} else {
|
||||
boss = new NioEventLoopGroup(DEFAULT_THREAD_POOL_SIZE, new NamedThreadFactory(this.name + "-TCP", nettyGroup));
|
||||
|
||||
tcpBootstrap.channel(NioSocketChannel.class);
|
||||
}
|
||||
}
|
||||
|
@ -168,7 +157,6 @@ public class Client extends EndPointClient {
|
|||
} else {
|
||||
// CANNOT USE EpollDatagramChannel on the client!
|
||||
boss = new NioEventLoopGroup(DEFAULT_THREAD_POOL_SIZE, new NamedThreadFactory(this.name + "-UDP", nettyGroup));
|
||||
|
||||
udpBootstrap.channel(NioDatagramChannel.class);
|
||||
}
|
||||
|
||||
|
@ -258,6 +246,8 @@ public class Client extends EndPointClient {
|
|||
|
||||
/**
|
||||
* will attempt to connect to the server, and will the specified timeout.
|
||||
* <p>
|
||||
* will BLOCK until completed
|
||||
*
|
||||
* @param connectionTimeout wait for x milliseconds. 0 will wait indefinitely
|
||||
*/
|
||||
|
@ -269,59 +259,19 @@ public class Client extends EndPointClient {
|
|||
synchronized (this.shutdownInProgress) {
|
||||
}
|
||||
|
||||
// have to BLOCK here, because we don't want sendTCP() called before registration is complete
|
||||
// have to start the registration process
|
||||
this.connectingBootstrap.set(0);
|
||||
registerNextProtocol();
|
||||
|
||||
// have to BLOCK
|
||||
// don't want the client to run before registration is complete
|
||||
synchronized (this.registrationLock) {
|
||||
this.registrationInProgress = true;
|
||||
|
||||
// we will only do a local channel when NOT doing TCP/UDP channels. This is EXCLUSIVE. (XOR)
|
||||
int size = this.bootstraps.size();
|
||||
for (int i=0;i<size;i++) {
|
||||
if (!this.registrationInProgress) {
|
||||
break;
|
||||
}
|
||||
|
||||
this.registrationComplete = i == size-1;
|
||||
BootstrapWrapper bootstrapWrapper = this.bootstraps.get(i);
|
||||
ChannelFuture future;
|
||||
|
||||
if (connectionTimeout != 0) {
|
||||
// must be before connect
|
||||
bootstrapWrapper.bootstrap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, connectionTimeout);
|
||||
}
|
||||
|
||||
Logger logger2 = this.logger;
|
||||
try {
|
||||
// UDP : When this is CONNECT on a udp socket will ONLY accept UDP traffic from the remote address (ip/port combo).
|
||||
// If the reply isn't from the correct port, then the other end will receive a "Port Unreachable" exception.
|
||||
|
||||
future = bootstrapWrapper.bootstrap.connect();
|
||||
future.await();
|
||||
} catch (Exception e) {
|
||||
String errorMessage = stopWithErrorMessage(logger2, "Could not connect to the " + bootstrapWrapper.type + " server on port: " + bootstrapWrapper.port, e);
|
||||
this.registrationInProgress = false;
|
||||
throw new IllegalArgumentException(errorMessage);
|
||||
}
|
||||
|
||||
if (!future.isSuccess()) {
|
||||
String errorMessage = stopWithErrorMessage(logger2, "Could not connect to the " + bootstrapWrapper.type + " server on port: " + bootstrapWrapper.port, future.cause());
|
||||
this.registrationInProgress = false;
|
||||
throw new IllegalArgumentException(errorMessage);
|
||||
}
|
||||
|
||||
if (logger2.isTraceEnabled()) {
|
||||
logger2.trace("Waiting for registration from server.");
|
||||
}
|
||||
manageForShutdown(future);
|
||||
|
||||
// WAIT for the next one to complete.
|
||||
try {
|
||||
this.registrationLock.wait(connectionTimeout);
|
||||
} catch (InterruptedException e) {
|
||||
this.logger.error("Registration thread interrupted!");
|
||||
}
|
||||
}
|
||||
|
||||
this.registrationInProgress = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -356,18 +306,8 @@ public class Client extends EndPointClient {
|
|||
*/
|
||||
@Override
|
||||
public void close() {
|
||||
// in case a different thread is blocked waiting for registration.
|
||||
synchronized (this.registrationLock) {
|
||||
if (this.registrationInProgress) {
|
||||
try {
|
||||
this.registrationLock.wait();
|
||||
} catch (InterruptedException e) {
|
||||
}
|
||||
}
|
||||
|
||||
// inside the sync block, because we DON'T want to allow a connect WHILE close is happening! Since connect is also
|
||||
// in the sync bloc, we prevent it from happening.
|
||||
super.close();
|
||||
this.registrationLock.notify();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package dorkbox.network;
|
||||
|
||||
import dorkbox.network.rmi.RemoteObject;
|
||||
import dorkbox.network.util.SerializationManager;
|
||||
import dorkbox.network.util.store.SettingsStore;
|
||||
|
||||
|
@ -18,6 +19,17 @@ public class ConnectionOptions {
|
|||
public SerializationManager serializationManager = null;
|
||||
public SettingsStore settingsStore = null;
|
||||
|
||||
/**
|
||||
* Enable remote method invocation (RMI) for this connection. This is additional overhead to using RMI.
|
||||
* <p>
|
||||
* Specifically, It costs at least 2 bytes more to use remote method invocation than just
|
||||
* sending the parameters. If the method has a return value which is not
|
||||
* {@link RemoteObject#setNonBlocking(boolean) ignored}, an extra byte is
|
||||
* written. If the type of a parameter is not final (note primitives are final)
|
||||
* then an extra byte is written for that parameter.
|
||||
*/
|
||||
public boolean enableRmi = false;
|
||||
|
||||
|
||||
public ConnectionOptions() {
|
||||
}
|
||||
|
|
|
@ -7,8 +7,6 @@ import io.netty.channel.ChannelFuture;
|
|||
import io.netty.channel.ChannelOption;
|
||||
import io.netty.channel.DefaultEventLoopGroup;
|
||||
import io.netty.channel.EventLoopGroup;
|
||||
import io.netty.channel.epoll.EpollChannelOption;
|
||||
import io.netty.channel.epoll.EpollDatagramChannel;
|
||||
import io.netty.channel.epoll.EpollEventLoopGroup;
|
||||
import io.netty.channel.epoll.EpollServerSocketChannel;
|
||||
import io.netty.channel.local.LocalAddress;
|
||||
|
@ -217,17 +215,17 @@ public class Server extends EndPointServer {
|
|||
worker = new OioEventLoopGroup(0, new NamedThreadFactory(this.name + "-worker-UDP", nettyGroup));
|
||||
this.udpBootstrap.channel(OioDatagramChannel.class);
|
||||
} else {
|
||||
if (OS.isLinux()) {
|
||||
// JNI network stack is MUCH faster (but only on linux)
|
||||
worker = new EpollEventLoopGroup(DEFAULT_THREAD_POOL_SIZE, new NamedThreadFactory(this.name + "-worker-UDP", nettyGroup));
|
||||
|
||||
this.udpBootstrap.channel(EpollDatagramChannel.class)
|
||||
.option(EpollChannelOption.SO_REUSEPORT, true);
|
||||
} else {
|
||||
// if (OS.isLinux()) {
|
||||
// // JNI network stack is MUCH faster (but only on linux)
|
||||
// worker = new EpollEventLoopGroup(DEFAULT_THREAD_POOL_SIZE, new NamedThreadFactory(this.name + "-worker-UDP", nettyGroup));
|
||||
//
|
||||
// this.udpBootstrap.channel(EpollDatagramChannel.class)
|
||||
// .option(EpollChannelOption.SO_REUSEPORT, true);
|
||||
// } else {
|
||||
worker = new NioEventLoopGroup(DEFAULT_THREAD_POOL_SIZE, new NamedThreadFactory(this.name + "-worker-UDP", nettyGroup));
|
||||
|
||||
this.udpBootstrap.channel(NioDatagramChannel.class);
|
||||
}
|
||||
// }
|
||||
}
|
||||
|
||||
manageForShutdown(worker);
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
package dorkbox.network;
|
||||
package dorkbox.network.connection;
|
||||
|
||||
import io.netty.bootstrap.Bootstrap;
|
||||
|
||||
class BootstrapWrapper {
|
||||
final String type;
|
||||
final Bootstrap bootstrap;
|
||||
final int port;
|
||||
public class BootstrapWrapper {
|
||||
public final String type;
|
||||
public final Bootstrap bootstrap;
|
||||
public final int port;
|
||||
|
||||
BootstrapWrapper(String type, int port, Bootstrap bootstrap) {
|
||||
public BootstrapWrapper(String type, int port, Bootstrap bootstrap) {
|
||||
this.type = type;
|
||||
this.port = port;
|
||||
this.bootstrap = bootstrap;
|
|
@ -6,9 +6,10 @@ import org.bouncycastle.crypto.params.ParametersWithIV;
|
|||
import dorkbox.network.connection.bridge.ConnectionBridge;
|
||||
import dorkbox.network.connection.idle.IdleBridge;
|
||||
import dorkbox.network.connection.idle.IdleSender;
|
||||
import dorkbox.network.rmi.RemoteObject;
|
||||
import dorkbox.network.rmi.TimeoutException;
|
||||
|
||||
public interface Connection {
|
||||
|
||||
public static final String connection = "connection";
|
||||
|
||||
/**
|
||||
|
@ -97,4 +98,37 @@ public interface Connection {
|
|||
* Closes the connection
|
||||
*/
|
||||
public void close();
|
||||
|
||||
/**
|
||||
* Identical to {@link #getRemoteObject(C, int, Class...)} except returns
|
||||
* the object cast to the specified interface type. The returned object
|
||||
* still implements {@link RemoteObject}.
|
||||
*/
|
||||
public <T> T getRemoteObject(int objectID, Class<T> iface);
|
||||
|
||||
/**
|
||||
* 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 #addConnection(Connection)
|
||||
* 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#setNonBlocking(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
|
||||
*/
|
||||
public RemoteObject getRemoteObject(int objectID, Class<?>... ifaces);
|
||||
}
|
||||
|
|
|
@ -28,6 +28,8 @@ import dorkbox.network.connection.idle.IdleSender;
|
|||
import dorkbox.network.connection.wrapper.ChannelNetworkWrapper;
|
||||
import dorkbox.network.connection.wrapper.ChannelNull;
|
||||
import dorkbox.network.connection.wrapper.ChannelWrapper;
|
||||
import dorkbox.network.rmi.RemoteObject;
|
||||
import dorkbox.network.rmi.TimeoutException;
|
||||
|
||||
|
||||
/**
|
||||
|
@ -343,6 +345,47 @@ public class ConnectionImpl extends ChannelInboundHandlerAdapter
|
|||
return sender;
|
||||
}
|
||||
|
||||
/**
|
||||
* Identical to {@link #getRemoteObject(C, int, Class...)} except returns
|
||||
* the object cast to the specified interface type. The returned object
|
||||
* still implements {@link RemoteObject}.
|
||||
*/
|
||||
@Override
|
||||
public <T> T getRemoteObject(int objectID, Class<T> iface) {
|
||||
@SuppressWarnings({"unchecked"})
|
||||
T remoteObject = (T) this.endPoint.getRemoteObject(this, objectID, new Class<?>[] {iface});
|
||||
return remoteObject;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 #addConnection(Connection)
|
||||
* 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#setNonBlocking(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
|
||||
*/
|
||||
@Override
|
||||
public RemoteObject getRemoteObject(int objectID, Class<?>... ifaces) {
|
||||
return this.endPoint.getRemoteObject(this, objectID, ifaces);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked when a {@link Channel} has been idle for a while.
|
||||
*/
|
||||
|
|
|
@ -7,6 +7,8 @@ import io.netty.util.concurrent.EventExecutor;
|
|||
import io.netty.util.concurrent.Future;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.security.AccessControlException;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.ArrayList;
|
||||
|
@ -37,7 +39,10 @@ import dorkbox.network.connection.wrapper.ChannelNetworkWrapper;
|
|||
import dorkbox.network.connection.wrapper.ChannelWrapper;
|
||||
import dorkbox.network.pipeline.KryoEncoder;
|
||||
import dorkbox.network.pipeline.KryoEncoderCrypto;
|
||||
import dorkbox.network.rmi.RemoteObject;
|
||||
import dorkbox.network.rmi.Rmi;
|
||||
import dorkbox.network.rmi.RmiBridge;
|
||||
import dorkbox.network.rmi.TimeoutException;
|
||||
import dorkbox.network.util.EndpointTool;
|
||||
import dorkbox.network.util.KryoSerializationManager;
|
||||
import dorkbox.network.util.SerializationManager;
|
||||
|
@ -51,6 +56,7 @@ import dorkbox.network.util.serializers.IgnoreSerialization;
|
|||
import dorkbox.network.util.store.NullSettingsStore;
|
||||
import dorkbox.network.util.store.SettingsStore;
|
||||
import dorkbox.network.util.udt.UdtEndpointProxy;
|
||||
import dorkbox.util.Sys;
|
||||
import dorkbox.util.collections.IntMap;
|
||||
import dorkbox.util.collections.IntMap.Entries;
|
||||
import dorkbox.util.crypto.Crypto;
|
||||
|
@ -139,7 +145,7 @@ public abstract class EndPoint {
|
|||
protected final RegistrationWrapper registrationWrapper;
|
||||
|
||||
// The remote object space is used by RMI.
|
||||
protected RmiBridge remoteObjectSpace = null;
|
||||
private final RmiBridge rmiBridge;
|
||||
|
||||
// the eventLoop groups are used to track and manage the event loops for startup/shutdown
|
||||
private List<EventLoopGroup> eventLoopGroups = new ArrayList<EventLoopGroup>(8);
|
||||
|
@ -172,6 +178,17 @@ public abstract class EndPoint {
|
|||
|
||||
this.registrationWrapper = new RegistrationWrapper(this, this.logger);
|
||||
|
||||
// make sure that 'localhost' is REALLY our specific IP address
|
||||
if (options.host != null && (options.host.equals("localhost") || options.host.startsWith("127."))) {
|
||||
try {
|
||||
InetAddress localHostLanAddress = Sys.getLocalHostLanAddress();
|
||||
options.host = localHostLanAddress.getHostAddress();
|
||||
this.logger.info("Network localhost request, using real IP instead: {}", options.host);
|
||||
} catch (UnknownHostException e) {
|
||||
this.logger.error("Unable to get the actual 'localhost' IP address", e);
|
||||
}
|
||||
}
|
||||
|
||||
// we have to be able to specify WHAT property store we want to use, since it can change!
|
||||
if (options.settingsStore == null) {
|
||||
this.propertyStore = new PropertyStore(name);
|
||||
|
@ -278,7 +295,18 @@ public abstract class EndPoint {
|
|||
|
||||
|
||||
// add the ping listener (internal use only!)
|
||||
this.connectionManager.add(new PingSystemListener(name));
|
||||
this.connectionManager.add(new PingSystemListener());
|
||||
|
||||
/*
|
||||
* Creates the remote method invocation (RMI) bridge for this endpoint.
|
||||
* <p>
|
||||
* there is some housekeeping that is necessary BEFORE a connection is actually connected..
|
||||
*/
|
||||
if (options.enableRmi) {
|
||||
this.rmiBridge = new RmiBridge(this.logger, this.serializationManager);
|
||||
} else {
|
||||
this.rmiBridge = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void disableRemoteKeyValidation() {
|
||||
|
@ -323,7 +351,7 @@ public abstract class EndPoint {
|
|||
* Internal call by the pipeline to notify the client to continue registering the different session protocols.
|
||||
* The server does not use this.
|
||||
*/
|
||||
protected boolean continueRegistration0() {
|
||||
protected boolean registerNextProtocol0() {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -385,25 +413,15 @@ public abstract class EndPoint {
|
|||
}
|
||||
|
||||
/**
|
||||
* Creates the remote (RMI) object space for this endpoint.
|
||||
* <p>
|
||||
* This method is safe, and is recommended. Make sure to call it BEFORE a connection is established, as
|
||||
* there is some housekeeping that is necessary BEFORE a connection is actually connected..
|
||||
* Gets the remote method invocation (RMI) bridge for this endpoint.
|
||||
*/
|
||||
public RmiBridge getRmiBridge() {
|
||||
synchronized (this) {
|
||||
if (this.remoteObjectSpace == null) {
|
||||
if (isConnected()) {
|
||||
throw new NetException("Cannot create a remote object space after the remote endpoint has already connected!");
|
||||
public Rmi rmi() {
|
||||
if (this.rmiBridge == null) {
|
||||
throw new NetException("Cannot use a remote object space that has NOT been created first! Configure the ConnectionOptions!");
|
||||
}
|
||||
|
||||
this.remoteObjectSpace = new RmiBridge(this.logger);
|
||||
return this.rmiBridge;
|
||||
}
|
||||
}
|
||||
|
||||
return this.remoteObjectSpace;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This method allows the connections used by the client/server to be subclassed (custom implementations).
|
||||
|
@ -417,7 +435,6 @@ public abstract class EndPoint {
|
|||
return new ConnectionImpl(name);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Internal call by the pipeline when:
|
||||
* - creating a new network connection
|
||||
|
@ -452,10 +469,8 @@ public abstract class EndPoint {
|
|||
metaChannel.connection = connection;
|
||||
|
||||
// notify our remote object space that it is able to receive method calls.
|
||||
synchronized (this) {
|
||||
if (this.remoteObjectSpace != null) {
|
||||
this.remoteObjectSpace.addConnection(connection);
|
||||
}
|
||||
if (this.rmiBridge != null) {
|
||||
connection.listeners().add(this.rmiBridge.getListener());
|
||||
}
|
||||
} else {
|
||||
// getting the baseClass
|
||||
|
@ -509,6 +524,34 @@ public abstract class EndPoint {
|
|||
*/
|
||||
public abstract ConnectionBridgeBase send();
|
||||
|
||||
/**
|
||||
* 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 #addConnection(Connection)
|
||||
* 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#setNonBlocking(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
|
||||
*/
|
||||
public RemoteObject getRemoteObject(Connection connection, int objectID, Class<?>[] ifaces) {
|
||||
return this.rmiBridge.getRemoteObject(connection, objectID, ifaces);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a tool with the server, to be used by other services.
|
||||
*/
|
||||
|
|
|
@ -1,5 +1,12 @@
|
|||
package dorkbox.network.connection;
|
||||
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelOption;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import dorkbox.network.ConnectionOptions;
|
||||
|
@ -11,11 +18,13 @@ import dorkbox.network.util.exceptions.SecurityException;
|
|||
/**
|
||||
* This serves the purpose of making sure that specific methods are not available to the end user.
|
||||
*/
|
||||
public class EndPointClient extends EndPoint {
|
||||
public class EndPointClient extends EndPoint implements Runnable {
|
||||
|
||||
protected List<BootstrapWrapper> bootstraps = new LinkedList<BootstrapWrapper>();
|
||||
protected AtomicInteger connectingBootstrap = new AtomicInteger(0);
|
||||
protected final Object registrationLock = new Object();
|
||||
protected volatile boolean registrationInProgress = false;
|
||||
|
||||
protected volatile int connectionTimeout = 5000; // default
|
||||
protected volatile boolean registrationComplete = false;
|
||||
|
||||
private volatile ConnectionBridgeFlushAlways connectionBridgeFlushAlways;
|
||||
|
@ -25,19 +34,58 @@ public class EndPointClient extends EndPoint {
|
|||
super(name, options);
|
||||
}
|
||||
|
||||
protected void registerNextProtocol() {
|
||||
new Thread(this, "Bootstrap registration").start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
synchronized(this.connectingBootstrap) {
|
||||
int bootstrapToRegister = this.connectingBootstrap.getAndIncrement();
|
||||
|
||||
BootstrapWrapper bootstrapWrapper = this.bootstraps.get(bootstrapToRegister);
|
||||
|
||||
ChannelFuture future;
|
||||
|
||||
if (this.connectionTimeout != 0) {
|
||||
// must be before connect
|
||||
bootstrapWrapper.bootstrap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, this.connectionTimeout);
|
||||
}
|
||||
|
||||
Logger logger2 = this.logger;
|
||||
try {
|
||||
// UDP : When this is CONNECT, a udp socket will ONLY accept UDP traffic from the remote address (ip/port combo).
|
||||
// If the reply isn't from the correct port, then the other end will receive a "Port Unreachable" exception.
|
||||
|
||||
future = bootstrapWrapper.bootstrap.connect();
|
||||
future.await();
|
||||
} catch (Exception e) {
|
||||
String errorMessage = stopWithErrorMessage(logger2, "Could not connect to the " + bootstrapWrapper.type + " server on port: " + bootstrapWrapper.port, e);
|
||||
throw new IllegalArgumentException(errorMessage);
|
||||
}
|
||||
|
||||
if (!future.isSuccess()) {
|
||||
String errorMessage = stopWithErrorMessage(logger2, "Could not connect to the " + bootstrapWrapper.type + " server on port: " + bootstrapWrapper.port, future.cause());
|
||||
throw new IllegalArgumentException(errorMessage);
|
||||
}
|
||||
|
||||
if (logger2.isTraceEnabled()) {
|
||||
logger2.trace("Waiting for registration from server.");
|
||||
}
|
||||
manageForShutdown(future);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal call by the pipeline to notify the client to continue registering the different session protocols.
|
||||
* @return true if we are done registering bootstraps
|
||||
*/
|
||||
@Override
|
||||
protected boolean continueRegistration0() {
|
||||
// we need to cache the value, since it can change in a different thread before we have the chance to return the value.
|
||||
boolean complete = this.registrationComplete;
|
||||
|
||||
// notify the block, but only if we are not ready.
|
||||
if (!complete) {
|
||||
synchronized (this.registrationLock) {
|
||||
this.registrationLock.notifyAll();
|
||||
protected boolean registerNextProtocol0() {
|
||||
synchronized(this.connectingBootstrap) {
|
||||
this.registrationComplete = this.connectingBootstrap.get() == this.bootstraps.size();
|
||||
if (!this.registrationComplete) {
|
||||
registerNextProtocol();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -48,11 +96,11 @@ public class EndPointClient extends EndPoint {
|
|||
|
||||
// only let us continue with connections (this starts up the client/server implementations) once ALL of the
|
||||
// bootstraps have connected
|
||||
return complete;
|
||||
return this.registrationComplete;
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal (to the networking stack) to notify the client that registration has completed. This is necessary because the client
|
||||
* Internal (to the networking stack) to notify the client that registration has COMPLETED. This is necessary because the client
|
||||
* will BLOCK until it has successfully registered it's connections.
|
||||
*/
|
||||
@Override
|
||||
|
@ -60,9 +108,9 @@ public class EndPointClient extends EndPoint {
|
|||
// invokes the listener.connection() method, and initialize the connection channels with whatever extra info they might need.
|
||||
super.connectionConnected0(connection);
|
||||
|
||||
// notify the block
|
||||
// notify the registration we are done!
|
||||
synchronized (this.registrationLock) {
|
||||
this.registrationLock.notifyAll();
|
||||
this.registrationLock.notify();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -70,7 +118,9 @@ public class EndPointClient extends EndPoint {
|
|||
* Internal call to abort registration if the shutdown command is issued during channel registration.
|
||||
*/
|
||||
void abortRegistration() {
|
||||
this.registrationInProgress = false;
|
||||
synchronized (this.registrationLock) {
|
||||
this.registrationLock.notify();
|
||||
}
|
||||
stop();
|
||||
}
|
||||
|
||||
|
|
|
@ -53,6 +53,7 @@ public abstract class ListenerRaw<C extends Connection, M extends Object> {
|
|||
* This method should not block for long periods as other network activity will not be processed
|
||||
* until it returns.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public void connected(C connection) {
|
||||
}
|
||||
|
||||
|
@ -61,6 +62,7 @@ public abstract class ListenerRaw<C extends Connection, M extends Object> {
|
|||
* <p>
|
||||
* Do not write data in this method! The channel can be closed, resulting in an error if you attempt to do so.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public void disconnected(C connection) {
|
||||
}
|
||||
|
||||
|
@ -68,18 +70,21 @@ public abstract class ListenerRaw<C extends Connection, M extends Object> {
|
|||
* Called when an object has been received from the remote end of the connection.
|
||||
* This method should not block for long periods as other network activity will not be processed until it returns.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public void received(C connection, M message) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the connection is idle for longer than the {@link EndPoint#setIdleTimeout(idle) idle threshold}.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public void idle(C connection) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when there is an error of some kind during the up/down stream process (to/from the socket or otherwise)
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public void error(C connection, Throwable throwable) {
|
||||
throwable.printStackTrace();
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ package dorkbox.network.connection;
|
|||
|
||||
class PingSystemListener extends ListenerRaw<ConnectionImpl, PingMessage> {
|
||||
|
||||
PingSystemListener(String name) {
|
||||
PingSystemListener() {
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -100,8 +100,8 @@ public class RegistrationWrapper implements UdpServer {
|
|||
* The server does not use this.
|
||||
* @return true if we are done registering bootstraps
|
||||
*/
|
||||
public boolean continueRegistration0() {
|
||||
return this.endPoint.continueRegistration0();
|
||||
public boolean registerNextProtocol0() {
|
||||
return this.endPoint.registerNextProtocol0();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -23,6 +23,7 @@ public abstract class RegistrationHandler extends ChannelInboundHandlerAdapter {
|
|||
this.registrationWrapper = registrationWrapper;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
protected void initChannel(Channel channel) {
|
||||
}
|
||||
|
||||
|
|
|
@ -63,7 +63,7 @@ public class RegistrationLocalHandlerClient extends RegistrationLocalHandler {
|
|||
channel.pipeline().remove(this);
|
||||
|
||||
// Event though a local channel is XOR with everything else, we still have to make the client clean up it's state.
|
||||
registrationWrapper.continueRegistration0();
|
||||
registrationWrapper.registerNextProtocol0();
|
||||
|
||||
Connection connection = metaChannel.connection;
|
||||
registrationWrapper.connectionConnected0(connection);
|
||||
|
|
|
@ -316,6 +316,7 @@ public abstract class RegistrationRemoteHandler extends RegistrationHandler {
|
|||
* The server will override this.
|
||||
* Only called if we have a UDP channel when we finalize the setup of the TCP connection
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
protected void setupServerUdpConnection(MetaChannel metaChannel) {
|
||||
}
|
||||
|
||||
|
|
|
@ -296,7 +296,7 @@ public class RegistrationRemoteHandlerClientTCP extends RegistrationRemoteHandle
|
|||
metaChannel.ecdhKey = null;
|
||||
|
||||
// notify the client that we are ready to continue registering other session protocols (bootstraps)
|
||||
boolean isDoneWithRegistration = registrationWrapper2.continueRegistration0();
|
||||
boolean isDoneWithRegistration = registrationWrapper2.registerNextProtocol0();
|
||||
|
||||
// tell the server we are done, and to setup crypto on it's side
|
||||
if (isDoneWithRegistration) {
|
||||
|
|
|
@ -152,7 +152,7 @@ public class RegistrationRemoteHandlerClientUDP extends RegistrationRemoteHandle
|
|||
// hooray! we are successful
|
||||
|
||||
// notify the client that we are ready to continue registering other session protocols (bootstraps)
|
||||
boolean isDoneWithRegistration = registrationWrapper2.continueRegistration0();
|
||||
boolean isDoneWithRegistration = registrationWrapper2.registerNextProtocol0();
|
||||
|
||||
// tell the server we are done, and to setup crypto on it's side
|
||||
if (isDoneWithRegistration) {
|
||||
|
|
|
@ -145,7 +145,7 @@ public class RegistrationRemoteHandlerClientUDT extends RegistrationRemoteHandle
|
|||
// hooray! we are successful
|
||||
|
||||
// notify the client that we are ready to continue registering other session protocols (bootstraps)
|
||||
boolean isDoneWithRegistration = registrationWrapper2.continueRegistration0();
|
||||
boolean isDoneWithRegistration = registrationWrapper2.registerNextProtocol0();
|
||||
|
||||
// tell the server we are done, and to setup crypto on it's side
|
||||
if (isDoneWithRegistration) {
|
||||
|
|
|
@ -17,7 +17,6 @@ import org.slf4j.Logger;
|
|||
import dorkbox.network.Broadcast;
|
||||
import dorkbox.network.connection.Connection;
|
||||
import dorkbox.network.connection.ConnectionImpl;
|
||||
import dorkbox.network.connection.EndPoint;
|
||||
import dorkbox.network.connection.RegistrationWrapper;
|
||||
import dorkbox.network.connection.registration.MetaChannel;
|
||||
import dorkbox.network.connection.registration.Registration;
|
||||
|
@ -76,8 +75,9 @@ public class RegistrationRemoteHandlerServerUDP extends MessageToMessageCodec<Da
|
|||
// this is the response from a discoverHost query
|
||||
out.add(new DatagramPacket((ByteBuf) object, remoteAddress));
|
||||
} else {
|
||||
ByteBuf buffer = Unpooled.buffer(EndPoint.udpMaxSize);
|
||||
|
||||
// this is regular registration stuff
|
||||
ByteBuf buffer = context.alloc().buffer();
|
||||
// writes data into buffer
|
||||
sendUDP(context, object, buffer, remoteAddress);
|
||||
|
||||
if (buffer != null) {
|
||||
|
@ -121,6 +121,7 @@ public class RegistrationRemoteHandlerServerUDP extends MessageToMessageCodec<Da
|
|||
}
|
||||
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public final void sendUDP(ChannelHandlerContext context, Object object, ByteBuf buffer, InetSocketAddress udpRemoteAddress) {
|
||||
|
||||
Connection networkConnection = this.registrationWrapper.getServerUDP(udpRemoteAddress);
|
||||
|
@ -135,7 +136,8 @@ public class RegistrationRemoteHandlerServerUDP extends MessageToMessageCodec<Da
|
|||
|
||||
|
||||
// this will be invoked by the UdpRegistrationHandlerServer. Remember, TCP will be established first.
|
||||
public final void receivedUDP(ChannelHandlerContext context, Channel channel, ByteBuf data, InetSocketAddress udpRemoteAddress) throws Exception {
|
||||
@SuppressWarnings("unused")
|
||||
private final void receivedUDP(ChannelHandlerContext context, Channel channel, ByteBuf data, InetSocketAddress udpRemoteAddress) throws Exception {
|
||||
// registration is the ONLY thing NOT encrypted
|
||||
Logger logger2 = this.logger;
|
||||
RegistrationWrapper registrationWrapper2 = this.registrationWrapper;
|
||||
|
@ -171,7 +173,7 @@ public class RegistrationRemoteHandlerServerUDP extends MessageToMessageCodec<Da
|
|||
|
||||
try {
|
||||
object = serializationManager2.read(data, data.writerIndex());
|
||||
} catch (NetException e) {
|
||||
} catch (Exception e) {
|
||||
logger2.error("UDP unable to deserialize buffer", e);
|
||||
shutdown(registrationWrapper2, channel);
|
||||
return;
|
||||
|
@ -227,7 +229,6 @@ public class RegistrationRemoteHandlerServerUDP extends MessageToMessageCodec<Da
|
|||
register.payload = Crypto.AES.encrypt(RegistrationRemoteHandler.getAesEngine(), metaChannel.aesKey, metaChannel.aesIV, idAsBytes);
|
||||
|
||||
channel.writeAndFlush(new UdpWrapper(register, udpRemoteAddress));
|
||||
|
||||
if (logger2.isTraceEnabled()) {
|
||||
logger2.trace("Register UDP connection from {}", udpRemoteAddress);
|
||||
}
|
||||
|
|
|
@ -16,10 +16,11 @@ public class KryoDecoder extends ByteToMessageDecoder {
|
|||
public KryoDecoder(SerializationManager kryoWrapper) {
|
||||
super();
|
||||
this.kryoWrapper = kryoWrapper;
|
||||
optimize = OptimizeUtilsByteBuf.get();
|
||||
this.optimize = OptimizeUtilsByteBuf.get();
|
||||
}
|
||||
|
||||
protected Object readObject(SerializationManager kryoWrapper, ChannelHandlerContext ctx, ByteBuf in, int length) {
|
||||
@SuppressWarnings("unused")
|
||||
protected Object readObject(SerializationManager kryoWrapper, ChannelHandlerContext context, ByteBuf in, int length) {
|
||||
// no connection here because we haven't created one yet. When we do, we replace this handler with a new one.
|
||||
return kryoWrapper.read(in, length);
|
||||
}
|
||||
|
@ -133,12 +134,12 @@ public class KryoDecoder extends ByteToMessageDecoder {
|
|||
length = optimize.readInt(in, true); // object LENGTH
|
||||
|
||||
// however many we need to
|
||||
out.add(readObject(kryoWrapper, ctx, in, length));
|
||||
out.add(readObject(this.kryoWrapper, ctx, in, length));
|
||||
}
|
||||
// the buffer reader index will be at the correct location, since the read object method advances it.
|
||||
} else {
|
||||
// exactly one!
|
||||
out.add(readObject(kryoWrapper, ctx, in, length));
|
||||
out.add(readObject(this.kryoWrapper, ctx, in, length));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,11 +21,12 @@ public class KryoEncoder extends MessageToByteEncoder<Object> {
|
|||
public KryoEncoder(SerializationManager kryoWrapper) {
|
||||
super();
|
||||
this.kryoWrapper = kryoWrapper;
|
||||
optimize = OptimizeUtilsByteBuf.get();
|
||||
this.optimize = OptimizeUtilsByteBuf.get();
|
||||
}
|
||||
|
||||
// the crypto writer will override this
|
||||
protected void writeObject(SerializationManager kryoWrapper, ChannelHandlerContext ctx, Object msg, ByteBuf buffer) {
|
||||
@SuppressWarnings("unused")
|
||||
protected void writeObject(SerializationManager kryoWrapper, ChannelHandlerContext context, Object msg, ByteBuf buffer) {
|
||||
// no connection here because we haven't created one yet. When we do, we replace this handler with a new one.
|
||||
kryoWrapper.write(buffer, msg);
|
||||
}
|
||||
|
@ -42,7 +43,7 @@ public class KryoEncoder extends MessageToByteEncoder<Object> {
|
|||
out.writeInt(0); // put an int in, which is the same size as reservedLengthIndex
|
||||
|
||||
try {
|
||||
writeObject(kryoWrapper, ctx, msg, out);
|
||||
writeObject(this.kryoWrapper, ctx, msg, out);
|
||||
|
||||
// now set the frame (if it's TCP)!
|
||||
int length = out.readableBytes() - startIndex - reservedLengthIndex; // (reservedLengthLength) 4 is the reserved space for the integer.
|
||||
|
|
|
@ -31,7 +31,8 @@ public class KryoEncoderUdp extends MessageToMessageEncoder<Object> {
|
|||
}
|
||||
|
||||
// the crypto writer will override this
|
||||
protected void writeObject(SerializationManager kryoWrapper, ChannelHandlerContext ctx, Object msg, ByteBuf buffer) {
|
||||
@SuppressWarnings("unused")
|
||||
protected void writeObject(SerializationManager kryoWrapper, ChannelHandlerContext context, Object msg, ByteBuf buffer) {
|
||||
// no connection here because we haven't created one yet. When we do, we replace this handler with a new one.
|
||||
kryoWrapper.write(buffer, msg);
|
||||
}
|
||||
|
@ -43,7 +44,7 @@ public class KryoEncoderUdp extends MessageToMessageEncoder<Object> {
|
|||
ByteBuf outBuffer = Unpooled.buffer(maxSize);
|
||||
|
||||
// no size info, since this is UDP, it is not segmented
|
||||
writeObject(kryoWrapper, ctx, msg, outBuffer);
|
||||
writeObject(this.kryoWrapper, ctx, msg, outBuffer);
|
||||
|
||||
|
||||
// have to check to see if we are too big for UDP!
|
||||
|
|
|
@ -10,6 +10,10 @@ class AsmCachedMethod extends CachedMethod {
|
|||
|
||||
@Override
|
||||
public Object invoke(Object target, Object[] args) throws IllegalAccessException, InvocationTargetException {
|
||||
try {
|
||||
return this.methodAccess.invoke(target, this.methodAccessIndex, args);
|
||||
} catch (Exception ex) {
|
||||
throw new InvocationTargetException(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,14 +1,8 @@
|
|||
package dorkbox.network.rmi;
|
||||
|
||||
import com.esotericsoftware.kryo.Kryo;
|
||||
import com.esotericsoftware.kryo.KryoException;
|
||||
import com.esotericsoftware.kryo.KryoSerializable;
|
||||
import com.esotericsoftware.kryo.Serializer;
|
||||
import com.esotericsoftware.kryo.io.Input;
|
||||
import com.esotericsoftware.kryo.io.Output;
|
||||
|
||||
/** Internal message to invoke methods remotely. */
|
||||
class InvokeMethod implements KryoSerializable, RmiMessages {
|
||||
class InvokeMethod implements RmiMessages {
|
||||
public int objectID;
|
||||
public CachedMethod cachedMethod;
|
||||
public Object[] args;
|
||||
|
@ -18,54 +12,7 @@ class InvokeMethod implements KryoSerializable, RmiMessages {
|
|||
// possible duplicate IDs. A response data of 0 means to not respond.
|
||||
public byte responseData;
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("rawtypes")
|
||||
public void write(Kryo kryo, Output output) {
|
||||
output.writeInt(this.objectID, true);
|
||||
output.writeInt(this.cachedMethod.methodClassID, true);
|
||||
output.writeByte(this.cachedMethod.methodIndex);
|
||||
|
||||
Serializer[] serializers = this.cachedMethod.serializers;
|
||||
Object[] args = this.args;
|
||||
for (int i = 0, n = serializers.length; i < n; i++) {
|
||||
Serializer serializer = serializers[i];
|
||||
if (serializer != null) {
|
||||
kryo.writeObjectOrNull(output, args[i], serializer);
|
||||
} else {
|
||||
kryo.writeClassAndObject(output, args[i]);
|
||||
}
|
||||
}
|
||||
|
||||
output.writeByte(this.responseData);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void read(Kryo kryo, Input input) {
|
||||
this.objectID = input.readInt(true);
|
||||
|
||||
int methodClassID = input.readInt(true);
|
||||
Class<?> methodClass = kryo.getRegistration(methodClassID).getType();
|
||||
|
||||
byte methodIndex = input.readByte();
|
||||
try {
|
||||
this.cachedMethod = RmiBridge.getMethods(kryo, methodClass)[methodIndex];
|
||||
} catch (IndexOutOfBoundsException ex) {
|
||||
throw new KryoException("Invalid method index " + methodIndex + " for class: " + methodClass.getName());
|
||||
}
|
||||
|
||||
Serializer<?>[] serializers = this.cachedMethod.serializers;
|
||||
Class<?>[] parameterTypes = this.cachedMethod.method.getParameterTypes();
|
||||
Object[] args = new Object[serializers.length];
|
||||
this.args = args;
|
||||
for (int i = 0, n = args.length; i < n; i++) {
|
||||
Serializer<?> serializer = serializers[i];
|
||||
if (serializer != null) {
|
||||
args[i] = kryo.readObjectOrNull(input, parameterTypes[i], serializer);
|
||||
} else {
|
||||
args[i] = kryo.readClassAndObject(input);
|
||||
}
|
||||
}
|
||||
|
||||
this.responseData = input.readByte();
|
||||
public InvokeMethod() {
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
package dorkbox.network.rmi;
|
||||
|
||||
import dorkbox.util.objectPool.PoolableObject;
|
||||
|
||||
public class InvokeMethodPoolable implements PoolableObject<InvokeMethod> {
|
||||
@Override
|
||||
public InvokeMethod create() {
|
||||
return new InvokeMethod();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
package dorkbox.network.rmi;
|
||||
|
||||
import com.esotericsoftware.kryo.Kryo;
|
||||
import com.esotericsoftware.kryo.KryoException;
|
||||
import com.esotericsoftware.kryo.Serializer;
|
||||
import com.esotericsoftware.kryo.io.Input;
|
||||
import com.esotericsoftware.kryo.io.Output;
|
||||
|
||||
/** Internal message to invoke methods remotely. */
|
||||
class InvokeMethodSerializer extends Serializer<InvokeMethod> {
|
||||
private RmiBridge rmi;
|
||||
|
||||
public InvokeMethodSerializer(RmiBridge rmi) {
|
||||
this.rmi = rmi;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("rawtypes")
|
||||
public void write(Kryo kryo, Output output, InvokeMethod object) {
|
||||
output.writeInt(object.objectID, true);
|
||||
output.writeInt(object.cachedMethod.methodClassID, true);
|
||||
output.writeByte(object.cachedMethod.methodIndex);
|
||||
|
||||
Serializer[] serializers = object.cachedMethod.serializers;
|
||||
Object[] args = object.args;
|
||||
for (int i = 0, n = serializers.length; i < n; i++) {
|
||||
Serializer serializer = serializers[i];
|
||||
if (serializer != null) {
|
||||
kryo.writeObjectOrNull(output, args[i], serializer);
|
||||
} else {
|
||||
kryo.writeClassAndObject(output, args[i]);
|
||||
}
|
||||
}
|
||||
|
||||
output.writeByte(object.responseData);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InvokeMethod read(Kryo kryo, Input input, Class<InvokeMethod> type) {
|
||||
InvokeMethod invokeMethod = new InvokeMethod();
|
||||
|
||||
invokeMethod.objectID = input.readInt(true);
|
||||
|
||||
int methodClassID = input.readInt(true);
|
||||
Class<?> methodClass = kryo.getRegistration(methodClassID).getType();
|
||||
|
||||
byte methodIndex = input.readByte();
|
||||
try {
|
||||
invokeMethod.cachedMethod = this.rmi.getMethods(kryo, methodClass)[methodIndex];
|
||||
} catch (IndexOutOfBoundsException ex) {
|
||||
throw new KryoException("Invalid method index " + methodIndex + " for class: " + methodClass.getName());
|
||||
}
|
||||
|
||||
Serializer<?>[] serializers = invokeMethod.cachedMethod.serializers;
|
||||
Class<?>[] parameterTypes = invokeMethod.cachedMethod.method.getParameterTypes();
|
||||
Object[] args = new Object[serializers.length];
|
||||
invokeMethod.args = args;
|
||||
for (int i = 0, n = args.length; i < n; i++) {
|
||||
Serializer<?> serializer = serializers[i];
|
||||
if (serializer != null) {
|
||||
args[i] = kryo.readObjectOrNull(input, parameterTypes[i], serializer);
|
||||
} else {
|
||||
args[i] = kryo.readClassAndObject(input);
|
||||
}
|
||||
}
|
||||
|
||||
invokeMethod.responseData = input.readByte();
|
||||
|
||||
return invokeMethod;
|
||||
}
|
||||
}
|
|
@ -12,6 +12,8 @@ import dorkbox.network.connection.Connection;
|
|||
import dorkbox.network.connection.EndPoint;
|
||||
import dorkbox.network.connection.ListenerRaw;
|
||||
import dorkbox.network.util.exceptions.NetException;
|
||||
import dorkbox.util.objectPool.ObjectPool;
|
||||
import dorkbox.util.objectPool.ObjectPoolHolder;
|
||||
|
||||
/** Handles network communication when methods are invoked on a proxy. */
|
||||
class RemoteInvocationHandler implements InvocationHandler {
|
||||
|
@ -26,6 +28,9 @@ class RemoteInvocationHandler implements InvocationHandler {
|
|||
private boolean transmitExceptions = true;
|
||||
|
||||
private boolean remoteToString;
|
||||
private boolean udp;
|
||||
private boolean udt;
|
||||
|
||||
private Byte lastResponseID;
|
||||
private byte nextResponseId = 1;
|
||||
|
||||
|
@ -37,8 +42,11 @@ class RemoteInvocationHandler implements InvocationHandler {
|
|||
final InvokeMethodResult[] responseTable = new InvokeMethodResult[64];
|
||||
final boolean[] pendingResponses = new boolean[64];
|
||||
|
||||
public RemoteInvocationHandler(Connection connection, final int objectID) {
|
||||
private final ObjectPool<InvokeMethod> invokeMethodPool;
|
||||
|
||||
public RemoteInvocationHandler(ObjectPool<InvokeMethod> invokeMethodPool, Connection connection, final int objectID) {
|
||||
super();
|
||||
this.invokeMethodPool = invokeMethodPool;
|
||||
this.connection = connection;
|
||||
this.objectID = objectID;
|
||||
|
||||
|
@ -97,6 +105,12 @@ class RemoteInvocationHandler implements InvocationHandler {
|
|||
} else if (name.equals("setTransmitExceptions")) {
|
||||
this.transmitExceptions = (Boolean) args[0];
|
||||
return null;
|
||||
} else if (name.equals("setUDP")) {
|
||||
this.udp = (Boolean)args[0];
|
||||
return null;
|
||||
} else if (name.equals("setUDT")) {
|
||||
this.udt = (Boolean)args[0];
|
||||
return null;
|
||||
} else if (name.equals("setRemoteToString")) {
|
||||
this.remoteToString = (Boolean) args[0];
|
||||
return null;
|
||||
|
@ -124,12 +138,15 @@ class RemoteInvocationHandler implements InvocationHandler {
|
|||
return "<proxy>";
|
||||
}
|
||||
|
||||
InvokeMethod invokeMethod = new InvokeMethod();
|
||||
EndPoint endPoint = this.connection.getEndPoint();
|
||||
RmiBridge rmi = (RmiBridge) endPoint.rmi();
|
||||
|
||||
ObjectPoolHolder<InvokeMethod> invokeMethodHolder = this.invokeMethodPool.take();
|
||||
InvokeMethod invokeMethod = invokeMethodHolder.getValue();
|
||||
invokeMethod.objectID = this.objectID;
|
||||
invokeMethod.args = args;
|
||||
|
||||
EndPoint endPoint = this.connection.getEndPoint();
|
||||
CachedMethod[] cachedMethods = RmiBridge.getMethods(endPoint.getSerialization().getSingleInstanceUnsafe(), method.getDeclaringClass());
|
||||
CachedMethod[] cachedMethods = rmi.getMethods(endPoint.getSerialization().getSingleInstanceUnsafe(), method.getDeclaringClass());
|
||||
for (int i = 0, n = cachedMethods.length; i < n; i++) {
|
||||
CachedMethod cachedMethod = cachedMethods[i];
|
||||
if (cachedMethod.method.equals(method)) {
|
||||
|
@ -145,7 +162,7 @@ class RemoteInvocationHandler implements InvocationHandler {
|
|||
|
||||
|
||||
// An invocation doesn't need a response is if it's async and no return values or exceptions are wanted back.
|
||||
boolean needsResponse = this.transmitReturnValue || this.transmitExceptions || !this.nonBlocking;
|
||||
boolean needsResponse = !this.udp && (this.transmitReturnValue || this.transmitExceptions || !this.nonBlocking);
|
||||
byte responseID = 0;
|
||||
if (needsResponse) {
|
||||
synchronized (this) {
|
||||
|
@ -159,17 +176,23 @@ class RemoteInvocationHandler implements InvocationHandler {
|
|||
// Pack other data into the high bits.
|
||||
byte responseData = responseID;
|
||||
if (this.transmitReturnValue) {
|
||||
responseData |= RmiBridge.returnValMask;
|
||||
responseData |= RmiBridge.returnValueMask;
|
||||
}
|
||||
if (this.transmitExceptions) {
|
||||
responseData |= RmiBridge.returnExMask;
|
||||
responseData |= RmiBridge.returnExceptionMask;
|
||||
}
|
||||
invokeMethod.responseData = responseData;
|
||||
} else {
|
||||
invokeMethod.responseData = 0; // A response data of 0 means to not respond.
|
||||
}
|
||||
|
||||
if (this.udp) {
|
||||
this.connection.send().UDP(invokeMethod).flush();
|
||||
} else if (this.udt) {
|
||||
this.connection.send().UDT(invokeMethod).flush();
|
||||
} else {
|
||||
this.connection.send().TCP(invokeMethod).flush();
|
||||
}
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
String argString = "";
|
||||
|
@ -182,8 +205,9 @@ class RemoteInvocationHandler implements InvocationHandler {
|
|||
}
|
||||
|
||||
this.lastResponseID = (byte)(invokeMethod.responseData & RmiBridge.responseIdMask);
|
||||
this.invokeMethodPool.release(invokeMethodHolder);
|
||||
|
||||
if (this.nonBlocking) {
|
||||
if (this.nonBlocking || this.udp) {
|
||||
Class<?> returnType = method.getReturnType();
|
||||
if (returnType.isPrimitive()) {
|
||||
if (returnType == int.class) {
|
||||
|
@ -232,7 +256,6 @@ class RemoteInvocationHandler implements InvocationHandler {
|
|||
}
|
||||
|
||||
private Object waitForResponse(byte responseID) {
|
||||
|
||||
long endTime = System.currentTimeMillis() + this.timeoutMillis;
|
||||
long remaining = this.timeoutMillis;
|
||||
|
||||
|
|
|
@ -15,11 +15,11 @@ public interface RemoteObject {
|
|||
/**
|
||||
* Sets the blocking behavior when invoking a remote method. Default is false.
|
||||
*
|
||||
* @param nonBlocking If false, the invoking thread will wait for the remote method to return or timeout (default). If
|
||||
* true, the invoking thread will not wait for a response. The method will return immediately and the return
|
||||
* value should be ignored. If they are being transmitted, the return value or any thrown exception can later
|
||||
* be retrieved with {@link #waitForLastResponse()} or {@link #waitForResponse(byte)}. The responses will be
|
||||
* stored until retrieved, so each method call should have a matching retrieve.
|
||||
* @param nonBlocking If false, the invoking thread will wait for the remote method to return or timeout (default). If true, the
|
||||
* invoking thread will not wait for a response. The method will return immediately and the return value should be ignored. If
|
||||
* they are being transmitted, the return value or any thrown exception can later be retrieved with
|
||||
* {@link #waitForLastResponse()} or {@link #waitForResponse(byte)}. The responses will be stored until retrieved, so each method
|
||||
* call should have a matching retrieve.
|
||||
*/
|
||||
public void setNonBlocking(boolean nonBlocking);
|
||||
|
||||
|
@ -27,35 +27,45 @@ public interface RemoteObject {
|
|||
* Sets whether return values are sent back when invoking a remote method. Default is true.
|
||||
*
|
||||
* @param transmit If true, then the return value for non-blocking method invocations can be retrieved with
|
||||
* {@link #waitForLastResponse()} or {@link #waitForResponse(byte)}. If false, then non-primitive return values
|
||||
* for remote method invocations are not sent by the remote side of the connection and the response can never
|
||||
* be retrieved. This can also be used to save bandwidth if you will not check the return value of a blocking
|
||||
* remote invocation. Note that an exception could still be returned by {@link #waitForLastResponse()} or
|
||||
* {@link #waitForResponse(byte)} if {@link #setTransmitExceptions(boolean)} is true.
|
||||
* {@link #waitForLastResponse()} or {@link #waitForResponse(byte)}. If false, then non-primitive return values for remote method
|
||||
* invocations are not sent by the remote side of the connection and the response can never be retrieved. This can also be used
|
||||
* to save bandwidth if you will not check the return value of a blocking remote invocation. Note that an exception could still
|
||||
* be returned by {@link #waitForLastResponse()} or {@link #waitForResponse(byte)} if {@link #setTransmitExceptions(boolean)} is
|
||||
* true.
|
||||
*/
|
||||
public void setTransmitReturnValue(boolean transmit);
|
||||
|
||||
/**
|
||||
* Sets whether exceptions are sent back when invoking a remote method. Default is true.
|
||||
*
|
||||
* @param transmit If false, exceptions will be unhandled and rethrown as RuntimeExceptions inside the invoking
|
||||
* thread. This is the legacy behavior. If true, behavior is dependent on whether
|
||||
* {@link #setNonBlocking(boolean)}. If non-blocking is true, the exception will be serialized and sent back to
|
||||
* the call site of the remotely invoked method, where it will be re-thrown. If non-blocking is false, an
|
||||
* exception will not be thrown in the calling thread but instead can be retrieved with
|
||||
* @param transmit If false, exceptions will be unhandled and rethrown as RuntimeExceptions inside the invoking thread. This is the
|
||||
* legacy behavior. If true, behavior is dependent on whether {@link #setNonBlocking(boolean)}. If non-blocking is true, the
|
||||
* exception will be serialized and sent back to the call site of the remotely invoked method, where it will be re-thrown. If
|
||||
* non-blocking is false, an exception will not be thrown in the calling thread but instead can be retrieved with
|
||||
* {@link #waitForLastResponse()} or {@link #waitForResponse(byte)}, similar to a return value.
|
||||
*/
|
||||
public void setTransmitExceptions(boolean transmit);
|
||||
|
||||
/**
|
||||
* If false, calls to {@link Object#toString()} will return "<proxy>" instead of being invoking the remote method.
|
||||
* Default is false.
|
||||
* If true, UDP will be used to send the remote method invocation. UDP remote method invocations will never return a response and the
|
||||
* invoking thread will not wait for a response.
|
||||
*/
|
||||
public void setUDP(boolean udp);
|
||||
|
||||
/**
|
||||
* If true, UDT will be used to send the remote method invocation. UDT remote method invocations <b>will</b> return a response and the
|
||||
* invoking thread <b>will</b> wait for a response.
|
||||
*/
|
||||
public void setUDT(boolean udt);
|
||||
|
||||
/**
|
||||
* If false, calls to {@link Object#toString()} will return "<proxy>" instead of being invoking the remote method. Default is false.
|
||||
*/
|
||||
public void setRemoteToString(boolean remoteToString);
|
||||
|
||||
/**
|
||||
* Waits for the response to the last method invocation to be received or the response timeout to be reached. Must not
|
||||
* be called from the connection's update thread.
|
||||
* Waits for the response to the last method invocation to be received or the response timeout to be reached. Must not be called from
|
||||
* the connection's update thread.
|
||||
*
|
||||
* @see RmiBridge#getRemoteObject(dorkbox.Connection.connection.interfaces.IConnection.Connection, int, Class...)
|
||||
*/
|
||||
|
@ -65,10 +75,10 @@ public interface RemoteObject {
|
|||
public byte getLastResponseID();
|
||||
|
||||
/**
|
||||
* Waits for the specified method invocation response to be received or the response timeout to be reached. Must not
|
||||
* be called from the connection's update thread. Response IDs use a six bit identifier, with one identifier reserved
|
||||
* for "no response". This means that this method should be called to get the result for a non-blocking call before an
|
||||
* additional 63 non-blocking calls are made, or risk undefined behavior due to identical IDs.
|
||||
* Waits for the specified method invocation response to be received or the response timeout to be reached. Must not be called from the
|
||||
* connection's update thread. Response IDs use a six bit identifier, with one identifier reserved for "no response". This means that
|
||||
* this method should be called to get the result for a non-blocking call before an additional 63 non-blocking calls are made, or risk
|
||||
* undefined behavior due to identical IDs.
|
||||
*
|
||||
* @see RmiBridge#getRemoteObject(dorkbox.Connection.connection.interfaces.IConnection.Connection, int, Class...)
|
||||
*/
|
||||
|
|
|
@ -7,6 +7,7 @@ import com.esotericsoftware.kryo.io.Output;
|
|||
import com.sun.xml.internal.ws.encoding.soap.SerializationException;
|
||||
|
||||
import dorkbox.network.connection.Connection;
|
||||
import dorkbox.network.connection.EndPoint;
|
||||
|
||||
/**
|
||||
* Serializes an object registered with the RmiBridge so the receiving side
|
||||
|
@ -16,11 +17,16 @@ import dorkbox.network.connection.Connection;
|
|||
* @author Nathan Sweet <misc@n4te.com>
|
||||
*/
|
||||
public class RemoteObjectSerializer<T> extends Serializer<T> {
|
||||
|
||||
private final RmiBridge rmi;
|
||||
|
||||
public RemoteObjectSerializer(EndPoint endpoint) {
|
||||
this.rmi = (RmiBridge) endpoint.rmi();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(Kryo kryo, Output output, T object) {
|
||||
@SuppressWarnings("unchecked")
|
||||
Connection connection = (Connection) kryo.getContext().get(Connection.connection);
|
||||
int id = RmiBridge.getRegisteredId(connection, object);
|
||||
int id = this.rmi.getRegisteredId(object);
|
||||
if (id == Integer.MAX_VALUE) {
|
||||
throw new SerializationException("Object not found in an ObjectSpace: " + object);
|
||||
}
|
||||
|
@ -33,6 +39,6 @@ public class RemoteObjectSerializer<T> extends Serializer<T> {
|
|||
public T read(Kryo kryo, Input input, Class type) {
|
||||
int objectID = input.readInt(true);
|
||||
Connection connection = (Connection) kryo.getContext().get(Connection.connection);
|
||||
return (T) RmiBridge.getRemoteObject(connection, objectID, type);
|
||||
return (T) this.rmi.getRemoteObject(connection, objectID, type);
|
||||
}
|
||||
}
|
22
Dorkbox-Network/src/dorkbox/network/rmi/Rmi.java
Normal file
22
Dorkbox-Network/src/dorkbox/network/rmi/Rmi.java
Normal file
|
@ -0,0 +1,22 @@
|
|||
package dorkbox.network.rmi;
|
||||
|
||||
|
||||
|
||||
public interface Rmi {
|
||||
/**
|
||||
* Registers an object to allow the remote end of the RmiBridge connections to access it using the specified ID.
|
||||
*
|
||||
* @param objectID Must not be Integer.MAX_VALUE.
|
||||
*/
|
||||
public void register(int objectID, Object object);
|
||||
|
||||
/**
|
||||
* Removes an object. The remote end of the RmiBridge connection will no longer be able to access it.
|
||||
*/
|
||||
public void remove(int objectID);
|
||||
|
||||
/**
|
||||
* Removes an object. The remote end of the RmiBridge connection will no longer be able to access it.
|
||||
*/
|
||||
public void remove(Object object);
|
||||
}
|
|
@ -9,9 +9,10 @@ import java.util.Arrays;
|
|||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
|
||||
|
@ -30,9 +31,11 @@ import dorkbox.network.connection.ListenerRaw;
|
|||
import dorkbox.network.util.SerializationManager;
|
||||
import dorkbox.network.util.exceptions.NetException;
|
||||
import dorkbox.util.collections.ObjectIntMap;
|
||||
import dorkbox.util.objectPool.ObjectPool;
|
||||
import dorkbox.util.objectPool.ObjectPoolFactory;
|
||||
|
||||
/**
|
||||
* Allows methods on objects to be invoked remotely over TCP. Objects are
|
||||
* Allows methods on objects to be invoked remotely over TCP, UDP, or UDT. Objects are
|
||||
* {@link #register(int, Object) registered} with an ID. The remote end of
|
||||
* connections that have been {@link #addConnection(Connection) added} are
|
||||
* allowed to {@link #getRemoteObject(Connection, int, Class) access} registered
|
||||
|
@ -46,53 +49,48 @@ import dorkbox.util.collections.ObjectIntMap;
|
|||
*
|
||||
* @author Nathan Sweet <misc@n4te.com>, Nathan Robinson
|
||||
*/
|
||||
public class RmiBridge {
|
||||
public class RmiBridge implements Rmi {
|
||||
private static final String OBJECT_ID = "objectID";
|
||||
|
||||
static CopyOnWriteArrayList<RmiBridge> instances = new CopyOnWriteArrayList<RmiBridge>();
|
||||
|
||||
private static final HashMap<Class<?>, CachedMethod[]> methodCache = new HashMap<Class<?>, CachedMethod[]>();
|
||||
static final int returnValueMask = 1 << 7;
|
||||
static final int returnExceptionMask = 1 << 6;
|
||||
static final int responseIdMask = 0xFF & ~returnValueMask & ~returnExceptionMask;
|
||||
|
||||
|
||||
static final int returnValMask = 1 << 7;
|
||||
static final int returnExMask = 1 << 6;
|
||||
static final int responseIdMask = 0xff & ~returnValMask & ~returnExMask;
|
||||
// the name of who created this RmiBridge
|
||||
private final org.slf4j.Logger logger;
|
||||
|
||||
private static boolean asm = true;
|
||||
private HashMap<Class<?>, CachedMethod[]> methodCache = new HashMap<Class<?>, CachedMethod[]>();
|
||||
|
||||
// can be access by DIFFERENT threads.
|
||||
volatile IntMap<Object> idToObject = new IntMap<Object>();
|
||||
volatile ObjectIntMap<Object> objectToID = new ObjectIntMap<Object>();
|
||||
private final boolean asm;
|
||||
|
||||
private CopyOnWriteArrayList<Connection> connections = new CopyOnWriteArrayList<Connection>();
|
||||
// can be accessed by DIFFERENT threads.
|
||||
private ReentrantReadWriteLock objectLock = new ReentrantReadWriteLock();
|
||||
private IntMap<Object> idToObject = new IntMap<Object>();
|
||||
private ObjectIntMap<Object> objectToID = new ObjectIntMap<Object>();
|
||||
|
||||
private Executor executor;
|
||||
|
||||
// the name of who created this object space.
|
||||
private final org.slf4j.Logger logger;
|
||||
// 4096 concurrent method invocations max
|
||||
private final ObjectPool<InvokeMethod> invokeMethodPool = ObjectPoolFactory.create(new InvokeMethodPoolable(), 4096);
|
||||
|
||||
private final ListenerRaw<Connection, InvokeMethod> invokeListener = new ListenerRaw<Connection, InvokeMethod>() {
|
||||
@Override
|
||||
public void received(final Connection connection, final InvokeMethod invokeMethod) {
|
||||
boolean found = false;
|
||||
|
||||
Iterator<Connection> iterator = RmiBridge.this.connections.iterator();
|
||||
while (iterator.hasNext()) {
|
||||
Connection c = iterator.next();
|
||||
if (c == connection) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// The InvokeMethod message is not for a connection in this ObjectSpace.
|
||||
if (!found) {
|
||||
return;
|
||||
}
|
||||
ReadLock readLock = RmiBridge.this.objectLock.readLock();
|
||||
readLock.lock();
|
||||
|
||||
final Object target = RmiBridge.this.idToObject.get(invokeMethod.objectID);
|
||||
|
||||
readLock.unlock();
|
||||
|
||||
|
||||
if (target == null) {
|
||||
RmiBridge.this.logger.warn("Ignoring remote invocation request for unknown object ID: {}", invokeMethod.objectID);
|
||||
Logger logger2 = RmiBridge.this.logger;
|
||||
if (logger2.isWarnEnabled()) {
|
||||
logger2.warn("Ignoring remote invocation request for unknown object ID: {}", invokeMethod.objectID);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -112,23 +110,18 @@ public class RmiBridge {
|
|||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disconnected(Connection connection) {
|
||||
removeConnection(connection);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates an ObjectSpace with no connections. Connections must be
|
||||
* Creates an RmiBridge with no connections. Connections must be
|
||||
* {@link #connectionConnected(Connection) added} to allow the remote end of
|
||||
* the connections to access objects in this ObjectSpace.
|
||||
* <p>
|
||||
* For safety, this should ONLY be called by {@link EndPoint#getRmiBridge() }
|
||||
*/
|
||||
public RmiBridge(Logger logger) {
|
||||
public RmiBridge(Logger logger, SerializationManager serializationManager) {
|
||||
this.logger = logger;
|
||||
instances.addIfAbsent(this);
|
||||
this.asm = serializationManager.getSingleInstanceUnsafe().getAsmEnabled();
|
||||
|
||||
registerClasses(serializationManager);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -143,22 +136,12 @@ public class RmiBridge {
|
|||
this.executor = executor;
|
||||
}
|
||||
|
||||
/** If true, an attempt will be made to use ReflectASM for invoking methods. Default is true. */
|
||||
static public void setAsm(boolean asm) {
|
||||
RmiBridge.asm = asm;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers an object to allow the remote end of the ObjectSpace's
|
||||
* connections to access it using the specified ID.
|
||||
* <p>
|
||||
* If a connection is added to multiple ObjectSpaces, the same object ID
|
||||
* should not be registered in more than one of those ObjectSpaces.
|
||||
* Registers an object to allow the remote end of the RmiBridge connections to access it using the specified ID.
|
||||
*
|
||||
* @param objectID
|
||||
* Must not be Integer.MAX_VALUE.
|
||||
* @see #getRemoteObject(Connection, int, Class...)
|
||||
* @param objectID Must not be Integer.MAX_VALUE.
|
||||
*/
|
||||
@Override
|
||||
public void register(int objectID, Object object) {
|
||||
if (objectID == Integer.MAX_VALUE) {
|
||||
throw new IllegalArgumentException("objectID cannot be Integer.MAX_VALUE.");
|
||||
|
@ -166,9 +149,15 @@ public class RmiBridge {
|
|||
if (object == null) {
|
||||
throw new IllegalArgumentException("object cannot be null.");
|
||||
}
|
||||
|
||||
WriteLock writeLock = RmiBridge.this.objectLock.writeLock();
|
||||
writeLock.lock();
|
||||
|
||||
this.idToObject.put(objectID, object);
|
||||
this.objectToID.put(object, objectID);
|
||||
|
||||
writeLock.unlock();
|
||||
|
||||
Logger logger2 = this.logger;
|
||||
if (logger2.isTraceEnabled()) {
|
||||
logger2.trace("Object registered with ObjectSpace as {}:{}", objectID, object);
|
||||
|
@ -176,33 +165,20 @@ public class RmiBridge {
|
|||
}
|
||||
|
||||
/**
|
||||
* Causes this ObjectSpace to stop listening to the connections for method
|
||||
* invocation messages.
|
||||
*/
|
||||
public void close() {
|
||||
Iterator<Connection> iterator = this.connections.iterator();
|
||||
while (iterator.hasNext()) {
|
||||
Connection connection = iterator.next();
|
||||
connection.listeners().remove(this.invokeListener);
|
||||
}
|
||||
|
||||
instances.remove(this);
|
||||
Logger logger2 = this.logger;
|
||||
if (logger2.isTraceEnabled()) {
|
||||
logger2.trace("Closed ObjectSpace.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes an object. The remote end of the ObjectSpace's connections will
|
||||
* no longer be able to access it.
|
||||
* Removes an object. The remote end of the RmiBridge connection will no longer be able to access it.
|
||||
*/
|
||||
@Override
|
||||
public void remove(int objectID) {
|
||||
WriteLock writeLock = RmiBridge.this.objectLock.writeLock();
|
||||
writeLock.lock();
|
||||
|
||||
Object object = this.idToObject.remove(objectID);
|
||||
if (object != null) {
|
||||
this.objectToID.remove(object, 0);
|
||||
}
|
||||
|
||||
writeLock.unlock();
|
||||
|
||||
Logger logger2 = this.logger;
|
||||
if (logger2.isTraceEnabled()) {
|
||||
logger2.trace("Object {} removed from ObjectSpace: {}", objectID, object);
|
||||
|
@ -210,11 +186,15 @@ public class RmiBridge {
|
|||
}
|
||||
|
||||
/**
|
||||
* Removes an object. The remote end of the ObjectSpace's connections will
|
||||
* no longer be able to access it.
|
||||
* Removes an object. The remote end of the RmiBridge connection will no longer be able to access it.
|
||||
*/
|
||||
@Override
|
||||
public void remove(Object object) {
|
||||
WriteLock writeLock = RmiBridge.this.objectLock.writeLock();
|
||||
writeLock.lock();
|
||||
|
||||
if (!this.idToObject.containsValue(object, true)) {
|
||||
writeLock.unlock();
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -222,52 +202,27 @@ public class RmiBridge {
|
|||
this.idToObject.remove(objectID);
|
||||
this.objectToID.remove(object, 0);
|
||||
|
||||
writeLock.unlock();
|
||||
|
||||
Logger logger2 = this.logger;
|
||||
if (logger2.isTraceEnabled()) {
|
||||
logger2.trace("Object {} removed from ObjectSpace: {}", objectID, object);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows the remote end of the specified connection to access objects
|
||||
* registered in this ObjectSpace.
|
||||
*/
|
||||
public void addConnection(Connection connection) {
|
||||
if (connection == null) {
|
||||
throw new IllegalArgumentException("connection cannot be null.");
|
||||
}
|
||||
|
||||
this.connections.addIfAbsent(connection);
|
||||
connection.listeners().add(this.invokeListener);
|
||||
|
||||
Logger logger2 = this.logger;
|
||||
if (logger2.isTraceEnabled()) {
|
||||
logger2.trace("Added connection to ObjectSpace: {}", connection);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the specified connection, it will no longer be able to access
|
||||
* objects registered in this ObjectSpace.
|
||||
* @return the invocation listener
|
||||
*/
|
||||
public void removeConnection(Connection connection) {
|
||||
if (connection == null) {
|
||||
throw new IllegalArgumentException("connection cannot be null.");
|
||||
}
|
||||
|
||||
connection.listeners().remove(this.invokeListener);
|
||||
this.connections.remove(connection);
|
||||
|
||||
Logger logger2 = this.logger;
|
||||
if (logger2.isTraceEnabled()) {
|
||||
logger2.trace("Removed connection from ObjectSpace: {}", connection);
|
||||
}
|
||||
@SuppressWarnings("rawtypes")
|
||||
public ListenerRaw getListener() {
|
||||
return this.invokeListener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes the method on the object and, if necessary, sends the result back
|
||||
* to the connection that made the invocation request. This method is
|
||||
* invoked on the update thread of the {@link EndPoint} for this ObjectSpace
|
||||
* invoked on the update thread of the {@link EndPoint} for this RmiBridge
|
||||
* and unless an {@link #setExecutor(Executor) executor} has been set.
|
||||
*
|
||||
* @param connection
|
||||
|
@ -292,8 +247,8 @@ public class RmiBridge {
|
|||
|
||||
|
||||
byte responseData = invokeMethod.responseData;
|
||||
boolean transmitReturnVal = (responseData & returnValMask) == returnValMask;
|
||||
boolean transmitExceptions = (responseData & returnExMask) == returnExMask;
|
||||
boolean transmitReturnVal = (responseData & returnValueMask) == returnValueMask;
|
||||
boolean transmitExceptions = (responseData & returnExceptionMask) == returnExceptionMask;
|
||||
int responseID = responseData & responseIdMask;
|
||||
|
||||
Object result = null;
|
||||
|
@ -342,17 +297,6 @@ public class RmiBridge {
|
|||
// logger.error("{} sent data: {} with id ({})", connection, result, invokeMethod.responseID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Identical to {@link #getRemoteObject(C, int, Class...)} except returns
|
||||
* the object cast to the specified interface type. The returned object
|
||||
* still implements {@link RemoteObject}.
|
||||
*/
|
||||
static public <T, C extends Connection> T getRemoteObject(final C connection, int objectID, Class<T> iface) {
|
||||
@SuppressWarnings({"unchecked"})
|
||||
T remoteObject = (T) getRemoteObject(connection, objectID, new Class<?>[] {iface});
|
||||
return remoteObject;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a proxy object that implements the specified interfaces. Methods
|
||||
* invoked on the proxy object will be invoked remotely on the object with
|
||||
|
@ -377,7 +321,7 @@ public class RmiBridge {
|
|||
*
|
||||
* @see RemoteObject
|
||||
*/
|
||||
public static RemoteObject getRemoteObject(Connection connection, int objectID, Class<?>... ifaces) {
|
||||
public RemoteObject getRemoteObject(Connection connection, int objectID, Class<?>... ifaces) {
|
||||
if (connection == null) {
|
||||
throw new IllegalArgumentException("connection cannot be null.");
|
||||
}
|
||||
|
@ -391,11 +335,11 @@ public class RmiBridge {
|
|||
|
||||
return (RemoteObject) Proxy.newProxyInstance(RmiBridge.class.getClassLoader(),
|
||||
temp,
|
||||
new RemoteInvocationHandler(connection, objectID));
|
||||
new RemoteInvocationHandler(this.invokeMethodPool, connection, objectID));
|
||||
}
|
||||
|
||||
static CachedMethod[] getMethods(Kryo kryo, Class<?> type) {
|
||||
CachedMethod[] cachedMethods = methodCache.get(type); // Maybe should cache per Kryo instance?
|
||||
public CachedMethod[] getMethods(Kryo kryo, Class<?> type) {
|
||||
CachedMethod[] cachedMethods = this.methodCache.get(type); // Maybe should cache per Kryo instance?
|
||||
if (cachedMethods != null) {
|
||||
return cachedMethods;
|
||||
}
|
||||
|
@ -453,7 +397,7 @@ public class RmiBridge {
|
|||
});
|
||||
|
||||
Object methodAccess = null;
|
||||
if (asm && !Util.isAndroid && Modifier.isPublic(type.getModifiers())) {
|
||||
if (this.asm && !Util.isAndroid && Modifier.isPublic(type.getModifiers())) {
|
||||
methodAccess = MethodAccess.get(type);
|
||||
}
|
||||
|
||||
|
@ -493,73 +437,47 @@ public class RmiBridge {
|
|||
cachedMethods[i] = cachedMethod;
|
||||
}
|
||||
|
||||
methodCache.put(type, cachedMethods);
|
||||
this.methodCache.put(type, cachedMethods);
|
||||
return cachedMethods;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the first object registered with the specified ID in any of the
|
||||
* ObjectSpaces the specified connection belongs to.
|
||||
* Returns the object registered with the specified ID.
|
||||
*/
|
||||
static Object getRegisteredObject(Connection connection, int objectID) {
|
||||
CopyOnWriteArrayList<RmiBridge> instances = RmiBridge.instances;
|
||||
for (RmiBridge objectSpace : instances) {
|
||||
// Check if the connection is in this ObjectSpace.
|
||||
Iterator<Connection> iterator = objectSpace.connections.iterator();
|
||||
while (iterator.hasNext()) {
|
||||
Connection c = iterator.next();
|
||||
if (c != connection) {
|
||||
continue;
|
||||
}
|
||||
Object getRegisteredObject(int objectID) {
|
||||
ReadLock readLock = this.objectLock.readLock();
|
||||
readLock.lock();
|
||||
|
||||
// Find an object with the objectID.
|
||||
Object object = objectSpace.idToObject.get(objectID);
|
||||
if (object != null) {
|
||||
Object object = this.idToObject.get(objectID);
|
||||
readLock.unlock();
|
||||
|
||||
return object;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the first ID registered for the specified object with any of the
|
||||
* ObjectSpaces the specified connection belongs to, or Integer.MAX_VALUE
|
||||
* if not found.
|
||||
* Returns the ID registered for the specified object, or Integer.MAX_VALUE if not found.
|
||||
*/
|
||||
public static int getRegisteredId(Connection connection, Object object) {
|
||||
CopyOnWriteArrayList<RmiBridge> instances = RmiBridge.instances;
|
||||
for (RmiBridge objectSpace : instances) {
|
||||
// Check if the connection is in this ObjectSpace.
|
||||
Iterator<Connection> iterator = objectSpace.connections.iterator();
|
||||
while (iterator.hasNext()) {
|
||||
Connection c = iterator.next();
|
||||
if (c != connection) {
|
||||
continue;
|
||||
}
|
||||
|
||||
public int getRegisteredId(Object object) {
|
||||
// Find an ID with the object.
|
||||
int id = objectSpace.objectToID.get(object, Integer.MAX_VALUE);
|
||||
if (id != Integer.MAX_VALUE) {
|
||||
ReadLock readLock = this.objectLock.readLock();
|
||||
|
||||
readLock.lock();
|
||||
int id = this.objectToID.get(object, Integer.MAX_VALUE);
|
||||
readLock.unlock();
|
||||
|
||||
return id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Integer.MAX_VALUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the classes needed to use ObjectSpaces. This should be called
|
||||
* before any connections are opened.
|
||||
* Registers the classes needed to use RMI. This should be called before any connections are opened.
|
||||
*/
|
||||
public static void registerClasses(final SerializationManager smanager) {
|
||||
smanager.registerForRmiClasses(new RmiRegisterClassesCallback() {
|
||||
private void registerClasses(final SerializationManager manager) {
|
||||
manager.registerForRmiClasses(new RmiRegisterClassesCallback() {
|
||||
@Override
|
||||
public void registerForClasses(Kryo kryo) {
|
||||
kryo.register(Object[].class);
|
||||
kryo.register(InvokeMethod.class);
|
||||
kryo.register(InvokeMethod.class, new InvokeMethodSerializer(RmiBridge.this));
|
||||
|
||||
FieldSerializer<InvokeMethodResult> resultSerializer = new FieldSerializer<InvokeMethodResult>(kryo, InvokeMethodResult.class) {
|
||||
@Override
|
||||
|
@ -590,7 +508,7 @@ public class RmiBridge {
|
|||
public Object read(Kryo kryo, Input input, Class<Object> type) {
|
||||
int objectID = input.readInt(true);
|
||||
Connection connection = (Connection) kryo.getContext().get(Connection.connection);
|
||||
Object object = getRegisteredObject(connection, objectID);
|
||||
Object object = getRegisteredObject(objectID);
|
||||
if (object == null) {
|
||||
final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(RmiBridge.class);
|
||||
logger.warn("Unknown object ID {} for connection: {}", objectID, connection);
|
||||
|
|
|
@ -659,6 +659,7 @@ public class KryoSerializationManager implements SerializationManager {
|
|||
}
|
||||
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static void compress(ByteBuf inputBuffer, ByteBuf outputBuffer, int length, Deflater compress) {
|
||||
|
||||
byte[] in = new byte[inputBuffer.readableBytes()];
|
||||
|
|
Loading…
Reference in New Issue
Block a user