RMI is now more robust and forgiving

This commit is contained in:
nathan 2019-01-09 18:19:20 +01:00
parent c927f87316
commit 33853d44e3
19 changed files with 416 additions and 334 deletions

View File

@ -53,7 +53,7 @@ class CachedMethod {
* in some cases, we want to override the cached method, with one that supports passing 'Connection' as the first argument. This is
* completely OPTIONAL, however - greatly adds functionality to RMI methods.
*/
public boolean overriddenMethod;
public Method overriddenMethod;
@SuppressWarnings("rawtypes")
public Serializer[] serializers;
@ -62,4 +62,10 @@ class CachedMethod {
Object invoke(final Connection connection, Object target, Object[] args) throws Exception {
return this.method.invoke(target, args);
}
@Override
public
String toString() {
return "CachedMethod{" + method.getName() + ", methodClassID=" + methodClassID + ", methodIndex=" + methodIndex + '}';
}
}

View File

@ -42,6 +42,7 @@ public
class InvokeMethod implements RmiMessage {
public int objectID; // the registered kryo ID for the object
// This class is NOT sent across the wire. We use a custom serializer to manage this.
public CachedMethod cachedMethod;
public Object[] args;

View File

@ -34,6 +34,8 @@
*/
package dorkbox.network.rmi;
import java.lang.reflect.Method;
import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.KryoException;
import com.esotericsoftware.kryo.Serializer;
@ -47,6 +49,7 @@ import dorkbox.network.connection.KryoExtra;
*/
public
class InvokeMethodSerializer extends Serializer<InvokeMethod> {
private static final boolean DEBUG = false;
public
InvokeMethodSerializer() {
@ -56,9 +59,12 @@ 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);
if (DEBUG) {
System.err.println("WRITING");
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);
@ -90,9 +96,12 @@ class InvokeMethodSerializer extends Serializer<InvokeMethod> {
int methodClassID = input.readInt(true);
byte methodIndex = input.readByte();
// System.err.println(":: objectID " + objectID);
// System.err.println(":: methodClassID " + methodClassID);
// System.err.println(":: methodIndex " + methodIndex);
if (DEBUG) {
System.err.println("READING");
System.err.println(":: objectID " + objectID);
System.err.println(":: methodClassID " + methodClassID);
System.err.println(":: methodIndex " + methodIndex);
}
CachedMethod cachedMethod;
try {
@ -107,10 +116,13 @@ class InvokeMethodSerializer extends Serializer<InvokeMethod> {
Object[] args;
Serializer<?>[] serializers = cachedMethod.serializers;
Method method;
int argStartIndex;
if (cachedMethod.overriddenMethod) {
if (cachedMethod.overriddenMethod != null) {
// did we override our cached method? This is not common.
method = cachedMethod.overriddenMethod;
// this is specifically when we override an interface method, with an implementation method + Connection parameter (@ index 0)
argStartIndex = 1;
@ -118,12 +130,15 @@ class InvokeMethodSerializer extends Serializer<InvokeMethod> {
args[0] = ((KryoExtra) kryo).connection;
}
else {
method = cachedMethod.method;
argStartIndex = 0;
args = new Object[serializers.length];
}
Class<?>[] parameterTypes = cachedMethod.method.getParameterTypes();
Class<?>[] parameterTypes = method.getParameterTypes();
// we don't start at 0 for the arguments, in case we have an overwritten method (in which case, the 1st arg is always "Connection.class")
for (int i = 0, n = serializers.length, j = argStartIndex; i < n; i++, j++) {
Serializer<?> serializer = serializers[i];

View File

@ -38,6 +38,7 @@ import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.Serializer;
import com.esotericsoftware.kryo.io.Input;
import com.esotericsoftware.kryo.io.Output;
import com.esotericsoftware.kryo.util.IdentityMap;
import dorkbox.network.connection.KryoExtra;
@ -50,8 +51,12 @@ import dorkbox.network.connection.KryoExtra;
public
class RemoteObjectSerializer<T> extends Serializer<T> {
private final IdentityMap<Class<?>, Class<?>> rmiImplToIface;
public
RemoteObjectSerializer() {
RemoteObjectSerializer(final IdentityMap<Class<?>, Class<?>> rmiImplToIface) {
super(false);
this.rmiImplToIface = rmiImplToIface;
}
@Override
@ -65,9 +70,13 @@ class RemoteObjectSerializer<T> extends Serializer<T> {
@SuppressWarnings({"rawtypes", "unchecked"})
@Override
public
T read(Kryo kryo, Input input, Class type) {
T read(Kryo kryo, Input input, Class implementationType) {
KryoExtra kryoExtra = (KryoExtra) kryo;
int objectID = input.readInt(true);
return (T) kryoExtra.connection.getProxyObject(objectID, type);
// We have to lookup the iface, since the proxy object requires it
Class<?> iface = rmiImplToIface.get(implementationType);
return (T) kryoExtra.connection.getProxyObject(objectID, iface);
}
}

View File

@ -54,7 +54,7 @@ import dorkbox.util.collections.LockFreeIntBiMap;
* object transformation (because there is no serialization occurring) using a series of weak hashmaps.
* <p/>
* <p/>
* Objects are {@link RmiSerializationManager#registerRmiInterface(Class)}, and endpoint connections can then {@link
* Objects are {@link RmiSerializationManager#registerRmi(Class, Class)}, and endpoint connections can then {@link
* Connection#createRemoteObject(Class, RemoteObjectCallback)} for the registered objects.
* <p/>
* 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
@ -236,7 +236,7 @@ class RmiBridge {
.append(argString)
.append(")");
if (cachedMethod.overriddenMethod) {
if (cachedMethod.overriddenMethod != null) {
// did we override our cached method? This is not common.
stringBuilder.append(" [Connection method override]");
}
@ -279,8 +279,8 @@ class RmiBridge {
}
InvokeMethodResult invokeMethodResult = new InvokeMethodResult();
invokeMethodResult.objectID = invokeMethod.objectID;
invokeMethodResult.responseID = (byte) responseID;
invokeMethodResult.rmiObjectId = invokeMethod.objectID;
invokeMethodResult.responseId = (byte) responseID;
// Do not return non-primitives if transmitReturnVal is false

View File

@ -101,7 +101,7 @@ class RmiObjectLocalHandler extends RmiObjectHandler {
int argStartIndex;
if (cachedMethod.overriddenMethod) {
if (cachedMethod.overriddenMethod != null) {
// did we override our cached method? This is not common.
// this is specifically when we override an interface method, with an implementation method + Connection parameter (@ index 0)
argStartIndex = 1;

View File

@ -54,8 +54,12 @@ class RmiObjectNetworkHandler extends RmiObjectHandler {
//
// CREATE a new ID, and register the ID and new object (must create a new one) in the object maps
// have to lookup the implementation class
Class<?> rmiImpl = connection.getEndPoint().getSerialization().getRmiImpl(interfaceClass);
// For network connections, the interface class kryo ID == implementation class kryo ID, so they switch automatically.
RmiRegistration registrationResult = connection.createNewRmiObject(interfaceClass, interfaceClass, callbackId);
RmiRegistration registrationResult = connection.createNewRmiObject(interfaceClass, rmiImpl, callbackId);
connection.send(registrationResult);
// connection transport is flushed in calling method (don't need to do it here)
}

View File

@ -42,12 +42,6 @@ class RmiRegistration implements RmiMessage {
*/
public int callbackId;
@SuppressWarnings("unused")
private
RmiRegistration() {
// for serialization
}
/**
* When requesting a new or existing remote object
* SENT FROM "local" -> "remote"

View File

@ -0,0 +1,83 @@
/*
* Copyright 2010 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.network.rmi;
import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.Serializer;
import com.esotericsoftware.kryo.io.Input;
import com.esotericsoftware.kryo.io.Output;
import dorkbox.network.connection.KryoExtra;
/**
* This is required, because with RMI, it is possible that the IMPL and IFACE can have DIFFERENT class IDs, in which case, the "client" cannot read the correct
* objects (because the IMPL class might not be registered, or that ID might be registered to a different class)
*/
public
class RmiRegistrationSerializer extends Serializer<RmiRegistration> {
public
RmiRegistrationSerializer() {
}
@Override
public
void write(Kryo kryo, Output output, RmiRegistration object) {
output.writeBoolean(object.isRequest);
output.writeInt(object.rmiId, true);
output.writeInt(object.callbackId, true);
int id = kryo.getRegistration(object.interfaceClass).getId();
output.writeInt(id, true);
if (object.remoteObject != null) {
KryoExtra kryoExtra = (KryoExtra) kryo;
id = kryoExtra.connection.getRegisteredId(object.remoteObject);
} else {
// can be < 0 or >= RmiBridge.INVALID_RMI (Integer.MAX_VALUE)
id = -1;
}
output.writeInt(id, false);
}
@SuppressWarnings({"rawtypes", "unchecked"})
@Override
public
RmiRegistration read(Kryo kryo, Input input, Class implementationType) {
boolean isRequest = input.readBoolean();
int rmiId = input.readInt(true);
int callbackId = input.readInt(true);
int interfaceClassId = input.readInt(true);
int remoteObjectId = input.readInt(false);
// // We have to lookup the iface, since the proxy object requires it
Class iface = kryo.getRegistration(interfaceClassId).getType();
Object remoteObject = null;
if (remoteObjectId >= 0 && remoteObjectId < RmiBridge.INVALID_RMI) {
KryoExtra kryoExtra = (KryoExtra) kryo;
remoteObject = kryoExtra.connection.getProxyObject(remoteObjectId, iface);
}
RmiRegistration rmiRegistration = new RmiRegistration(iface, rmiId, callbackId, remoteObject);
rmiRegistration.isRequest = isRequest;
return rmiRegistration;
}
}

View File

@ -100,18 +100,13 @@ class RmiUtils {
/**
* @param logger
* @param kryo
* @param asmEnabled
* @param iFace this is never null.
* @param impl this is NULL on the rmi "client" side. This is NOT NULL on the "server" side (where the object lives)
* @param classId
* @return
*/
public static
CachedMethod[] getCachedMethods(final Logger logger, final Kryo kryo, final boolean asmEnabled, final Class<?> iFace, final Class<?> impl, final int classId) {
MethodAccess ifaceMethodAccess = null;
MethodAccess implMethodAccess = null;
MethodAccess ifaceAsmMethodAccess = null;
MethodAccess implAsmMethodAccess = null;
// RMI is **ALWAYS** based upon an interface, so we must always make sure to get the methods of the interface, instead of the
// implementation, otherwise we will have the wrong order of methods, so invoking a method by it's index will fail.
@ -120,22 +115,24 @@ class RmiUtils {
final int size = methods.length;
final CachedMethod[] cachedMethods = new CachedMethod[size];
final Method[] implMethods;
if (impl != null) {
if (impl.isInterface()) {
throw new IllegalArgumentException("Cannot have type as an interface, it must be an implementation");
}
final Method[] implMethods = getMethods(impl);
overwriteMethodsWithConnectionParam(implMethods, methods);
implMethods = getMethods(impl);
// reflectASM
// doesn't work on android (set correctly by the serialization manager)
// can't get any method from the 'Object' object (we get from the interface, which is NOT 'Object')
// and it MUST be public (iFace is always public)
if (asmEnabled) {
implMethodAccess = getReflectAsmMethod(logger, impl);
implAsmMethodAccess = getReflectAsmMethod(logger, impl);
}
} else {
implMethods = null;
}
// reflectASM
@ -143,7 +140,7 @@ class RmiUtils {
// can't get any method from the 'Object' object (we get from the interface, which is NOT 'Object')
// and it MUST be public (iFace is always public)
if (asmEnabled) {
ifaceMethodAccess = getReflectAsmMethod(logger, iFace);
ifaceAsmMethodAccess = getReflectAsmMethod(logger, iFace);
}
for (int i = 0; i < size; i++) {
@ -153,50 +150,44 @@ class RmiUtils {
Class<?>[] parameterTypes = method.getParameterTypes();
// copy because they can be overridden
boolean overriddenMethod = false;
MethodAccess tweakMethodAccess = ifaceMethodAccess;
CachedMethod cachedMethod = null;
MethodAccess ifaceORimplMethodAccess = ifaceAsmMethodAccess;
// reflectAsm doesn't like "Object" class methods
boolean canUseAsm = asmEnabled && method.getDeclaringClass() != Object.class;
Method overwrittenMethod = null;
// this is how we detect if the method has been changed from the interface -> implementation + connection parameter
if (declaringClass.equals(impl)) {
tweakMethodAccess = implMethodAccess;
overriddenMethod = true;
if (implMethods != null) {
overwrittenMethod = getOverwriteMethodWithConnectionParam(implMethods, method);
if (overwrittenMethod != null) {
if (logger.isTraceEnabled()) {
logger.trace("Overridden method: {}.{}", impl, method.getName());
}
if (logger.isTraceEnabled()) {
logger.trace("Overridden method: {}.{}", impl, method.getName());
// still might be null!
ifaceORimplMethodAccess = implAsmMethodAccess;
}
}
CachedMethod cachedMethod = null;
// reflectAsm doesn't like "Object" class methods...
if (tweakMethodAccess != null && method.getDeclaringClass() != Object.class) {
if (canUseAsm) {
try {
final int index = tweakMethodAccess.getIndex(method.getName(), parameterTypes);
int index;
if (overwrittenMethod != null) {
// have to take into account the overwritten method's first parameter will ALWAYS be "Connection"
index = ifaceORimplMethodAccess.getIndex(method.getName(), overwrittenMethod.getParameterTypes());
} else {
index = ifaceORimplMethodAccess.getIndex(method.getName(), parameterTypes);
}
AsmCachedMethod asmCachedMethod = new AsmCachedMethod();
asmCachedMethod.methodAccessIndex = index;
asmCachedMethod.methodAccess = tweakMethodAccess;
asmCachedMethod.methodAccess = ifaceORimplMethodAccess;
asmCachedMethod.name = method.getName();
if (overriddenMethod) {
// logger.error(tweakMethod.getName() + " " + Arrays.toString(parameterTypes) + " index: " + index +
// " methodIndex: " + i + " classID: " + classId);
// This is because we have to store the serializer for each parameter, but ONLY for the ORIGINAL method, not the overridden one.
// this gets our parameters "back to the original" method. We do this to minimize the overhead of sending the args over
int length = parameterTypes.length;
if (length == 1) {
parameterTypes = new Class<?>[0];
}
else {
length--;
Class<?>[] newArgs = new Class<?>[length];
System.arraycopy(parameterTypes, 1, newArgs, 0, length);
parameterTypes = newArgs;
}
}
cachedMethod = asmCachedMethod;
} catch (Exception e) {
logger.trace("Unable to use ReflectAsm for {}.{} (using java reflection instead)", declaringClass, method.getName(), e);
@ -207,10 +198,11 @@ class RmiUtils {
cachedMethod = new CachedMethod();
}
cachedMethod.overriddenMethod = overriddenMethod;
cachedMethod.methodClassID = classId;
// we ALSO have to setup "normal" reflection access to these methods
// this MIGHT be null, but if it is not, this is the method we will invoke INSTEAD of the "normal" method
cachedMethod.overriddenMethod = overwrittenMethod;
cachedMethod.method = method;
cachedMethod.methodIndex = i;
@ -229,9 +221,9 @@ class RmiUtils {
return cachedMethods;
}
// NOTE: does not null check
/**
* This will overwrite an original (iface based) method with a method from the implementation ONLY if there is the extra 'Connection' parameter (as per above)
* NOTE: does not null check
*
* @param implMethods methods from the implementation
* @param origMethods methods from the interface
@ -241,47 +233,62 @@ class RmiUtils {
for (int i = 0, origMethodsSize = origMethods.length; i < origMethodsSize; i++) {
final Method origMethod = origMethods[i];
String origName = origMethod.getName();
Class<?>[] origTypes = origMethod.getParameterTypes();
int origLength = origTypes.length + 1;
Method overwriteMethodsWithConnectionParam = getOverwriteMethodWithConnectionParam(implMethods, origMethod);
if (overwriteMethodsWithConnectionParam != null) {
origMethods[i] = overwriteMethodsWithConnectionParam;
}
}
}
for (Method implMethod : implMethods) {
String implName = implMethod.getName();
Class<?>[] implTypes = implMethod.getParameterTypes();
int implLength = implTypes.length;
/**
* This will overwrite an original (iface based) method with a method from the implementation ONLY if there is the extra 'Connection' parameter (as per above)
* NOTE: does not null check
*
* @param implMethods methods from the implementation
* @param origMethod original method from the interface
*/
private static
Method getOverwriteMethodWithConnectionParam(final Method[] implMethods, final Method origMethod) {
String origName = origMethod.getName();
Class<?>[] origTypes = origMethod.getParameterTypes();
int origLength = origTypes.length + 1;
if (origLength != implLength || !(origName.equals(implName))) {
continue;
for (Method implMethod : implMethods) {
String implName = implMethod.getName();
Class<?>[] implTypes = implMethod.getParameterTypes();
int implLength = implTypes.length;
if (origLength != implLength || !(origName.equals(implName))) {
continue;
}
// checkLength > 0
Class<?> shouldBeConnectionType = implTypes[0];
if (ClassHelper.hasInterface(Connection.class, shouldBeConnectionType)) {
// now we check to see if our "check" method is equal to our "cached" method + Connection
if (implLength == 1) {
// we only have "Connection" as a parameter
return implMethod;
}
// checkLength > 0
Class<?> shouldBeConnectionType = implTypes[0];
if (ClassHelper.hasInterface(Connection.class, shouldBeConnectionType)) {
// now we check to see if our "check" method is equal to our "cached" method + Connection
if (implLength == 1) {
// we only have "Connection" as a parameter
origMethods[i] = implMethod;
break;
}
else {
boolean found = true;
for (int k = 1; k < implLength; k++) {
if (origTypes[k - 1] != implTypes[k]) {
// make sure all the parameters match. Cannot use arrays.equals(*), because one will have "Connection" as
// a parameter - so we check that the rest match
found = false;
break;
}
}
if (found) {
origMethods[i] = implMethod;
else {
boolean found = true;
for (int k = 1; k < implLength; k++) {
if (origTypes[k - 1] != implTypes[k]) {
// make sure all the parameters match. Cannot use arrays.equals(*), because one will have "Connection" as
// a parameter - so we check that the rest match
found = false;
break;
}
}
if (found) {
return implMethod;
}
}
}
}
return null;
}
/**

View File

@ -103,30 +103,37 @@ interface RmiSerializationManager extends SerializationManager {
Class<?> getRmiImpl(Class<?> iFace);
/**
* Enable remote method invocation (RMI) for this connection. There is additional overhead to using RMI.
* Enable this endpoint (a "client") to access methods and create objects (RMI) on a "remote server". This is NOT bi-directional, and the "remote server"
* cannot access or create remote objects on this endpoint.
* <p>
* This is the same as calling {@link RmiSerializationManager#registerRmi(Class, Class)} with a null parameter for the implementation class.
* <p>
* 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.
* <p>
* If the type of a parameter is not final (primitives are final) then an extra byte is written for that parameter.
*/
RmiSerializationManager registerRmiInterface(Class<?> ifaceClass);
RmiSerializationManager registerRmi(Class<?> ifaceClass);
/**
* Enable remote method invocation (RMI) for this connection. There is additional overhead to using RMI.
* Enable a "remote client" to access methods and create objects (RMI) for this endpoint. This is NOT bi-directional, and this endpoint cannot access or \
* create remote objects on the "remote client".
* <p>
* Calling this method with a null parameter for the implementation class is the same as calling {@link RmiSerializationManager#registerRmi(Class)}
* <p>
* 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.
* <p>
* If the type of a parameter is not final (primitives are final) then an extra byte is written for that parameter.
* <p>
* This method overrides the interface -> implementation. This is so incoming proxy objects will get auto-changed into their correct
* implementation type, so this side of the connection knows what to do with the proxy object.
* <p>
*
* @throws IllegalArgumentException if the iface/impl have previously been overridden
*/
<Iface, Impl extends Iface> RmiSerializationManager registerRmiImplementation(Class<Iface> ifaceClass, Class<Impl> implClass);
<Iface, Impl extends Iface> RmiSerializationManager registerRmi(Class<Iface> ifaceClass, Class<Impl> implClass);
/**
* Gets the cached methods for the specified class ID

View File

@ -31,7 +31,6 @@ import org.slf4j.Logger;
import com.esotericsoftware.kryo.ClassResolver;
import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.KryoException;
import com.esotericsoftware.kryo.Registration;
import com.esotericsoftware.kryo.Serializer;
import com.esotericsoftware.kryo.factories.ReflectionSerializerFactory;
import com.esotericsoftware.kryo.factories.SerializerFactory;
@ -55,6 +54,7 @@ import dorkbox.network.rmi.InvokeMethodResult;
import dorkbox.network.rmi.InvokeMethodSerializer;
import dorkbox.network.rmi.RemoteObjectSerializer;
import dorkbox.network.rmi.RmiRegistration;
import dorkbox.network.rmi.RmiRegistrationSerializer;
import dorkbox.network.rmi.RmiUtils;
import dorkbox.objectPool.ObjectPool;
import dorkbox.objectPool.PoolableObject;
@ -127,23 +127,12 @@ class Serialization<C extends CryptoConnection> implements CryptoSerializationMa
}
}
private static
class RemoteIfaceClass {
private final Class<?> ifaceClass;
RemoteIfaceClass(final Class<?> ifaceClass) {
this.ifaceClass = ifaceClass;
}
}
private static
class RemoteImplClass {
class RmiClassSerializer {
private final Class<?> ifaceClass;
private final Class<?> implClass;
RemoteImplClass(final Class<?> ifaceClass, final Class<?> implClass) {
RmiClassSerializer(final Class<?> ifaceClass, final Class<?> implClass) {
this.ifaceClass = ifaceClass;
this.implClass = implClass;
}
@ -171,8 +160,8 @@ class Serialization<C extends CryptoConnection> implements CryptoSerializationMa
* <p>
* Registered classes are serialized as an int id, avoiding the overhead of serializing the class name, but have the
* drawback of needing to know the classes to be serialized up front.
* @param implementationRequired If true, interfaces are not permitted to be registered, outside of the {@link #registerRmiInterface(Class)} and
* {@link #registerRmiImplementation(Class, Class)} methods. If false, then interfaces can also be registered.
* @param implementationRequired If true, interfaces are not permitted to be registered, outside of the {@link #registerRmi(Class, Class)} and
* {@link #registerRmi(Class, Class)} methods. If false, then interfaces can also be registered.
* <p>
* Enabling interface registration permits matching a different RMI client/server serialization scheme, since
* interfaces are generally in a "common" package, accessible to both the RMI client and server.
@ -263,10 +252,7 @@ class Serialization<C extends CryptoConnection> implements CryptoSerializationMa
// the purpose of the method cache, is to accelerate looking up methods for specific class
private IntMap<CachedMethod[]> methodCache;
private RemoteObjectSerializer remoteObjectSerializer;
// used to track which interface -> implementation, for use by RMI
private final IntMap<Class<?>> rmiKryoIdToImpl = new IntMap<Class<?>>();
private final IntMap<Class<?>> rmiKryoIdToIface = new IntMap<Class<?>>();
private RmiRegistrationSerializer rmiRegistrationSerializer;
private final IdentityMap<Class<?>, Class<?>> rmiIfaceToImpl = new IdentityMap<Class<?>, Class<?>>();
private final IdentityMap<Class<?>, Class<?>> rmiImplToIface = new IdentityMap<Class<?>, Class<?>>();
@ -296,8 +282,8 @@ class Serialization<C extends CryptoConnection> implements CryptoSerializationMa
* Registered classes are serialized as an int id, avoiding the overhead of serializing the class name, but have the
* drawback of needing to know the classes to be serialized up front.
* <p>
* @param implementationRequired If true, interfaces are not permitted to be registered, outside of the {@link #registerRmiInterface(Class)} and
* {@link #registerRmiImplementation(Class, Class)} methods. If false, then interfaces can also be registered.
* @param implementationRequired If true, interfaces are not permitted to be registered, outside of the {@link #registerRmi(Class, Class)} and
* {@link #registerRmi(Class, Class)} methods. If false, then interfaces can also be registered.
* <p>
* Enabling interface registration permits matching a different RMI client/server serialization scheme, since
* interfaces are generally in a "common" package, accessible to both the RMI client and server.
@ -325,6 +311,8 @@ class Serialization<C extends CryptoConnection> implements CryptoSerializationMa
// we HAVE to pre-allocate the KRYOs
KryoExtra<C> kryo = new KryoExtra<C>(Serialization.this);
boolean traceEnabled = logger.isTraceEnabled();
kryo.getFieldSerializerConfig()
.setUseAsm(useAsm);
kryo.setRegistrationRequired(registrationRequired);
@ -333,13 +321,13 @@ class Serialization<C extends CryptoConnection> implements CryptoSerializationMa
if (usesRmi) {
kryo.register(Class.class);
kryo.register(RmiRegistration.class);
kryo.register(RmiRegistration.class, rmiRegistrationSerializer);
kryo.register(InvokeMethod.class, methodSerializer);
kryo.register(Object[].class);
// This has to be for each kryo instance!
InvocationResultSerializer resultSerializer = new InvocationResultSerializer(kryo);
resultSerializer.removeField("objectID");
resultSerializer.removeField("rmiObjectId");
kryo.register(InvokeMethodResult.class, resultSerializer);
kryo.register(InvocationHandler.class, invocationSerializer);
@ -349,63 +337,56 @@ class Serialization<C extends CryptoConnection> implements CryptoSerializationMa
for (Object clazz : classesToRegister) {
if (clazz instanceof Class) {
Class aClass = (Class) clazz;
kryo.register(aClass);
int id = kryo.register(aClass).getId();
if (traceEnabled) {
logger.trace("Registering {} -> {}", id, aClass.getName());
}
}
else if (clazz instanceof ClassSerializer) {
ClassSerializer classSerializer = (ClassSerializer) clazz;
kryo.register(classSerializer.clazz, classSerializer.serializer);
int id = kryo.register(classSerializer.clazz, classSerializer.serializer).getId();
if (traceEnabled) {
logger.trace("Registering {} -> {} using {}", id, classSerializer.clazz.getName(), classSerializer.serializer.getClass().getName());
}
}
else if (clazz instanceof ClassSerializer1) {
ClassSerializer1 classSerializer = (ClassSerializer1) clazz;
kryo.register(classSerializer.clazz, classSerializer.id);
if (traceEnabled) {
logger.trace("Registering {} (specified) -> {}", classSerializer.id, classSerializer.clazz);
}
}
else if (clazz instanceof ClassSerializer2) {
ClassSerializer2 classSerializer = (ClassSerializer2) clazz;
kryo.register(classSerializer.clazz, classSerializer.serializer, classSerializer.id);
}
else if (clazz instanceof RemoteIfaceClass) {
// THIS IS DONE ON THE "CLIENT"
// "server" means the side of the connection that has the implementation details for the RMI object
// "client" means the side of the connection that accesses the "server" side object via a proxy object
// the client will NEVER send this object to the server.
// the server will ONLY send this object to the client, where on the client it becomes the proxy/interface.
RemoteIfaceClass remoteIfaceClass = (RemoteIfaceClass) clazz;
// registers the interface, so that when it is READ, it becomes a "magic" proxy object
kryo.register(remoteIfaceClass.ifaceClass, remoteObjectSerializer);
}
else if (clazz instanceof RemoteImplClass) {
// THIS IS DONE ON THE "SERVER"
// "server" means the side of the connection that has the implementation details for the RMI object
// "client" means the side of the connection that accesses the "server" side object via a proxy object
// the client will NEVER send this object to the server.
// the server will ONLY send this object to the client, where on the client it becomes the proxy/interface.
RemoteImplClass remoteImplClass = (RemoteImplClass) clazz;
int id;
// check to see if the interface is already registered. If so, we override it with the implementation class
EditableDefaultClassResolver classResolver = (EditableDefaultClassResolver) kryo.getClassResolver();
Registration registration = classResolver.getRegistration(remoteImplClass.ifaceClass);
if (registration != null) {
// override it with the implementation
int oldId = registration.getId();
id = kryo.register(remoteImplClass.implClass, remoteObjectSerializer, oldId).getId();
// delete the old one (since it shouldn't be stored anywhere)
classResolver.deleteRegistrationType(remoteImplClass.ifaceClass);
if (traceEnabled) {
logger.trace("Registering {} (specified) -> {} using {}", classSerializer.id, classSerializer.clazz.getName(), classSerializer.serializer.getClass().getName());
}
else {
// we have a new registration.
}
else if (clazz instanceof RmiClassSerializer) {
// "client" always sends kryo-ID (of iface) with RMI-ID (or command to create/get one)
// "server" always responds with kryo-ID (of impl) with RMI-ID of remote object. When creating proxy object, "client" looks-up kryo-id of iface from impl
// registers the implementation, so that when it is WRITTEN, it becomes a "magic" proxy object
id = kryo.register(remoteImplClass.implClass, remoteObjectSerializer).getId();
RmiClassSerializer remoteImplClass = (RmiClassSerializer) clazz;
int id = kryo.register(remoteImplClass.ifaceClass, remoteObjectSerializer).getId();
if (traceEnabled) {
logger.trace("Registering {} -> RMI interface : {}", id, remoteImplClass.ifaceClass);
}
// sets up the RMI, so when we receive the iface class from the client, we know what impl to use
// if this is over-written, we don't care.
rmiKryoIdToImpl.put(id, remoteImplClass.implClass); // the "server" translates the ID back to the impl on kryo read
rmiKryoIdToIface.put(id, remoteImplClass.ifaceClass); // the "server" translates the ID to the iface on kryo write
// we only want to use an implementation class IFF we plan on having one, otherwise ignore it
if (remoteImplClass.implClass != null) {
// NOTE: as a "server" we must have the "remote object" serializer write out the INTERFACE CLASS ID so that the "client" (if it DOES NOT have an impl)
// can read this as the correct class!
int id2 = kryo.register(remoteImplClass.implClass, remoteObjectSerializer).getId();
if (traceEnabled) {
logger.trace("Registering {} -> RMI implementation: {}", id2, remoteImplClass.implClass);
}
}
}
}
@ -568,7 +549,8 @@ class Serialization<C extends CryptoConnection> implements CryptoSerializationMa
methodSerializer = new InvokeMethodSerializer();
invocationSerializer = new InvocationHandlerSerializer(logger);
remoteObjectSerializer = new RemoteObjectSerializer();
remoteObjectSerializer = new RemoteObjectSerializer(rmiImplToIface);
rmiRegistrationSerializer = new RmiRegistrationSerializer();
methodCache = new IntMap<CachedMethod[]>();
return true;
@ -604,7 +586,12 @@ class Serialization<C extends CryptoConnection> implements CryptoSerializationMa
}
/**
* Enable remote method invocation (RMI) for this connection. There is additional overhead to using RMI.
* Enable this endpoint (a "client") to access methods and create objects (RMI) on a "remote server". This is NOT bi-directional, and the "remote server"
* cannot access or create remote objects on this endpoint.
* <p>
* This is the same as calling {@link RmiSerializationManager#registerRmi(Class, Class)} with a null parameter for the implementation class.
* <p>
* 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.
@ -613,67 +600,120 @@ class Serialization<C extends CryptoConnection> implements CryptoSerializationMa
*/
@Override
public synchronized
RmiSerializationManager registerRmiInterface(Class<?> ifaceClass) {
if (initialized) {
logger.warn("Serialization manager already initialized. Ignoring duplicate registerRemote(Class) call.");
return this;
}
else if (!ifaceClass.isInterface()) {
throw new IllegalArgumentException("Cannot register an implementation for RMI access. It must be an interface.");
}
usesRmi = true;
classesToRegister.add(new RemoteIfaceClass(ifaceClass));
return this;
RmiSerializationManager registerRmi(Class<?> ifaceClass) {
return registerRmi(ifaceClass, null);
}
/**
* Enable remote method invocation (RMI) for this connection. There is additional overhead to using RMI.
* Enable a "remote client" to access methods and create objects (RMI) for this endpoint. This is NOT bi-directional, and this endpoint cannot access or \
* create remote objects on the "remote client".
* <p>
* Calling this method with a null parameter for the implementation class is the same as calling {@link RmiSerializationManager#registerRmi(Class)}
* <p>
* 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.
* <p>
* If the type of a parameter is not final (primitives are final) then an extra byte is written for that parameter.
* <p>
* This method overrides the interface -> implementation. This is so incoming proxy objects will get auto-changed into their correct
* implementation type, so this side of the connection knows what to do with the proxy object.
* <p>
*
* @throws IllegalArgumentException if the iface/impl have previously been overridden
*/
@Override
public synchronized
<Iface, Impl extends Iface> RmiSerializationManager registerRmiImplementation(Class<Iface> ifaceClass, Class<Impl> implClass) {
<Iface, Impl extends Iface> RmiSerializationManager registerRmi(Class<Iface> ifaceClass, Class<Impl> implClass) {
if (initialized) {
logger.warn("Serialization manager already initialized. Ignoring duplicate registerRemote(Class, Class) call.");
logger.warn("Serialization manager already initialized. Ignoring duplicate registerRmiImplementation(Class, Class) call.");
return this;
}
if (!ifaceClass.isInterface()) {
throw new IllegalArgumentException("Cannot register an implementation for RMI access. It must be an interface.");
}
if (implClass.isInterface()) {
if (implClass != null && implClass.isInterface()) {
throw new IllegalArgumentException("Cannot register an interface for RMI implementations. It must be an implementation.");
}
usesRmi = true;
classesToRegister.add(new RemoteImplClass(ifaceClass, implClass));
classesToRegister.add(new RmiClassSerializer(ifaceClass, implClass));
// this MUST BE UNIQUE otherwise unexpected and BAD things can happen.
Class<?> a = this.rmiIfaceToImpl.put(ifaceClass, implClass);
Class<?> b = this.rmiImplToIface.put(implClass, ifaceClass);
// we only want to assign an implementation class IFF we plan on having one, otherwise ignore it
if (implClass != null) {
// rmiIfaceToImpl tells the "server" how to create a (requested) remote object
// this MUST BE UNIQUE otherwise unexpected and BAD things can happen.
Class<?> a = this.rmiIfaceToImpl.put(ifaceClass, implClass);
Class<?> b = this.rmiImplToIface.put(implClass, ifaceClass);
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" +
" both unique per JVM");
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" +
" both unique per JVM");
}
}
return this;
}
/**
* Called when initialization is complete. This is to prevent (and recognize) out-of-order class/serializer registration. If an ID
* is already in use by a different type, a {@link KryoException} is thrown.
*/
@Override
public synchronized
void finishInit(final Logger wireReadLogger, final Logger wireWriteLogger) {
this.wireReadLogger = wireReadLogger;
this.wireWriteLogger = wireWriteLogger;
initialized = true;
// initialize the kryo pool with at least 1 kryo instance. This ALSO makes sure that all of our class registration is done
// correctly and (if not) we are are notified on the initial thread (instead of on the network update thread)
KryoExtra<C> kryo = null;
try {
kryo = kryoPool.take();
ClassResolver classResolver = kryo.getClassResolver();
// now initialize the RMI cached methods, so that they are "final" when the network threads need access to it.
for (Object clazz : classesToRegister) {
if (clazz instanceof RmiClassSerializer) {
// THIS IS DONE ON THE "SERVER"
// "server" means the side of the connection that has the implementation details for the RMI object
// "client" means the side of the connection that accesses the "server" side object via a proxy object
// the client will NEVER send this object to the server.
// the server will ONLY send this object to the client, where on the client it becomes the proxy/interface.
RmiClassSerializer remoteImplClass = (RmiClassSerializer) clazz;
int id = classResolver.getRegistration(remoteImplClass.ifaceClass)
.getId();
CachedMethod[] cachedMethods = RmiUtils.getCachedMethods(Serialization.logger, kryo, useAsm,
remoteImplClass.ifaceClass,
remoteImplClass.implClass,
id);
methodCache.put(id, cachedMethods);
}
}
} finally {
if (kryo != null) {
kryoPool.put(kryo);
}
}
}
@Override
public
CachedMethod[] getMethods(final int classId) {
return methodCache.get(classId);
}
@Override
public synchronized
boolean initialized() {
return initialized;
}
/**
* Waits until a kryo is available to write, using CAS operations to prevent having to synchronize.
* <p>
@ -780,81 +820,6 @@ class Serialization<C extends CryptoConnection> implements CryptoSerializationMa
}
}
/**
* Called when initialization is complete. This is to prevent (and recognize) out-of-order class/serializer registration. If an ID
* is already in use by a different type, a {@link KryoException} is thrown.
*/
@Override
public synchronized
void finishInit(final Logger wireReadLogger, final Logger wireWriteLogger) {
this.wireReadLogger = wireReadLogger;
this.wireWriteLogger = wireWriteLogger;
initialized = true;
// initialize the kryo pool with at least 1 kryo instance. This ALSO makes sure that all of our class registration is done
// correctly and (if not) we are are notified on the initial thread (instead of on the network update thread)
KryoExtra<C> kryo = null;
try {
kryo = kryoPool.take();
ClassResolver classResolver = kryo.getClassResolver();
// now initialize the RMI cached methods, so that they are "final" when the network threads need access to it.
for (Object clazz : classesToRegister) {
if (clazz instanceof RemoteIfaceClass) {
// THIS IS DONE ON THE "CLIENT"
// "server" means the side of the connection that has the implementation details for the RMI object
// "client" means the side of the connection that accesses the "server" side object via a proxy object
// the client will NEVER send this object to the server.
// the server will ONLY send this object to the client, where on the client it becomes the proxy/interface.
RemoteIfaceClass remoteIfaceClass = (RemoteIfaceClass) clazz;
int id = classResolver.getRegistration(remoteIfaceClass.ifaceClass)
.getId();
CachedMethod[] cachedMethods = RmiUtils.getCachedMethods(Serialization.logger, kryo, useAsm,
remoteIfaceClass.ifaceClass,
null,
id);
methodCache.put(id, cachedMethods);
}
else if (clazz instanceof RemoteImplClass) {
// THIS IS DONE ON THE "SERVER"
// "server" means the side of the connection that has the implementation details for the RMI object
// "client" means the side of the connection that accesses the "server" side object via a proxy object
// the client will NEVER send this object to the server.
// the server will ONLY send this object to the client, where on the client it becomes the proxy/interface.
RemoteImplClass remoteImplClass = (RemoteImplClass) clazz;
int id = classResolver.getRegistration(remoteImplClass.implClass)
.getId();
CachedMethod[] cachedMethods = RmiUtils.getCachedMethods(Serialization.logger, kryo, useAsm,
remoteImplClass.ifaceClass,
remoteImplClass.implClass,
id);
methodCache.put(id, cachedMethods);
}
}
} finally {
if (kryo != null) {
kryoPool.put(kryo);
}
}
}
@Override
public
CachedMethod[] getMethods(final int classId) {
return methodCache.get(classId);
}
@Override
public synchronized
boolean initialized() {
return initialized;
}
/**
* Waits until a kryo is available to write, using CAS operations to prevent having to synchronize.
* <p>

View File

@ -222,11 +222,8 @@ class RmiGlobalTest extends BaseTest {
configuration.serialization = Serialization.DEFAULT();
register(configuration.serialization);
// for Server -> Client RMI (ID: CLIENT_GLOBAL_OBJECT_ID) - NOTICE: none of the super classes/interfaces are registered!
configuration.serialization.registerRmiInterface(TestCow.class);
// for Client -> Server RMI (ID: SERVER_GLOBAL_OBJECT_ID) - NOTICE: none of the super classes/interfaces are registered!
configuration.serialization.registerRmiImplementation(TestCow.class, TestCowImpl.class);
// NOTICE: none of the super classes/interfaces are registered!
configuration.serialization.registerRmi(TestCow.class, TestCowImpl.class);
final Server server = new Server(configuration);
server.setIdleTimeout(0);
@ -286,11 +283,8 @@ class RmiGlobalTest extends BaseTest {
configuration.serialization = Serialization.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);
// NOTICE: none of the super classes/interfaces are registered!
configuration.serialization.registerRmi(TestCow.class, TestCowImpl.class);
final Client client = new Client(configuration);
@ -340,7 +334,8 @@ class RmiGlobalTest extends BaseTest {
}
});
client.connect(5000);
// client.connect(5000);
client.connect(0);
waitForThreads();
}

View File

@ -41,7 +41,7 @@ class RmiObjectIdExhaustionTest extends BaseTest {
manager.register(UnsupportedOperationException.class);
}
// @Test // NOTE: change final to test!
// @Test // NOTE: remove final from RmiBridge.INVALID_RMI to test!
public
void rmiNetwork() throws SecurityException, IOException, InterruptedException {
rmi(new Config() {
@ -60,7 +60,7 @@ class RmiObjectIdExhaustionTest extends BaseTest {
Thread.sleep(2000L);
}
// @Test // NOTE: change final to test!
// @Test // NOTE: remove final from RmiBridge.INVALID_RMI to test!
public
void rmiLocal() throws SecurityException, IOException, InterruptedException {
rmi(new Config() {
@ -80,7 +80,7 @@ class RmiObjectIdExhaustionTest extends BaseTest {
public
void rmi(final Config config) throws SecurityException, IOException {
// NOTE: change final to test!
// NOTE: remove final from RmiBridge.INVALID_RMI to test!
// RmiBridge.INVALID_RMI = 4;
Configuration configuration = new Configuration();
@ -88,13 +88,7 @@ class RmiObjectIdExhaustionTest extends BaseTest {
configuration.serialization = Serialization.DEFAULT();
register(configuration.serialization);
// for Client -> Server RMI (ID 1)
configuration.serialization.registerRmiImplementation(TestCow.class, TestCowImpl.class);
// for Server -> Client RMI (ID 2)
configuration.serialization.registerRmiInterface(TestCow.class);
configuration.serialization.registerRmi(TestCow.class, TestCowImpl.class);
final Server server = new Server(configuration);
server.setIdleTimeout(0);
@ -102,18 +96,16 @@ class RmiObjectIdExhaustionTest extends BaseTest {
addEndPoint(server);
server.bind(false);
// ----
configuration = new Configuration();
config.apply(configuration);
configuration.serialization = Serialization.DEFAULT();
register(configuration.serialization);
// for Client -> Server RMI (ID 1)
configuration.serialization.registerRmiInterface(TestCow.class);
// for Server -> Client RMI (ID 2)
configuration.serialization.registerRmiImplementation(TestCow.class, TestCowImpl.class);
configuration.serialization.registerRmi(TestCow.class, TestCowImpl.class);
final Client client = new Client(configuration);

View File

@ -106,9 +106,9 @@ class RmiSendObjectOverrideMethodTest extends BaseTest {
final boolean isUDP = configuration.udpPort > 0;
configuration.serialization = Serialization.DEFAULT();
configuration.serialization.registerRmiImplementation(TestObject.class, TestObjectImpl.class);
configuration.serialization.registerRmiImplementation(OtherObject.class, OtherObjectImpl.class);
configuration.serialization = Serialization.DEFAULT(true, true, false, null);
configuration.serialization.registerRmi(TestObject.class, TestObjectImpl.class);
configuration.serialization.registerRmi(OtherObject.class, OtherObjectImpl.class);
Server server = new Server(configuration);
server.setIdleTimeout(0);
@ -138,9 +138,9 @@ class RmiSendObjectOverrideMethodTest extends BaseTest {
configuration = new Configuration();
config.apply(configuration);
configuration.serialization = Serialization.DEFAULT();
configuration.serialization.registerRmiInterface(TestObject.class);
configuration.serialization.registerRmiInterface(OtherObject.class);
configuration.serialization = Serialization.DEFAULT(true, true, false, null);
configuration.serialization.registerRmi(TestObject.class, TestObjectImpl.class);
configuration.serialization.registerRmi(OtherObject.class, OtherObjectImpl.class);
Client client = new Client(configuration);
client.setIdleTimeout(0);

View File

@ -90,9 +90,9 @@ class RmiSendObjectTest extends BaseTest {
Configuration configuration = new Configuration();
config.apply(configuration);
configuration.serialization = Serialization.DEFAULT();
configuration.serialization.registerRmiImplementation(TestObject.class, TestObjectImpl.class);
configuration.serialization.registerRmiImplementation(OtherObject.class, OtherObjectImpl.class);
configuration.serialization = Serialization.DEFAULT(true, true, false, null);
configuration.serialization.registerRmi(TestObject.class, TestObjectImpl.class);
configuration.serialization.registerRmi(OtherObject.class, OtherObjectImpl.class);
@ -124,9 +124,9 @@ class RmiSendObjectTest extends BaseTest {
configuration = new Configuration();
config.apply(configuration);
configuration.serialization = Serialization.DEFAULT();
configuration.serialization.registerRmiInterface(TestObject.class);
configuration.serialization.registerRmiInterface(OtherObject.class);
configuration.serialization = Serialization.DEFAULT(true, true, false, null);
configuration.serialization.registerRmi(TestObject.class, TestObjectImpl.class);
configuration.serialization.registerRmi(OtherObject.class, OtherObjectImpl.class);
Client client = new Client(configuration);

View File

@ -171,6 +171,7 @@ class RmiTest extends BaseTest {
public static
void register(dorkbox.network.serialization.CryptoSerializationManager manager) {
manager.register(Object.class); // Needed for Object#toString, hashCode, etc.
manager.register(TestCow.class);
manager.register(MessageWithTestCow.class);
manager.register(UnsupportedOperationException.class);
}
@ -217,14 +218,14 @@ class RmiTest extends BaseTest {
Configuration configuration = new Configuration();
config.apply(configuration);
configuration.serialization = Serialization.DEFAULT();
configuration.serialization = Serialization.DEFAULT(true, true, false, null);
register(configuration.serialization);
// for Client -> Server RMI (ID 1)
configuration.serialization.registerRmiImplementation(TestCow.class, TestCowImpl.class);
configuration.serialization.registerRmi(TestCow.class, TestCowImpl.class);
// for Server -> Client RMI (ID 2)
configuration.serialization.registerRmiInterface(TestCow.class);
// configuration.serialization.registerRmiInterface(TestCow.class);
final Server server = new Server(configuration);
@ -274,14 +275,15 @@ class RmiTest extends BaseTest {
configuration = new Configuration();
config.apply(configuration);
configuration.serialization = Serialization.DEFAULT();
configuration.serialization = Serialization.DEFAULT(true, true, false, null);
register(configuration.serialization);
// for Client -> Server RMI (ID 1)
configuration.serialization.registerRmiInterface(TestCow.class);
// the 'TestCow' object lives on the 'Server'. The 'Client' accesses remote methods on it
// configuration.serialization.registerRmiInterface(TestCow.class);
// for Server -> Client RMI (ID 2)
configuration.serialization.registerRmiImplementation(TestCow.class, TestCowImpl.class);
// // for Server -> Client RMI (ID 2)
configuration.serialization.registerRmi(TestCow.class, TestCowImpl.class);
final Client client = new Client(configuration);

View File

@ -37,8 +37,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);
@ -78,14 +78,15 @@ class TestClient
configuration.udpPort = 2001;
configuration.host = "localhost";
configuration.serialization = Serialization.DEFAULT();
configuration.serialization = Serialization.DEFAULT(true, true, false, null);
RmiTest.register(configuration.serialization);
configuration.serialization.registerRmiInterface(TestCow.class);
configuration.serialization.registerRmi(TestCow.class);
try {
final Client client = new Client(configuration);
// client.setIdleTimeout(0);
client.disableRemoteKeyValidation();
client.setIdleTimeout(0);
client.listeners()
.add(new dorkbox.network.connection.Listener.OnConnected<Connection>() {
@ -121,7 +122,7 @@ class TestClient
}
});
client.connect(3330);
client.connect(0);
client.waitForShutdown();
} catch (Exception e) {

View File

@ -21,17 +21,18 @@ class TestServer
configuration.tcpPort = 2000;
configuration.udpPort = 2001;
configuration.serialization = Serialization.DEFAULT();
configuration.serialization = Serialization.DEFAULT(true, true, false, null);
RmiTest.register(configuration.serialization);
configuration.serialization.registerRmiImplementation(TestCow.class, TestCowImpl.class);
configuration.serialization.registerRmi(TestCow.class, TestCowImpl.class);
Server server = null;
try {
server = new Server(configuration);
server.disableRemoteKeyValidation();
} catch (SecurityException e) {
e.printStackTrace();
}
// server.setIdleTimeout(0);
server.setIdleTimeout(0);
server.bind(true);
}}