RMI working now

This commit is contained in:
nathan 2017-09-25 23:06:21 +02:00
parent e84688ba85
commit f8d71b96dd
18 changed files with 369 additions and 311 deletions

View File

@ -42,7 +42,7 @@ import com.esotericsoftware.kryo.util.IntMap;
import com.esotericsoftware.kryo.util.MapReferenceResolver;
import dorkbox.network.connection.ping.PingMessage;
import dorkbox.network.rmi.CachedMethod;
import dorkbox.network.rmi.ClassDefinitions;
import dorkbox.network.rmi.InvocationHandlerSerializer;
import dorkbox.network.rmi.InvocationResultSerializer;
import dorkbox.network.rmi.InvokeMethod;
@ -153,9 +153,11 @@ class CryptoSerializationManager implements dorkbox.network.util.CryptoSerializa
}
private static class RemoteImplClass {
private final Class<?> ifaceClass;
private final Class<?> implClass;
RemoteImplClass(final Class<?> implClass) {
RemoteImplClass(final Class<?> ifaceClass, final Class<?> implClass) {
this.ifaceClass = ifaceClass;
this.implClass = implClass;
}
}
@ -174,7 +176,9 @@ class CryptoSerializationManager implements dorkbox.network.util.CryptoSerializa
private RemoteObjectSerializer remoteObjectSerializer;
// used to track which interface -> implementation, for use by RMI
private final IntMap<Class<?>> rmiInterfaceToImpl = new IntMap<Class<?>>();
private final IntMap<Class<?>> rmiIdToImpl = new IntMap<Class<?>>();
private final IntMap<Class<?>> rmiIdToIface = new IntMap<Class<?>>();
private final ClassDefinitions classDefinitions = new ClassDefinitions();
/**
* By default, the serialization manager will compress+encrypt data to connections with remote IPs, and only compress on the loopback IP
@ -207,12 +211,12 @@ class CryptoSerializationManager implements dorkbox.network.util.CryptoSerializa
public
KryoExtra create() {
synchronized (CryptoSerializationManager.this) {
KryoExtra kryo = new KryoExtra();
KryoExtra kryo = new KryoExtra(CryptoSerializationManager.this);
// we HAVE to pre-allocate the KRYOs
boolean useAsm = !useUnsafeMemory;
kryo.setAsmEnabled(useAsm);
kryo.getFieldSerializerConfig().setUseAsm(useAsm);
kryo.setRegistrationRequired(registrationRequired);
kryo.setReferences(references);
@ -268,7 +272,8 @@ class CryptoSerializationManager implements dorkbox.network.util.CryptoSerializa
int id = kryo.register(remoteImplClass.implClass, remoteObjectSerializer).getId();
// sets up the RMI, so when we receive the iface class from the client, we know what impl to use
rmiInterfaceToImpl.put(id, remoteImplClass.implClass);
rmiIdToImpl.put(id, remoteImplClass.implClass);
rmiIdToIface.put(id, remoteImplClass.ifaceClass);
}
}
@ -355,38 +360,12 @@ class CryptoSerializationManager implements dorkbox.network.util.CryptoSerializa
}
/**
* @return the previously registered class
* Enable remote method invocation (RMI) for this connection. There 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 dorkbox.network.rmi.RemoteObject#setAsync(boolean) ignored}, an extra byte is written. If the
* type of a parameter is not final (primitives are final) then an extra byte is written for that parameter.
*/
private
Class<?> getLastAddedClass() {
// get the previously registered class
Object obj = classesToRegister.get(classesToRegister.size() - 1);
if (obj instanceof Class) {
return (Class) obj;
}
else if (obj instanceof ClassSerializer) {
ClassSerializer classSerializer = (ClassSerializer) obj;
return classSerializer.clazz;
}
else if (obj instanceof ClassSerializer2) {
ClassSerializer2 classSerializer = (ClassSerializer2) obj;
return classSerializer.clazz;
}
return null;
}
private static
boolean testInterface(Class<?> from, Class<?> iface) {
for (Class<?> intface : from.getInterfaces()) {
if (iface.equals(intface) || testInterface(intface, iface)) {
return true;
}
}
return false;
}
@Override
public synchronized
RmiSerializationManager registerRmiInterface(Class<?> ifaceClass) {
@ -421,13 +400,11 @@ class CryptoSerializationManager implements dorkbox.network.util.CryptoSerializa
}
usesRmi = true;
classesToRegister.add(new RemoteImplClass(ifaceClass, implClass));
// THIS IS DONE ON THE SERVER ONLY
// we have to save the fact that we might have overridden methods.
// will throw IllegalArgumentException if the iface/impl have previously been overridden
CachedMethod.registerOverridden(ifaceClass, implClass);
// this MUST BE UNIQUE otherwise unexpected things can happen.
classDefinitions.set(ifaceClass, implClass);
classesToRegister.add(new RemoteImplClass(implClass));
return this;
}
@ -472,6 +449,40 @@ class CryptoSerializationManager implements dorkbox.network.util.CryptoSerializa
return initialized;
}
/**
* Gets the RMI interface based on the specified ID (which is the ID for the registered implementation)
*
* @param objectId ID of the registered interface, which will map to the corresponding implementation.
* @return the implementation for the interface, or null
*/
@Override
public
Class<?> getRmiIface(int objectId) {
return rmiIdToIface.get(objectId);
}
/**
* Gets the RMI implementation based on the specified interface
*
* @return the corresponding implementation
*/
@Override
public
Class<?> getRmiImpl(Class<?> iface) {
return classDefinitions.get(iface);
}
/**
* Gets the RMI interface based on the specified implementation
*
* @return the corresponding interface
*/
@Override
public
Class<?> getRmiIface(Class<?> implementation) {
return classDefinitions.getReverse(implementation);
}
/**
* Gets the RMI implementation based on the specified ID (which is the ID for the registered interface)
*
@ -479,8 +490,9 @@ class CryptoSerializationManager implements dorkbox.network.util.CryptoSerializa
* @return the implementation for the interface, or null
*/
@Override
public Class<?> getRmiImpl(int objectId) {
return rmiInterfaceToImpl.get(objectId);
public
Class<?> getRmiImpl(int objectId) {
return rmiIdToImpl.get(objectId);
}
/**

View File

@ -15,7 +15,14 @@
*/
package dorkbox.network.connection;
import java.io.IOException;
import org.bouncycastle.crypto.engines.AESFastEngine;
import org.bouncycastle.crypto.modes.GCMBlockCipher;
import org.bouncycastle.crypto.params.ParametersWithIV;
import com.esotericsoftware.kryo.Kryo;
import dorkbox.network.pipeline.ByteBufInput;
import dorkbox.network.pipeline.ByteBufOutput;
import dorkbox.util.bytes.BigEndian;
@ -26,11 +33,6 @@ import io.netty.buffer.Unpooled;
import net.jpountz.lz4.LZ4Compressor;
import net.jpountz.lz4.LZ4Factory;
import net.jpountz.lz4.LZ4FastDecompressor;
import org.bouncycastle.crypto.engines.AESFastEngine;
import org.bouncycastle.crypto.modes.GCMBlockCipher;
import org.bouncycastle.crypto.params.ParametersWithIV;
import java.io.IOException;
/**
* Nothing in this class is thread safe
@ -83,8 +85,11 @@ class KryoExtra<C extends ICryptoConnection> extends Kryo {
private byte[] decompressOutput;
private ByteBuf decompressBuf;
private dorkbox.network.util.CryptoSerializationManager serializationManager;
public
KryoExtra() {
KryoExtra(final dorkbox.network.util.CryptoSerializationManager serializationManager) {
this.serializationManager = serializationManager;
}
public synchronized
@ -580,4 +585,9 @@ class KryoExtra<C extends ICryptoConnection> extends Kryo {
super.finalize();
}
public
dorkbox.network.util.CryptoSerializationManager getSerializationManager() {
return serializationManager;
}
}

View File

@ -15,20 +15,18 @@
*/
package dorkbox.network.pipeline;
import java.lang.reflect.Field;
import java.util.List;
import dorkbox.network.connection.ConnectionImpl;
import dorkbox.network.rmi.OverriddenMethods;
import dorkbox.network.rmi.RemoteObject;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToMessageDecoder;
import java.lang.reflect.Field;
import java.util.List;
public
class LocalRmiDecoder extends MessageToMessageDecoder<Object> {
private static final RmiFieldCache fieldCache = RmiFieldCache.INSTANCE();
private static final OverriddenMethods overriddenMethods = OverriddenMethods.INSTANCE();
public
LocalRmiDecoder() {
@ -70,7 +68,10 @@ class LocalRmiDecoder extends MessageToMessageDecoder<Object> {
throw new RuntimeException("Unable to get RMI interface object for RMI implementation");
}
final Class<?> iface = overriddenMethods.getReverse(localRmiObject.getClass());
final Class<?> iface = connection.getEndPoint()
.getSerialization()
.getRmiIface(localRmiObject.getClass());
if (iface == null) {
throw new RuntimeException("Unable to get interface for RMI implementation");
}

View File

@ -15,20 +15,20 @@
*/
package dorkbox.network.pipeline;
import dorkbox.network.connection.ConnectionImpl;
import dorkbox.network.connection.EndPoint;
import dorkbox.network.rmi.RMI;
import dorkbox.util.FastThreadLocal;
import io.netty.channel.ChannelHandler.Sharable;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToMessageEncoder;
import java.lang.reflect.Field;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.concurrent.ConcurrentHashMap;
import dorkbox.network.connection.ConnectionImpl;
import dorkbox.network.connection.EndPoint;
import dorkbox.network.rmi.Rmi;
import dorkbox.util.FastThreadLocal;
import io.netty.channel.ChannelHandler.Sharable;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToMessageEncoder;
@Sharable
public
class LocalRmiEncoder extends MessageToMessageEncoder<Object> {
@ -66,7 +66,7 @@ class LocalRmiEncoder extends MessageToMessageEncoder<Object> {
Boolean needsTransform = transformObjectCache.get(implClass);
if (needsTransform == null) {
boolean hasRmi = implClass.getAnnotation(RMI.class) != null;
boolean hasRmi = implClass.getAnnotation(Rmi.class) != null;
if (hasRmi) {
// replace object

View File

@ -15,13 +15,14 @@
*/
package dorkbox.network.pipeline;
import com.esotericsoftware.kryo.util.IdentityMap;
import dorkbox.network.rmi.RMI;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
import com.esotericsoftware.kryo.util.IdentityMap;
import dorkbox.network.rmi.Rmi;
/**
* Uses the "single writer principle" for fast access, but disregards 'single writer', because duplicates are OK
*/
@ -57,7 +58,7 @@ class RmiFieldCache {
final ArrayList<Field> fields = new ArrayList<Field>();
for (Field field : clazz.getDeclaredFields()) {
if (field.getAnnotation(RMI.class) != null) {
if (field.getAnnotation(Rmi.class) != null) {
fields.add(field);
}
}

View File

@ -55,6 +55,7 @@ import com.esotericsoftware.reflectasm.MethodAccess;
import dorkbox.network.connection.Connection;
import dorkbox.network.connection.EndPoint;
import dorkbox.network.connection.KryoExtra;
import dorkbox.network.util.CryptoSerializationManager;
import dorkbox.network.util.RmiSerializationManager;
import dorkbox.util.ClassHelper;
@ -96,27 +97,34 @@ class CachedMethod {
// the purpose of the method cache, is to accelerate looking up methods for specific class
private static final Map<Class<?>, CachedMethod[]> methodCache = new ConcurrentHashMap<Class<?>, CachedMethod[]>(EndPoint.DEFAULT_THREAD_POOL_SIZE);
private static final OverriddenMethods overriddenMethods = OverriddenMethods.INSTANCE();
// type will be likely be the interface
public static
CachedMethod[] getMethods(final Kryo kryo, final Class<?> type) {
/**
* Called when we read a RMI method invocation on the "server" side (by kryo)
*
* @param type is the implementation type
*/
static
CachedMethod[] getMethods(final Kryo kryo, final Class<?> type, final int classId) {
CachedMethod[] cachedMethods = methodCache.get(type);
if (cachedMethods != null) {
return cachedMethods;
}
cachedMethods = getCachedMethods(kryo, type);
cachedMethods = getCachedMethods(kryo, type, classId);
methodCache.put(type, cachedMethods);
return cachedMethods;
}
// type will be likely be the interface
public static
CachedMethod[] getMethods(final RmiSerializationManager serializationManager, final Class<?> type) {
/**
* Called when we write an RMI method invocation on the "client" side (by RmiProxyHandler)
*
* @param type this is the interface.
*/
static
CachedMethod[] getMethods(final RmiSerializationManager serializationManager, final Class<?> type, final int classId) {
CachedMethod[] cachedMethods = methodCache.get(type);
if (cachedMethods != null) {
return cachedMethods;
@ -124,7 +132,7 @@ class CachedMethod {
final KryoExtra kryo = serializationManager.takeKryo();
try {
cachedMethods = getCachedMethods(kryo, type);
cachedMethods = getCachedMethods(kryo, type, classId);
methodCache.put(type, cachedMethods);
} finally {
serializationManager.returnKryo(kryo);
@ -133,10 +141,25 @@ class CachedMethod {
return cachedMethods;
}
// race-conditions are OK, because we just recreate the same thing.
private static
CachedMethod[] getCachedMethods(final Kryo kryo, final Class<?> type) {
// race-conditions are OK, because we just recreate the same thing.
final ArrayList<Method> methods = getMethods(type);
CachedMethod[] getCachedMethods(final Kryo kryo, final Class<?> type, final int classId) {
// sometimes, the method index is based upon an interface and NOT the implementation. We have to clear that up here.
CryptoSerializationManager serialization = ((KryoExtra) kryo).getSerializationManager();
// when there is an interface available, we want to use that instead of the implementation. This is because the incoming
// implementation is ACTUALLY mapped (on the "client" side) to the interface. If we don't use the interface, we will have the
// wrong order of methods, so invoking a method by it's index will fail.
Class<?> interfaceClass = serialization.getRmiIface(classId);
final ArrayList<Method> methods;
if (interfaceClass == null) {
methods = getMethods(type);
} else {
methods = getMethods(interfaceClass);
}
final int size = methods.size();
final CachedMethod[] cachedMethods = new CachedMethod[size];
@ -155,8 +178,17 @@ class CachedMethod {
// To facilitate this functionality, for methods with the same name, the "overriding" method is the one that inherits the Connection
// interface as the first parameter, and .registerRemote(ifaceClass, implClass) must be called.
final IdentityMap<Method, Method> overriddenMethods = getOverriddenMethods(type, methods);
final boolean asmEnabled = kryo.getAsmEnabled();
// will only be valid if implementation type is NOT NULL (otherwise will be null)
final IdentityMap<Method, Method> overriddenMethods;
if (interfaceClass == null) {
overriddenMethods = null;
} else {
// type here must be the implementation
overriddenMethods = getOverriddenMethods(type, methods);
}
final boolean asmEnabled = kryo.getFieldSerializerConfig().isUseAsm();
MethodAccess methodAccess = null;
// reflectASM can't get any method from the 'Object' object, and it MUST be public
@ -173,6 +205,7 @@ class CachedMethod {
for (int i = 0; i < size; i++) {
final Method origMethod = methods.get(i);
Method method = origMethod; // copy because one or more can be overridden
Class<?> declaringClass = method.getDeclaringClass();
MethodAccess localMethodAccess = methodAccess; // copy because one or more can be overridden
Class<?>[] parameterTypes = method.getParameterTypes();
Class<?>[] asmParameterTypes = parameterTypes;
@ -184,7 +217,7 @@ class CachedMethod {
// the correct object for this overridden method to be called on.
method = overriddenMethod;
Class<?> overrideType = method.getDeclaringClass();
Class<?> overrideType = declaringClass;
if (asmEnabled && !Util.isAndroid && Modifier.isPublic(overrideType.getModifiers())) {
localMethodAccess = MethodAccess.get(overrideType);
asmParameterTypes = method.getParameterTypes();
@ -203,7 +236,7 @@ class CachedMethod {
cachedMethod = asmCachedMethod;
} catch (Exception e) {
if (logger.isTraceEnabled()) {
logger.trace("Unable to use ReflectAsm for {}.{}", method.getDeclaringClass(), method.getName(), e);
logger.trace("Unable to use ReflectAsm for {}.{}", declaringClass, method.getName(), e);
}
}
}
@ -213,7 +246,18 @@ class CachedMethod {
}
cachedMethod.method = method;
cachedMethod.origMethod = origMethod;
cachedMethod.methodClassID = kryo.getRegistration(method.getDeclaringClass()).getId();
// on the "server", we only register the implementation. NOT THE INTERFACE, so for RMI classes, we have to get the impl
Class<?> impl = serialization.getRmiImpl(declaringClass);
if (impl != null) {
cachedMethod.methodClassID = kryo.getRegistration(impl)
.getId();
}
else {
cachedMethod.methodClassID = kryo.getRegistration(declaringClass)
.getId();
}
cachedMethod.methodIndex = i;
// Store the serializer for each final parameter.
@ -231,54 +275,48 @@ class CachedMethod {
return cachedMethods;
}
// does not null check
private static
IdentityMap<Method, Method> getOverriddenMethods(final Class<?> type, final ArrayList<Method> origMethods) {
final Class<?> implType = overriddenMethods.get(type);
final ArrayList<Method> implMethods = getMethods(type);
final IdentityMap<Method, Method> overrideMap = new IdentityMap<Method, Method>(implMethods.size());
if (implType != null) {
final ArrayList<Method> implMethods = getMethods(implType);
final IdentityMap<Method, Method> overrideMap = new IdentityMap<Method, Method>(implMethods.size());
for (Method origMethod : origMethods) {
String name = origMethod.getName();
Class<?>[] origTypes = origMethod.getParameterTypes();
int origLength = origTypes.length + 1;
for (Method origMethod : origMethods) {
String name = origMethod.getName();
Class<?>[] origTypes = origMethod.getParameterTypes();
int origLength = origTypes.length + 1;
METHOD_CHECK:
for (Method implMethod : implMethods) {
String checkName = implMethod.getName();
Class<?>[] checkTypes = implMethod.getParameterTypes();
int checkLength = checkTypes.length;
METHOD_CHECK:
for (Method implMethod : implMethods) {
String checkName = implMethod.getName();
Class<?>[] checkTypes = implMethod.getParameterTypes();
int checkLength = checkTypes.length;
if (origLength != checkLength || !(name.equals(checkName))) {
continue;
}
if (origLength != checkLength || !(name.equals(checkName))) {
continue;
// checkLength > 0
Class<?> shouldBeConnectionType = checkTypes[0];
if (ClassHelper.hasInterface(dorkbox.network.connection.Connection.class, shouldBeConnectionType)) {
// now we check to see if our "check" method is equal to our "cached" method + Connection
if (checkLength == 1) {
overrideMap.put(origMethod, implMethod);
break;
}
// checkLength > 0
Class<?> shouldBeConnectionType = checkTypes[0];
if (ClassHelper.hasInterface(dorkbox.network.connection.Connection.class, shouldBeConnectionType)) {
// now we check to see if our "check" method is equal to our "cached" method + Connection
if (checkLength == 1) {
overrideMap.put(origMethod, implMethod);
break;
}
else {
for (int k = 1; k < checkLength; k++) {
if (origTypes[k - 1] == checkTypes[k]) {
overrideMap.put(origMethod, implMethod);
break METHOD_CHECK;
}
else {
for (int k = 1; k < checkLength; k++) {
if (origTypes[k - 1] == checkTypes[k]) {
overrideMap.put(origMethod, implMethod);
break METHOD_CHECK;
}
}
}
}
}
}
return overrideMap;
}
else {
return null;
}
return overrideMap;
}
private static
@ -314,18 +352,6 @@ class CachedMethod {
return methods;
}
/**
* Called by the SerializationManager, so that RMI classes that are overridden for serialization purposes, can check to see if certain
* methods need to be overridden.
* <p>
* NOTE: It is CRITICAL that this is unique per JVM, otherwise unexpected things can happen.
*
* @throws IllegalArgumentException if the iface/impl have previously been overridden
*/
public static
void registerOverridden(final Class<?> ifaceClass, final Class<?> implClass) {
overriddenMethods.set(ifaceClass, implClass);
}
public Method method;
public int methodClassID;

View File

@ -23,28 +23,23 @@ import com.esotericsoftware.kryo.util.IdentityMap;
* Uses the "single writer principle" for fast access
*/
public
class OverriddenMethods {
class ClassDefinitions {
// not concurrent because they are setup during system initialization
private volatile IdentityMap<Class<?>, Class<?>> overriddenMethods = new IdentityMap<Class<?>, Class<?>>();
private volatile IdentityMap<Class<?>, Class<?>> overriddenReverseMethods = new IdentityMap<Class<?>, Class<?>>();
private static final AtomicReferenceFieldUpdater<OverriddenMethods, IdentityMap> overriddenMethodsREF =
AtomicReferenceFieldUpdater.newUpdater(OverriddenMethods.class,
private static final AtomicReferenceFieldUpdater<ClassDefinitions, IdentityMap> overriddenMethodsREF =
AtomicReferenceFieldUpdater.newUpdater(ClassDefinitions.class,
IdentityMap.class,
"overriddenMethods");
private static final AtomicReferenceFieldUpdater<OverriddenMethods, IdentityMap> overriddenReverseMethodsREF =
AtomicReferenceFieldUpdater.newUpdater(OverriddenMethods.class,
private static final AtomicReferenceFieldUpdater<ClassDefinitions, IdentityMap> overriddenReverseMethodsREF =
AtomicReferenceFieldUpdater.newUpdater(ClassDefinitions.class,
IdentityMap.class,
"overriddenReverseMethods");
private static final OverriddenMethods INSTANCE = new OverriddenMethods();
public static synchronized OverriddenMethods INSTANCE() {
return INSTANCE;
}
private
OverriddenMethods() {
public
ClassDefinitions() {
}
/**
@ -76,7 +71,7 @@ class OverriddenMethods {
Class<?> a = this.overriddenMethods.put(ifaceClass, implClass);
Class<?> b = this.overriddenReverseMethods.put(implClass, ifaceClass);
// this MUST BE UNIQUE per JVM, otherwise unexpected things can happen.
// this MUST BE UNIQUE otherwise unexpected things can happen.
if (a != null || b != null) {
throw new IllegalArgumentException("Unable to override interface (" + ifaceClass + ") and implementation (" + implClass + ") " +
"because they have already been overridden by something else. It is critical that they are" +

View File

@ -54,16 +54,22 @@ class InvokeMethodSerializer extends Serializer<InvokeMethod> {
@SuppressWarnings("rawtypes")
public
void write(final Kryo kryo, final Output output, final InvokeMethod object) {
// System.err.println(":: objectID " + object.objectID);
// System.err.println(":: methodClassID " + object.cachedMethod.methodClassID);
// System.err.println(":: methodIndex " + object.cachedMethod.methodIndex);
output.writeInt(object.objectID, true);
output.writeInt(object.cachedMethod.methodClassID, true);
output.writeByte(object.cachedMethod.methodIndex);
Serializer[] serializers = object.cachedMethod.serializers;
int length = serializers.length;
Object[] args = object.args;
int i = 0, n = serializers.length;
for (; i < n; i++) {
for (int i = 0; i < length; i++) {
Serializer serializer = serializers[i];
if (serializer != null) {
kryo.writeObjectOrNull(output, args[i], serializer);
}
@ -84,20 +90,22 @@ class InvokeMethodSerializer extends Serializer<InvokeMethod> {
int methodClassID = input.readInt(true);
Class<?> methodClass = kryo.getRegistration(methodClassID)
.getType();
.getType();
byte methodIndex = input.readByte();
CachedMethod cachedMethod;
try {
invokeMethod.cachedMethod = CachedMethod.getMethods(kryo, methodClass)[methodIndex];
cachedMethod = CachedMethod.getMethods(kryo, methodClass, methodClassID)[methodIndex];
invokeMethod.cachedMethod = cachedMethod;
} catch (IndexOutOfBoundsException ex) {
throw new KryoException("Invalid method index " + methodIndex + " for class: " + methodClass.getName());
}
CachedMethod cachedMethod = invokeMethod.cachedMethod;
Serializer<?>[] serializers = cachedMethod.serializers;
Class<?>[] parameterTypes = 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) {

View File

@ -42,7 +42,7 @@ import com.esotericsoftware.kryo.io.Output;
import dorkbox.network.connection.KryoExtra;
/**
* Serializes an object registered with the RmiImplHandler so the receiving side gets a {@link RemoteObject} proxy rather than the bytes for the
* Serializes an object registered with the RmiBridge so the receiving side gets a {@link RemoteObject} proxy rather than the bytes for the
* serialized object.
*
* @author Nathan Sweet <misc@n4te.com>

View File

@ -15,7 +15,11 @@
*/
package dorkbox.network.rmi;
import java.lang.annotation.*;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* This specifies to the serializer, that this class contains an RMI object, and that a specific field is an RMI object. Both are
@ -32,4 +36,4 @@ import java.lang.annotation.*;
@Inherited
@Target(value = {ElementType.TYPE, ElementType.FIELD})
public
@interface RMI {}
@interface Rmi {}

View File

@ -87,7 +87,7 @@ import dorkbox.util.collections.ObjectIntMap;
* @author Nathan Sweet <misc@n4te.com>, Nathan Robinson
*/
public final
class RmiImplHandler {
class RmiBridge {
public static final int INVALID_RMI = 0;
static final int returnValueMask = 1 << 7;
static final int returnExceptionMask = 1 << 6;
@ -105,7 +105,7 @@ class RmiImplHandler {
return (objectId & 1) != 0;
}
// the name of who created this RmiImplHandler
// the name of who created this RmiBridge
private final org.slf4j.Logger logger;
// we start at 1, because 0 (INVALID_RMI) means we access connection only objects
@ -130,7 +130,7 @@ class RmiImplHandler {
final Object target = connection.getImplementationObject(objectID);
if (target == null) {
Logger logger2 = RmiImplHandler.this.logger;
Logger logger2 = RmiBridge.this.logger;
if (logger2.isWarnEnabled()) {
logger2.warn("Ignoring remote invocation request for unknown object ID: {}", objectID);
}
@ -138,7 +138,7 @@ class RmiImplHandler {
return;
}
Executor executor2 = RmiImplHandler.this.executor;
Executor executor2 = RmiBridge.this.executor;
if (executor2 == null) {
try {
invoke(connection, target, invokeMethod);
@ -163,18 +163,18 @@ class RmiImplHandler {
};
/**
* Creates an RmiImplHandler with no connections. Connections must be {@link RmiImplHandler#register(int, Object)} added to allow the remote end
* Creates an RmiBridge with no connections. Connections must be {@link RmiBridge#register(int, Object)} added to allow the remote end
* of the connections to access objects in this ObjectSpace.
*
* @param executor
* Sets the executor used to invoke methods when an invocation is received from a remote endpoint. By default, no
* executor is set and invocations occur on the network thread, which should not be blocked for long, May be null.
* @param isGlobal
* specify if this RmiImplHandler is a "global" bridge, meaning connections will prefer objects from this bridge instead of
* specify if this RmiBridge is a "global" bridge, meaning connections will prefer objects from this bridge instead of
* the connection-local bridge.
*/
public
RmiImplHandler(final org.slf4j.Logger logger, final Executor executor, final boolean isGlobal) {
RmiBridge(final org.slf4j.Logger logger, final Executor executor, final boolean isGlobal) {
this.logger = logger;
this.executor = executor;
@ -197,7 +197,7 @@ class RmiImplHandler {
/**
* 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 RmiImplHandler and unless an executor has been set.
* method is invoked on the update thread of the {@link EndPoint} for this RmiBridge and unless an executor has been set.
*
* @param connection
* The remote side of this connection requested the invocation.
@ -298,13 +298,13 @@ class RmiImplHandler {
int value = rmiObjectIdCounter.getAndAdd(2);
if (value > MAX_RMI_VALUE) {
rmiObjectIdCounter.set(MAX_RMI_VALUE); // prevent wrapping by spammy callers
logger.error("RMI next value has exceeded maximum limits in RmiImplHandler!");
logger.error("RMI next value has exceeded maximum limits in RmiBridge!");
}
return value;
}
/**
* Registers an object to allow the remote end of the RmiImplHandler connections to access it using the specified ID.
* 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.
@ -319,7 +319,7 @@ class RmiImplHandler {
throw new IllegalArgumentException("object cannot be null.");
}
WriteLock writeLock = RmiImplHandler.this.objectLock.writeLock();
WriteLock writeLock = RmiBridge.this.objectLock.writeLock();
writeLock.lock();
this.idToObject.put(objectID, object);
@ -334,12 +334,12 @@ class RmiImplHandler {
}
/**
* Removes an object. The remote end of the RmiImplHandler connection 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.
*/
@SuppressWarnings("AutoBoxing")
public
void remove(int objectID) {
WriteLock writeLock = RmiImplHandler.this.objectLock.writeLock();
WriteLock writeLock = RmiBridge.this.objectLock.writeLock();
writeLock.lock();
Object object = this.idToObject.remove(objectID);
@ -356,12 +356,12 @@ class RmiImplHandler {
}
/**
* Removes an object. The remote end of the RmiImplHandler connection 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.
*/
@SuppressWarnings("AutoBoxing")
public
void remove(Object object) {
WriteLock writeLock = RmiImplHandler.this.objectLock.writeLock();
WriteLock writeLock = RmiBridge.this.objectLock.writeLock();
writeLock.lock();
if (!this.idToObject.containsValue(object, true)) {
@ -417,7 +417,7 @@ class RmiImplHandler {
* <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
* RmiImplHandler#register(int, Object)} added the connection to the ObjectSpace, the remote method invocations will be ignored.
* 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}.
@ -444,7 +444,7 @@ class RmiImplHandler {
temp[0] = RemoteObject.class;
temp[1] = iface;
return (RemoteObject) Proxy.newProxyInstance(RmiImplHandler.class.getClassLoader(),
return (RemoteObject) Proxy.newProxyInstance(RmiBridge.class.getClassLoader(),
temp,
new RmiProxyHandler(connection, objectID));
}

View File

@ -207,7 +207,7 @@ class RmiProxyHandler implements InvocationHandler {
}
EndPoint endPoint = this.connection.getEndPoint();
final RmiSerializationManager serializationManager = (RmiSerializationManager) endPoint.getSerialization();
final RmiSerializationManager serializationManager = endPoint.getSerialization();
InvokeMethod invokeMethod = new InvokeMethod();
invokeMethod.objectID = this.objectID;
@ -215,7 +215,7 @@ class RmiProxyHandler implements InvocationHandler {
// which method do we access?
CachedMethod[] cachedMethods = CachedMethod.getMethods(serializationManager, method.getDeclaringClass());
CachedMethod[] cachedMethods = CachedMethod.getMethods(serializationManager, method.getDeclaringClass(), invokeMethod.objectID);
for (int i = 0, n = cachedMethods.length; i < n; i++) {
CachedMethod cachedMethod = cachedMethods[i];
@ -255,10 +255,9 @@ class RmiProxyHandler implements InvocationHandler {
byte responseID = (byte) 0;
// An invocation doesn't need a response is if it's
// VOID return type
// ASYNC and no return values or exceptions are wanted back
Class<?> returnType = method.getReturnType();
boolean ignoreResponse = returnType == void.class || this.isAsync && !(this.transmitReturnValue || this.transmitExceptions);
boolean ignoreResponse = this.isAsync && !(this.transmitReturnValue || this.transmitExceptions);
if (ignoreResponse) {
invokeMethod.responseData = (byte) 0; // 0 means do not respond.
}
@ -266,7 +265,7 @@ class RmiProxyHandler implements InvocationHandler {
synchronized (this) {
// Increment the response counter and put it into the low bits of the responseID.
responseID = this.nextResponseId++;
if (this.nextResponseId > RmiImplHandler.responseIdMask) {
if (this.nextResponseId > RmiBridge.responseIdMask) {
this.nextResponseId = (byte) 1;
}
this.pendingResponses[responseID] = true;
@ -274,15 +273,17 @@ class RmiProxyHandler implements InvocationHandler {
// Pack other data into the high bits.
byte responseData = responseID;
if (this.transmitReturnValue) {
responseData |= (byte) RmiImplHandler.returnValueMask;
responseData |= (byte) RmiBridge.returnValueMask;
}
if (this.transmitExceptions) {
responseData |= (byte) RmiImplHandler.returnExceptionMask;
responseData |= (byte) RmiBridge.returnExceptionMask;
}
invokeMethod.responseData = responseData;
}
// Sends our invokeMethod to the remote connection, which the RmiImplHandler listens for
this.lastResponseID = (byte) (invokeMethod.responseData & RmiBridge.responseIdMask);
// Sends our invokeMethod to the remote connection, which the RmiBridge listens for
if (this.udp) {
this.connection.send()
.UDP(invokeMethod)
@ -310,10 +311,7 @@ class RmiProxyHandler implements InvocationHandler {
"#" + method.getName() + "(" + argString + ")");
}
this.lastResponseID = (byte) (invokeMethod.responseData & RmiImplHandler.responseIdMask);
// 0 means respond immediately because it's
// VOID return type
// ASYNC and no return values or exceptions are wanted back
if (this.isAsync) {
if (returnType.isPrimitive()) {
@ -341,15 +339,9 @@ class RmiProxyHandler implements InvocationHandler {
if (returnType == double.class) {
return 0.0d;
}
if (returnType == void.class) {
return 0.0d;
}
}
return null;
}
else if (returnType == void.class) {
return null;
}
try {
Object result = waitForResponse(this.lastResponseID);
@ -375,11 +367,21 @@ class RmiProxyHandler implements InvocationHandler {
*/
private
Object waitForResponse(final byte responseID) throws IOException {
long endTime = System.currentTimeMillis() + this.timeoutMillis;
long remaining = this.timeoutMillis;
// if timeout == 0, we wait "forever"
long remaining;
long endTime;
if (remaining == 0) {
// just wait however log it takes.
if (this.timeoutMillis != 0) {
remaining = this.timeoutMillis;
endTime = System.currentTimeMillis() + remaining;
} else {
// not forever, but close enough
remaining = Long.MAX_VALUE;
endTime = Long.MAX_VALUE;
}
// wait for the specified time
while (remaining > 0) {
InvokeMethodResult invokeMethodResult;
synchronized (this) {
invokeMethodResult = this.responseTable[responseID];
@ -392,7 +394,7 @@ class RmiProxyHandler implements InvocationHandler {
else {
this.lock.lock();
try {
this.responseCondition.await();
this.responseCondition.await(remaining, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
Thread.currentThread()
.interrupt();
@ -402,41 +404,7 @@ class RmiProxyHandler implements InvocationHandler {
}
}
synchronized (this) {
invokeMethodResult = this.responseTable[responseID];
}
if (invokeMethodResult != null) {
this.lastResponseID = null;
return invokeMethodResult.result;
}
}
else {
// wait for the specified time
while (remaining > 0) {
InvokeMethodResult invokeMethodResult;
synchronized (this) {
invokeMethodResult = this.responseTable[responseID];
}
if (invokeMethodResult != null) {
this.lastResponseID = null;
return invokeMethodResult.result;
}
else {
this.lock.lock();
try {
this.responseCondition.await(remaining, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
Thread.currentThread()
.interrupt();
throw new IOException("Response timed out.", e);
} finally {
this.lock.unlock();
}
}
remaining = endTime - System.currentTimeMillis();
}
remaining = endTime - System.currentTimeMillis();
}
// only get here if we timeout

View File

@ -68,6 +68,7 @@ interface RmiSerializationManager extends SerializationManager {
/**
* Necessary to register classes for RMI, only called once when the RMI bridge is created.
*
* @return true if there are classes that have been registered for RMI
*/
boolean initRmiSerialization();
@ -82,6 +83,15 @@ interface RmiSerializationManager extends SerializationManager {
*/
void returnKryo(KryoExtra kryo);
/**
* Gets the RMI interface based on the specified ID (which is the ID for the registered implementation)
*
* @param objectId ID of the registered interface, which will map to the corresponding implementation.
*
* @return the implementation for the interface, or null
*/
Class<?> getRmiIface(int objectId);
/**
* Gets the RMI implementation based on the specified ID (which is the ID for the registered interface)
*
@ -91,6 +101,20 @@ interface RmiSerializationManager extends SerializationManager {
*/
Class<?> getRmiImpl(int objectId);
/**
* Gets the RMI implementation based on the specified interface
*
* @return the corresponding implementation
*/
Class<?> getRmiImpl(Class<?> iface);
/**
* Gets the RMI interface based on the specified implementation
*
* @return the corresponding interface
*/
Class<?> getRmiIface(Class<?> implementation);
/**
* Enable remote method invocation (RMI) for this connection. There is additional overhead to using RMI.
* <p/>
@ -100,8 +124,6 @@ interface RmiSerializationManager extends SerializationManager {
*/
RmiSerializationManager registerRmiInterface(Class<?> ifaceClass);
/**
* Objects that we want to use RMI with, must be accessed via an interface. This method configures the serialization of an
* implementation to be serialized via the defined interface, as a RemoteObject (ie: proxy object). If the implementation class is

View File

@ -4,8 +4,8 @@ package dorkbox.network.rmi;
*
*/
public
class MessageWithTestObject implements RmiMessages {
class MessageWithTestCow implements RmiMessages {
public int number;
public String text;
public TestObject testObject;
public TestCow testCow;
}

View File

@ -40,7 +40,6 @@ import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.io.IOException;
import java.io.Serializable;
import org.junit.Test;
@ -61,11 +60,11 @@ class RmiGlobalTest extends BaseTest {
private int CLIENT_GLOBAL_OBJECT_ID = 0;
private int SERVER_GLOBAL_OBJECT_ID = 0;
private final TestObject globalRemoteServerObject = new TestObjectImpl();
private final TestObject globalRemoteClientObject = new TestObjectImpl();
private final TestCow globalRemoteServerObject = new TestCowImpl();
private final TestCow globalRemoteClientObject = new TestCowImpl();
private static
void runTest(final Connection connection, final TestObject rObject, final TestObject test, final int remoteObjectID) {
void runTest(final Connection connection, final TestCow rObject, final TestCow test, final int remoteObjectID) {
System.err.println("Starting test for: " + remoteObjectID);
assertEquals(rObject.hashCode(), test.hashCode());
@ -170,10 +169,10 @@ class RmiGlobalTest extends BaseTest {
// Test sending a reference to a remote object.
MessageWithTestObject m = new MessageWithTestObject();
MessageWithTestCow m = new MessageWithTestCow();
m.number = 678;
m.text = "sometext";
m.testObject = test;
m.testCow = test;
connection.send()
.TCP(m)
.flush();
@ -184,10 +183,7 @@ class RmiGlobalTest extends BaseTest {
public static
void register(dorkbox.network.util.CryptoSerializationManager manager) {
manager.register(Object.class); // Needed for Object#toString, hashCode, etc.
// manager.rmi().register(TestObject.class).override(TestObject.class, TestObjectImpl.class);
manager.register(MessageWithTestObject.class);
manager.register(MessageWithTestCow.class);
manager.register(UnsupportedOperationException.class);
}
@ -202,6 +198,12 @@ class RmiGlobalTest extends BaseTest {
configuration.serialization = CryptoSerializationManager.DEFAULT();
register(configuration.serialization);
// for Server -> Client RMI (ID: CLIENT_GLOBAL_OBJECT_ID)
configuration.serialization.registerRmiInterface(TestCow.class);
// for Client -> Server RMI (ID: SERVER_GLOBAL_OBJECT_ID)
configuration.serialization.registerRmiImplementation(TestCow.class, TestCowImpl.class);
final Server server = new Server(configuration);
server.setIdleTimeout(0);
@ -218,11 +220,20 @@ class RmiGlobalTest extends BaseTest {
public
void connected(final Connection connection) {
try {
connection.getRemoteObject(CLIENT_GLOBAL_OBJECT_ID, new RemoteObjectCallback<TestObject>() {
connection.getRemoteObject(CLIENT_GLOBAL_OBJECT_ID, new RemoteObjectCallback<TestCow>() {
@Override
public
void created(final TestObject remoteObject) {
runTest(connection, globalRemoteClientObject, remoteObject, CLIENT_GLOBAL_OBJECT_ID);
void created(final TestCow remoteObject) {
// MUST run on a separate thread because remote object method invocations are blocking
new Thread() {
@Override
public
void run() {
System.err.println("Running test for: Server -> Client");
runTest(connection, globalRemoteClientObject, remoteObject, CLIENT_GLOBAL_OBJECT_ID);
System.err.println("Done with test for: Server -> Client");
}
}.start();
}
});
} catch (IOException e) {
@ -233,14 +244,17 @@ class RmiGlobalTest extends BaseTest {
});
server.listeners()
.add(new Listener.OnMessageReceived<Connection, MessageWithTestObject>() {
.add(new Listener.OnMessageReceived<Connection, MessageWithTestCow>() {
@Override
public
void received(Connection connection, MessageWithTestObject m) {
TestObject object = m.testObject;
void received(Connection connection, MessageWithTestCow m) {
System.err.println("Received finish signal for test for: Client -> Server");
TestCow object = m.testCow;
final int id = object.id();
assertEquals(1, id);
System.err.println("Client/Server Finished!");
assertEquals(SERVER_GLOBAL_OBJECT_ID, id);
System.err.println("Finished test for: Client -> Server");
stopEndPoints(2000);
}
@ -248,32 +262,59 @@ class RmiGlobalTest extends BaseTest {
// ----
configuration = new Configuration();
configuration.tcpPort = tcpPort;
configuration.udpPort = udpPort;
configuration.host = host;
configuration.serialization = CryptoSerializationManager.DEFAULT();
register(configuration.serialization);
// for Server -> Client RMI (ID: CLIENT_GLOBAL_OBJECT_ID)
configuration.serialization.registerRmiImplementation(TestCow.class, TestCowImpl.class);
// for Client -> Server RMI (ID: SERVER_GLOBAL_OBJECT_ID)
configuration.serialization.registerRmiInterface(TestCow.class);
final Client client = new Client(configuration);
client.setIdleTimeout(0);
// register this object as a global object that the server will get
CLIENT_GLOBAL_OBJECT_ID = client.createGlobalObject(globalRemoteClientObject);
addEndPoint(client);
client.listeners()
.add(new Listener.OnMessageReceived<Connection, MessageWithTestObject>() {
.add(new Listener.OnMessageReceived<Connection, MessageWithTestCow>() {
@Override
public
void received(Connection connection, MessageWithTestObject m) {
TestObject object = m.testObject;
void received(Connection connection, MessageWithTestCow m) {
System.err.println("Received finish signal for test for: Server -> Client");
TestCow object = m.testCow;
final int id = object.id();
assertEquals(1, id);
System.err.println("Server/Client Finished!");
assertEquals(CLIENT_GLOBAL_OBJECT_ID, id);
System.err.println("Finished test for: Server -> Client");
// normally this is in the 'connected', but we do it here, so that it's more linear and easier to debug
try {
connection.getRemoteObject(SERVER_GLOBAL_OBJECT_ID, new RemoteObjectCallback<TestObject>() {
connection.getRemoteObject(SERVER_GLOBAL_OBJECT_ID, new RemoteObjectCallback<TestCow>() {
@Override
public
void created(final TestObject remoteObject) {
runTest(connection, globalRemoteServerObject, remoteObject, SERVER_GLOBAL_OBJECT_ID);
void created(final TestCow remoteObject) {
// MUST run on a separate thread because remote object method invocations are blocking
new Thread() {
@Override
public
void run() {
System.err.println("Running test for: Client -> Server");
runTest(connection, globalRemoteServerObject, remoteObject, SERVER_GLOBAL_OBJECT_ID);
System.err.println("Done with test for: Client -> Server");
}
}.start();
}
});
} catch (IOException e) {
@ -287,21 +328,6 @@ class RmiGlobalTest extends BaseTest {
waitForThreads();
}
private
interface TestObject extends Serializable {
void throwException();
void moo();
void moo(String value);
void moo(String value, long delay);
int id();
float slow();
}
private static
class ConnectionAware {
private
@ -320,13 +346,13 @@ class RmiGlobalTest extends BaseTest {
private static
class TestObjectImpl extends ConnectionAware implements TestObject {
class TestCowImpl extends ConnectionAware implements TestCow {
public long value = System.currentTimeMillis();
public int moos;
private final int id = 1;
public
TestObjectImpl() {
TestCowImpl() {
}
@Override
@ -379,12 +405,4 @@ class RmiGlobalTest extends BaseTest {
return 123.0F;
}
}
private static
class MessageWithTestObject implements RmiMessages {
public int number;
public String text;
public TestObject testObject;
}
}

View File

@ -159,7 +159,7 @@ class RmiTest extends BaseTest {
// Test sending a reference to a remote object.
MessageWithTestObject m = new MessageWithTestObject();
MessageWithTestCow m = new MessageWithTestCow();
m.number = 678;
m.text = "sometext";
m.testCow = test;
@ -172,7 +172,7 @@ class RmiTest extends BaseTest {
public static
void register(dorkbox.network.util.CryptoSerializationManager manager) {
manager.register(Object.class); // Needed for Object#toString, hashCode, etc.
manager.register(MessageWithTestObject.class);
manager.register(MessageWithTestCow.class);
manager.register(UnsupportedOperationException.class);
}
@ -201,10 +201,10 @@ class RmiTest extends BaseTest {
server.bind(false);
final ListenerBridge listeners = server.listeners();
listeners.add(new Listener.OnMessageReceived<Connection, MessageWithTestObject>() {
listeners.add(new Listener.OnMessageReceived<Connection, MessageWithTestCow>() {
@Override
public
void received(Connection connection, MessageWithTestObject m) {
void received(Connection connection, MessageWithTestCow m) {
System.err.println("Received finish signal for test for: Client -> Server");
TestCow object = m.testCow;
@ -296,10 +296,10 @@ class RmiTest extends BaseTest {
});
client.listeners()
.add(new Listener.OnMessageReceived<Connection, MessageWithTestObject>() {
.add(new Listener.OnMessageReceived<Connection, MessageWithTestCow>() {
@Override
public
void received(Connection connection, MessageWithTestObject m) {
void received(Connection connection, MessageWithTestCow m) {
System.err.println("Received finish signal for test for: Client -> Server");
TestCow object = m.testCow;

View File

@ -19,7 +19,7 @@ import dorkbox.network.connection.Connection;
import dorkbox.network.connection.CryptoSerializationManager;
import dorkbox.network.rmi.RemoteObjectCallback;
import dorkbox.network.rmi.RmiTest;
import dorkbox.network.rmi.TestObject;
import dorkbox.network.rmi.TestCow;
import io.netty.util.ResourceLeakDetector;
/**
@ -41,8 +41,8 @@ class TestClient
// rootLogger.setLevel(Level.OFF);
// rootLogger.setLevel(Level.DEBUG);
rootLogger.setLevel(Level.TRACE);
rootLogger.setLevel(Level.DEBUG);
// rootLogger.setLevel(Level.TRACE);
// rootLogger.setLevel(Level.ALL);
@ -79,11 +79,13 @@ class TestClient
Configuration configuration = new Configuration();
configuration.tcpPort = 2000;
configuration.udpPort = 2001;
configuration.udtPort = 2002;
configuration.host = "localhost";
configuration.serialization = CryptoSerializationManager.DEFAULT();
RmiTest.register(configuration.serialization);
configuration.serialization.registerRmiInterface(TestObject.class);
configuration.serialization.registerRmiInterface(TestCow.class);
try {
@ -99,16 +101,17 @@ class TestClient
try {
// if this is called in the dispatch thread, it will block network comms while waiting for a response and it won't work...
connection.getRemoteObject(TestObject.class, new RemoteObjectCallback<TestObject>() {
connection.getRemoteObject(TestCow.class, new RemoteObjectCallback<TestCow>() {
@Override
public
void created(final TestObject remoteObject) {
void created(final TestCow remoteObject) {
new Thread() {
@Override
public
void run() {
// MUST run on a separate thread because remote object method invocations are blocking
RmiTest.runTests(connection, remoteObject, 1);
System.err.println("DONE");
}
}.start();
}
@ -117,18 +120,6 @@ class TestClient
e.printStackTrace();
fail();
}
// try {
// connection.getRemoteObject(TestObject.class, new RemoteObjectCallback<TestObject>() {
// @Override
// public
// void created(final TestObject remoteObject) {
// remoteObject.test();
// }
// });
// } catch (IOException e) {
// e.printStackTrace();
// }
}
});

View File

@ -5,8 +5,8 @@ import java.io.IOException;
import dorkbox.network.Server;
import dorkbox.network.connection.CryptoSerializationManager;
import dorkbox.network.rmi.RmiTest;
import dorkbox.network.rmi.TestObject;
import dorkbox.network.rmi.TestObjectImpl;
import dorkbox.network.rmi.TestCow;
import dorkbox.network.rmi.TestCowImpl;
import dorkbox.util.exceptions.InitializationException;
import dorkbox.util.exceptions.SecurityException;
@ -22,10 +22,12 @@ class TestServer
dorkbox.network.Configuration configuration = new dorkbox.network.Configuration();
configuration.tcpPort = 2000;
configuration.udpPort = 2001;
configuration.udtPort = 2002;
configuration.serialization = CryptoSerializationManager.DEFAULT();
RmiTest.register(configuration.serialization);
configuration.serialization.registerRmiImplementation(TestObject.class, TestObjectImpl.class);
configuration.serialization.registerRmiImplementation(TestCow.class, TestCowImpl.class);
Server server = null;
try {
@ -65,7 +67,7 @@ class TestServer
// System.err.println("CONNECTED!");
//
// try {
// TestObject object = connection.createProxyObject(TestObject.class);
// TestCow object = connection.createProxyObject(TestCow.class);
// object.test();
// } catch (IOException e) {
// e.printStackTrace();