Frage zum Session Pattern

Added by duschata about 1 year ago

Hallo alle, hallo Alex,

ich habe ein wenig mit deinem Session Pattern herum gespielt und folgende Frage:
Wie bekommt der Client eine Liste aller Sessions (z.B. um dann eine Bestehende mit zu benutzen)

ServerInterface bekommt dazu folgende Deklaration:

1List<SessionInterface> getSessions();

ServerInterfaceImpl

1public List<SessionInterface> getSessions()
2{
3        return userSessions;
4}
5

und der Client:

1for(SessionInterface remoteSession: server.getSessions())
2{
3          //was hier steht ist egal, server.getSession() ist das letzte was abgearbeitet wird...
4}
5

Ich erhalte einen Haufen Fehlermeldungen, die ich alle nicht richtig interpretieren kann (und anhänge). Wieso kann ich eine einzelne Session bekommen, nicht aber eine ganze Liste?

Bei dem Spiel an dem ich gerade Programmiere, sollen mehrere Clients eine Session bekommen, die wiederum Callbacks auf den Clients ausführen kann. In meinem Programm konnte ich das Problem lösen, indem ich die Server Klassen zu den Client Klassen packe und die Liste der Clients als transient kennzeichne. Das bekomme ich bei dem Beispiel nicht hin, hilf (helft) mir doch bitte mal auf die Sprünge...

Viele Grüße Tom

stacktrace.zip - Fehlerausgabe (1.4 kB)


Replies (5)

RE: Frage zum Session Pattern - Added by achristian about 1 year ago

Hallo Tom,

Das Problem ist schnell erklärt: Simon ist ein Client-to-Server/Server-to-Client Framework. Client-to-Client Kommunikation ist nicht direkt möglich. Du musst das über den Server regeln.

Hintergrund: Ein Remote-Objekt ist immer an eine Netzwerkverbindung geknüpft:

[Server: [ServerRemoteObjekt]] <-------------------------> [Client1: [Proxy des ServerRemoteObjekts]]
[Server: [Proxy des ClientCallback1-Objekts]] <----------> [Client1: [ClientCallbackObjekt1]]

[Server: [ServerRemoteObjekt]] <-------------------------> [Client2: [Proxy des ServerRemoteObjekts]]
[Server: [Proxy des ClientCallback2-Objekts]] <----------> [Client2: [ClientCallbackObjekt2]]

Wenn du nun den Server ClientCallback2 an Client 1 senden lässt, dann funktioniert das nicht, da dieses Objekt einer ganz anderen TCP-Verbindung zugeordnet ist. Erschwerend kommt hinzu, dass diese Verbindung zwischen Client2 und Server besteht. Client1 wäre also gar nicht in der Lage zu Client2 zu kommunizieren. Es müsste erst eine neue Netzwerkverbindung aufgebaut werden...

RMI kann das soweit ich weiß. Intern wird dann der eine Endpunkt der Verbindung "umdefiniert" und Client1 baut dann eine Netzwerkverbindung zu Client2 auf. Das setzt aber vorraus, dass Client2 NICHT hinter einem Router/Firewall sitzt. Denn Client2 öffnet für den Verbindungsaufbau von Client1 einen SocketServer. Und der muss in Firewalls/Routern erst "freigegeben" werden. Und das ist, sofern man über's Internet kommuniziert, nicht so einfach/Userfreundlich (ähnlich wie bei den Callbacks).

Um also dein Problem zu beheben musst du folgendes tun (Ich erklär's mal anhand eines Chat-Servers):

Implementiere im Server eine Methode:

1public void sendToAllClients(String msg) {
2    for (ClientCallbackInterface callback : clientCallbackList) {
3        callback.sendMessage(msg);
4    }
5}

clientCallbackList ist hier eine Liste mit allen Callback-Objekten. Jeder einzelne Client muss so ein Callback beim Login oder so dem Server mitteilen.

Das mag jetzt im Vergleich zu RMI etwas umständlich aussehen. Aber die Anwendung wird - so denke ich - transparenter... Es ist klar dass alle Kommunikation zwischen Client und Server stattfindet und nicht irgendwie querbeet zwischen Client und Server und ClientX und ClientY.

So, nun nochmal zum Stacktrace:

Ich nehme mal an dass das der Stacktrace auf Serverseite ist?! Gabs auf Clientseite auch einen?!

Kannst du den Fehler bitte nochmal reproduzieren, aber dieses mal mit Debug-Log eingeschaltet?
Um mit kompletten Debug-Level zu loggen musst du folgendes tun:

Lade dir diese Log-Config runter: http://svn.root1.de/svn/simon/trunk/src/main/resources/log/simon_logging.properties
und lege sie in dein Hauptverzeichnis deiner Anwendung (Beispielsweise in das Hauptverzeichnis deines Eclipse oder Netbeans-Projekts).
Lege daneben einen Ordner names "log" an und starte die Anwendung mit folgendem JVM Parameter: -Djava.util.logging.config.file=simon_logging.properties

Dann reproduzierst du das Problem nochmal und lässt mir die Logfile(s) zukommen die anschließend im log-Ordner zu finden sind.

Alternativ kannst du natürlich auch den ausführbaren Beispielcode hier im Forum posten (oder per email an alex[at]root1.de).

Hintergrund: Normalerweise hättest du eine Exception bekommen müssen, die besagt, dass du ein Remote-Objekt nicht nochmal übertragen kannst. Stattdessen kam es zu einer NPE in der Klasse die eine Fehlermeldung transportiert.

- Alex

RE: Frage zum Session Pattern - Added by achristian about 1 year ago

Ich denke die Sache mit dem Beispielcode ist einfacher zu handhaben als die Logging-Geschichte. Wenn du den Code nicht public machen willst, kannst du mir das Sample auch an alex[at]root1.de mailen.

- Alex

RE: Frage zum Session Pattern - Added by achristian about 1 year ago

Tom via Email:

Folgende Grundidee steckt dahinter:
Ein Client nimmt Kontakt zum Server auf. Jetzt gibt es die Möglichkeit
ein eigenes Spiel (Session) zu eröffnen oder eine Liste bestehender
Spiele (Session) abzurufen bei denen man sich dann anmelden kann. Alles
funktioniert auch soweit, wenn ich beim Client die Serverklassen
hinzufüge. Wenn ich das nicht tue, dann gibt es "Ärger".

Ich verstehe es nicht ganz. Einzelne Sessions kann ich vom Server
anforden. Fordere ich die Liste an (die ja Referenzen auf die Sessions
haben) geht es nicht mehr. Aber das siehst du ja dann, habe dir alles +
Quellen zusammengepackt, Namen sprechen eigentlich für sich.

So, hab nun endlich etwas Zeit gefunden. Da Eclipse auf meinem Ubuntu64 wegen Firefox streikt (keine Ahnung was das soll.. Eclipse wird mir immer unheimlicher/unsympathischer), hab ich deinen Source jetzt mal in ein Netbeans-Projekt gezogen und nehm das ganze da unter die Lupe. Melde mich wieder wenn ich etwas gefunden habe..

- Alex

RE: Frage zum Session Pattern - Added by achristian about 1 year ago

So, hab die Sache mal unter die Lupe genommen ...

Problem ist folgendes:

Die Clients rufen Session#createSession(String) auf. Daraufhin wird auf Serverseite folgender Code ausgeführt:

1public Session createSession(String sessionName)
2{
3    Session session = new SessionImpl(sessionName);
4    sessions.add(session);
5    return session;
6}

Beim Session-Objekt handelt es sich um ein Remote-Objekt. Grund:

1@SimonRemote(value = { Session.class })
2public class SessionImpl implements Session, Serializable
3{
4...
5}

Zum Zeitpunkt des erzeugens ist das Objekt noch kein Remote-Objekt. Es ist lediglich "remote-fähig". Erst wenn das session Objekt mit dem return-Kommando zum Client als Rückgabewert des Session#createSession(String) aufrufs erfolgt, wird das Objekt als eine Remote-Instanz intern in SIMON registriert.

Soweit geht's ja noch. Der Client hat dann sein Remote-Session-Objekt und kann damit arbeiten. Waas auch noch okay ist, ist das speichern der Session auf Serverseite in einer Liste.

Kritisch ist jetzt nur das hier in der SessionProviderImpl Klasse:

1public List<Session> getSessions()
2{
3    return sessions;
4}

Der Client wird, gemäß deiner Implementierung, als nächstes alle im Server verfügbaren Session holen, und zwar mit dieser eben genannten Methode.
Das Problem hierbei ist: Das List-Objekt das du zurückgibst, ist kein Remote-Objekt. Und SIMON geht nun auch nicht her und schein in allen erdenklichen Collection-Objekten nach Remote-Objekten und registriert die dann als Remote-Objekte.

Ergo: Es wird die Liste serialisiert und alle darin enthaltenen Session-Objekte werden ebenfalls serialisiert. Der Client erhält somit keine Liste mit Remote-Objekten, sondern mit lokalen Objekten.

Wenn du deinem Client jetzt nicht auch noch die Implementierung zu dem Session-Objekten gibst, kann er die Objekte in der Liste nicht deserialisieren, und folglich auch nicht benutzen. Bei der normalen Remote-Objekt-Übergabe braucht der Client die Implementierung nicht. Denn Simon legt dafür eine Proxy-Klasse an, die dem Remote-Interface entspricht. Alle Aufrufe werden damit abgefangen und übers Netzwerk geschickt. Beim serialisierten Objekt geht das nicht. Da braucht man zum deserialisieren die Implementierungsklasse.

Das ist eben das "fatale" bei RPC: Es sieht so einfach aus, optisch gesehen kann man alles von A nach B und zurück schieben und kommunizieren. Aber faktisch haben Remote-Objekte "ein Zuhause" das an eine bestimmte JVM Instanz gebunden ist. Und wenn man SIMON nicht korrekt beibringt dass ein Objekt ein Remote-Objekt ist, das eine Heim-JVM hat, dann wird das Objekt in einen Karton gesteckt und an eine andere JVM verschickt, die das Paket auspakt und das Objekt lokal benutzt.

So, wie lösen wir dieses Dilemma nun?!

Nun, das kommt drauf an was deine Clients mit den Sessions tun können sollen (klingt komisch, oder?). Wenn du darauf aus warst, dass Client1 mit der Session von Client2 mehr oder weniger direkt mit Client2 kommunizieren kann: Dann muss ich dich enttäuschen: Das geht nicht. SIMON-Remote-Objekte funktionieren nur auf einer Socket-Verbindung: Die Session von Client2 hängt somit an der Socketverbindung von Client2<->Server. Client1 kann damit nicht wirklich viel anfangen.

Wenn du einfach nur die Parameter (Name, etc.) der Clients angezeigt haben willst, dann kannst du auf Serverseite zwei Listen führen:

 1transient private List<Session> sessions = new ArrayList<Session>();
 2transient private List<ClientDetail> clientDetails = new ArrayList<ClientDetail>();
 3
 4public Session createSession(String sessionName)
 5{
 6    Session session = new SessionImpl(sessionName);
 7    sessions.add(session);
 8    clientDetails.add(new ClientDetail(session)); // ClientDetail extrahiert notwendige Daten aus der Session
 9    return session;
10}
11
12public List<ClientDetail> getAllClientDetails()
13{
14    return clientDetails;
15}

Natürlich gibt's noch andere Wege und mittel. Aber als best-practice sollte man im Hinterkopf haben:

  • Remote-Objekte sind nach der ersten Übertragung an eine einzige Socketverbindung gebunden
  • Remote-Objekte sollte man nicht an andere Teilnehmer weiterschicken
  • Remote-Objekte sollte man nicht in Collections oder Container-Klassen versteckt verschicken

Ich hoffe ich konnte das Problem hinreichend genau erläutern. Wenn nicht: Einfach fragen ...

Gruß und schöne Ostern,

Alex

RE: Frage zum Session Pattern - Added by achristian about 1 year ago

Hallo nochmal,

es hat mir jetzt keine Ruhe gelassen dass der Fehler im Stacktrace/Log nicht eindeutig sichtbar war. Hab dann nochmal den debugger angeworfen und noch einen Fehler in der Error-Behandlung gefunden.

Hab das eben schnell gefixt. Wenn du in deinem client_pure.jar Archiv in de/root1/simon/codec/base die Class-File "AbstractMessageDecoder.class" durch die hier angehängte ersetzt und den Fehler reproduzierst, siehst du nun den Fehler in "lesbarer" Form (siehe letzten Abschnitt des Stacktraces):

2011-55-21 16:55::00.435 FINEST  tid=11 de.root1.simon.codec.base.AbstractMessageDecoder.decode: Message type [3] with sequence [6] with [276] bytes body size is available. Now decoding ...
2011-55-21 16:55::00.436 FINEST  tid=11 de.root1.simon.codec.base.AbstractMessageDecoder.decode: Error while decoding message. Forwarding/returning error.
2011-55-21 16:55::00.437 FINE    tid=11 de.root1.simon.Dispatcher.messageReceived: Received message from session 0x00000002
2011-55-21 16:55::00.438 FINE    tid=12 de.root1.simon.ProcessMessageRunnable.run: ProcessMessageRunnable: -1:MsgError(ron=null|errorMessage=Error while decoding message. sequence=6 bodySize=276 type=3|throwable=org.apache.mina.core.buffer.BufferDataException: java.io.InvalidClassException: failed to read class descriptor|isDecodeError=true) on sessionId 0x00000002
2011-55-21 16:55::00.439 FINE    tid=12 de.root1.simon.ProcessMessageRunnable.processError: begin
2011-55-21 16:55::00.439 FINE    tid=12 de.root1.simon.ProcessMessageRunnable.processError: processing MsgError...
2011-55-21 16:55::00.440 SEVERE  tid=11 de.root1.simon.Dispatcher.exceptionCaught: exception Caught. session=0x00000002. Exception:
 org.apache.mina.filter.codec.ProtocolDecoderException: Message decoder returned NOT_OK. (Hexdump: 00 32 64 65 2E 74 6F 6D 73 5F 74 6F 79 2E 67 61 6D 65 73 2E 64 6F 6B 6F 2E 73 68 61 72 65 64 2E 68 65 6C 70 65 72 2E 43 61 72 64 69 6E 61 6C 50 6F 69 6E 74 78 72 01 00 0E 6A 61 76 61 2E 6C 61 6E 67 2E 45 6E 75 6D 78 70 74 00 04 57 45 53 54 7E 71 00 7E 00 06 74 00 05 4E 4F 52 54 48 7E 71 00 7E 00 06 74 00 04 45 41 53 54 78 70 73 71 00 7E 00 02 74 00 05 74 65 73 74 31 73 71 00 7E 00 00 00 00 00 03 77 04 00 00 00 0A 71 00 7E 00 08 71 00 7E 00 0A 71 00 7E 00 0C 78 70 78)
    at org.apache.mina.filter.codec.demux.DemuxingProtocolDecoder.doDecode(DemuxingProtocolDecoder.java:187)
    at org.apache.mina.filter.codec.CumulativeProtocolDecoder.decode(CumulativeProtocolDecoder.java:178)
    at org.apache.mina.filter.codec.ProtocolCodecFilter.messageReceived(ProtocolCodecFilter.java:241)
    at org.apache.mina.core.filterchain.DefaultIoFilterChain.callNextMessageReceived(DefaultIoFilterChain.java:434)
    at org.apache.mina.core.filterchain.DefaultIoFilterChain.access$1200(DefaultIoFilterChain.java:46)
    at org.apache.mina.core.filterchain.DefaultIoFilterChain$EntryImpl$1.messageReceived(DefaultIoFilterChain.java:796)
    at org.apache.mina.filter.logging.LoggingFilter.messageReceived(LoggingFilter.java:177)
    at org.apache.mina.core.filterchain.DefaultIoFilterChain.callNextMessageReceived(DefaultIoFilterChain.java:434)
    at org.apache.mina.core.filterchain.DefaultIoFilterChain.access$1200(DefaultIoFilterChain.java:46)
    at org.apache.mina.core.filterchain.DefaultIoFilterChain$EntryImpl$1.messageReceived(DefaultIoFilterChain.java:796)
    at org.apache.mina.core.filterchain.IoFilterAdapter.messageReceived(IoFilterAdapter.java:119)
    at org.apache.mina.core.filterchain.DefaultIoFilterChain.callNextMessageReceived(DefaultIoFilterChain.java:434)
    at org.apache.mina.core.filterchain.DefaultIoFilterChain.fireMessageReceived(DefaultIoFilterChain.java:426)
    at org.apache.mina.core.polling.AbstractPollingIoProcessor.read(AbstractPollingIoProcessor.java:693)
    at org.apache.mina.core.polling.AbstractPollingIoProcessor.process(AbstractPollingIoProcessor.java:646)
    at org.apache.mina.core.polling.AbstractPollingIoProcessor.process(AbstractPollingIoProcessor.java:635)
    at org.apache.mina.core.polling.AbstractPollingIoProcessor.access$400(AbstractPollingIoProcessor.java:67)
    at org.apache.mina.core.polling.AbstractPollingIoProcessor$Processor.run(AbstractPollingIoProcessor.java:1079)
    at org.apache.mina.util.NamePreservingRunnable.run(NamePreservingRunnable.java:64)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1110)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:603)
    at java.lang.Thread.run(Thread.java:679)

2011-55-21 16:55::00.441 FINE    tid=11 de.root1.simon.Dispatcher.exceptionCaught: Closing the session now! session=0x00000002
2011-55-21 16:55::00.442 FINE    tid=12 de.root1.simon.ProcessMessageRunnable.processError: end
2011-55-21 16:55::00.442 INFO    tid=11 org.apache.mina.filter.logging.LoggingFilter.log: CLOSED
Exception in thread "Simon.Dispatcher.WorkerPool.#1" de.root1.simon.exceptions.SimonException: An error occured while reading a message. Error message: Error while decoding message. sequence=6 bodySize=276 type=3
    at de.root1.simon.ProcessMessageRunnable.processError(ProcessMessageRunnable.java:700)
    at de.root1.simon.ProcessMessageRunnable.run(ProcessMessageRunnable.java:178)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1110)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:603)
    at java.lang.Thread.run(Thread.java:679)
Caused by: org.apache.mina.core.buffer.BufferDataException: java.io.InvalidClassException: failed to read class descriptor
    at org.apache.mina.core.buffer.AbstractIoBuffer.getObject(AbstractIoBuffer.java:1984)
    at de.root1.simon.codec.base.MsgInvokeReturnDecoder.decodeBody(MsgInvokeReturnDecoder.java:55)
    at de.root1.simon.codec.base.AbstractMessageDecoder.decode(AbstractMessageDecoder.java:97)
    at org.apache.mina.filter.codec.demux.DemuxingProtocolDecoder.doDecode(DemuxingProtocolDecoder.java:178)
    at org.apache.mina.filter.codec.CumulativeProtocolDecoder.decode(CumulativeProtocolDecoder.java:178)
    at org.apache.mina.filter.codec.ProtocolCodecFilter.messageReceived(ProtocolCodecFilter.java:241)
    at org.apache.mina.core.filterchain.DefaultIoFilterChain.callNextMessageReceived(DefaultIoFilterChain.java:434)
    at org.apache.mina.core.filterchain.DefaultIoFilterChain.access$1200(DefaultIoFilterChain.java:46)
    at org.apache.mina.core.filterchain.DefaultIoFilterChain$EntryImpl$1.messageReceived(DefaultIoFilterChain.java:796)
    at org.apache.mina.filter.logging.LoggingFilter.messageReceived(LoggingFilter.java:177)
    at org.apache.mina.core.filterchain.DefaultIoFilterChain.callNextMessageReceived(DefaultIoFilterChain.java:434)
    at org.apache.mina.core.filterchain.DefaultIoFilterChain.access$1200(DefaultIoFilterChain.java:46)
    at org.apache.mina.core.filterchain.DefaultIoFilterChain$EntryImpl$1.messageReceived(DefaultIoFilterChain.java:796)
    at org.apache.mina.core.filterchain.IoFilterAdapter.messageReceived(IoFilterAdapter.java:119)
    at org.apache.mina.core.filterchain.DefaultIoFilterChain.callNextMessageReceived(DefaultIoFilterChain.java:434)
    at org.apache.mina.core.filterchain.DefaultIoFilterChain.fireMessageReceived(DefaultIoFilterChain.java:426)
    at org.apache.mina.core.polling.AbstractPollingIoProcessor.read(AbstractPollingIoProcessor.java:693)
    at org.apache.mina.core.polling.AbstractPollingIoProcessor.process(AbstractPollingIoProcessor.java:646)
    at org.apache.mina.core.polling.AbstractPollingIoProcessor.process(AbstractPollingIoProcessor.java:635)
    at org.apache.mina.core.polling.AbstractPollingIoProcessor.access$400(AbstractPollingIoProcessor.java:67)
    at org.apache.mina.core.polling.AbstractPollingIoProcessor$Processor.run(AbstractPollingIoProcessor.java:1079)
    at org.apache.mina.util.NamePreservingRunnable.run(NamePreservingRunnable.java:64)
    ... 3 more
Caused by: java.io.InvalidClassException: failed to read class descriptor
    at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1584)
    at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1513)
    at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1749)
    at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1346)
    at java.io.ObjectInputStream.readObject(ObjectInputStream.java:368)
    at java.util.ArrayList.readObject(ArrayList.java:696)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:616)
    at java.io.ObjectStreamClass.invokeReadObject(ObjectStreamClass.java:991)
    at java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:1865)
    at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1770)
    at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1346)
    at java.io.ObjectInputStream.readObject(ObjectInputStream.java:368)
    at org.apache.mina.core.buffer.AbstractIoBuffer.getObject(AbstractIoBuffer.java:1982)
    ... 24 more
Caused by: java.lang.ClassNotFoundException: de.toms_toy.games.doko.server.SessionImpl
    at java.net.URLClassLoader$1.run(URLClassLoader.java:217)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.net.URLClassLoader.findClass(URLClassLoader.java:205)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:321)
    at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:294)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:266)
    at java.lang.Class.forName0(Native Method)
    at java.lang.Class.forName(Class.java:264)
    at org.apache.mina.core.buffer.AbstractIoBuffer$3.readClassDescriptor(AbstractIoBuffer.java:1962)
    at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1582)
    ... 39 more

Hab die Änderung im trunk eingecheckt. Das nächtliche Build sollte dann morgen früh ein aktuelles 1.2.0-SNAPSHOT Nr. 14 ausspucken (hier zu finden: http://nexus.root1.de/content/repositories/snapshots/de/root1/simon/1.2.0-SNAPSHOT/)

Gruß
Alex

(1-5/5)