SIMON - Simple Invocation of Methods Over Network

Vorwort
Wer Client-Server Anwendungen mit RMI geschrieben hat, und diese versucht hat über’s Internet zu benutzen kennt vermutlich das Phänomen: Nix geht mehr. Ob der Server dabei hinter einem DSL-Router hängt und mittels Portforwarding betrieben wird oder nicht scheint dabei keine Rolle zu spielen.

Warum ist RMI etwas ungeschickt bei der Kommunikation im Internet?
Warum? Vermutlich benutzt du eine Callback-Strategie die es dem Server erlaubt, selbstständig Methoden am Client aufzurufen. RMI benutzt hierfür nicht wie erwartet die Verbindung, die der Client zum Server aufgebaut hat, sondern baut von sich aus eine neue Verbindung auf. Da in Europa (und vermutlich der Rest der Welt) zu einem Großteil hinter einem Router sitzt, der für einen den Verbindungsaufbau zum Internet erledigt, bleibt der Verbindungsaufbau vom Server zum Client an diesem Router hängen.

Die Lösung
Nun, SIMON ist hierfür die Lösung. SIMON nutzt nur eine einzige Netzwerkverbindung für die bi-direktionale Kommunikation. Nämlich die, die der Client zum Server hin aufbaut. Damit muss maximal für den Server ein Portforwarding konfiguriert werden. Mehr nicht.

Doch das ist noch nicht das Ende vom Lied:

RMI nutzt das normale Java IO. Das hat zum Nachteil, dass ein Server, der mehrere Clients bedienen soll, für jeden Client einen Thread starten muss. Das mag für eine Hand voll User noch gut gehen. Aber stell dir einen Server mit über 1000 Clients vor? Der Server wäre mit 1000 Threads nur für die Clients schon recht stark belastet. MIt steigender Client-Zahl nimmt die Zeit, die er für’s Dispatchen der vielen Threads braucht stetig zu und irgendwann kann er gar keinen Client mehr bedienen weil ihm die Ressourcen ausgehen.

SIMON verwendet hier nicht das alte IO, sondern das neue NIO (okay, das ist auch schon älter, aber bedeutend skalierbarer). SIMON kann so konfiguriert werden, dass eine beliebige Anzahl Clients mit einer begrenzten Anzahl an Threads bedient werden. Damit werden die Ressourcen geschont, der Server ist noch ansprechbar und das System lebt weiter.

Performance
In Tests konnte SIMON bis jetzt 3000 Clients mit rund 50 Threads bedienen. Geschwindigkeitsprobleme mit so wenig Threads? Nein. Im Gegenteil. Im Tests konnte der Server noch 10.000 (in Worten: Zehntausend!) Methodenaufrufe pro Sekunde bewältigen. Das ist doch schon eine ganze Menge (Testumgebung: Intel Core2 Quad 6600 mit 4x2,4Ghz, 4GB RAM, OS: openSUSE Linux, Verbindung zwischen Clients und Server via localhost).

Dazu kommt noch, dass SIMON etwas einfacher zu handhaben ist als RMI. Es gibt weniger Dinge die man beachten muss. Und da SIMON noch entwickelt wird, fließen die Anregungen und Wünsche der Anwender direkt in die Entwicklung mit ein.

Ein Codebeispiel
Damit du einen Überblick bekommst wie SIMON funktionier, hier ein Codesample:

ServerInterface.java:


import de.root1.simon.SimonRemote;
import de.root1.simon.SimonRemoteException;
   
public interface ServerInterface extends SimonRemote {

      public void login(ClientCallbackInterface clientCallback) throws SimonRemoteException;

}```

ClientCallbackInterface.java
```package de.root1.simon.codesample.common;

import de.root1.simon.SimonRemote;
import de.root1.simon.SimonRemoteException;

public interface ClientCallbackInterface extends SimonRemote {

      public void callback(String text) throws SimonRemoteException;

}```

ServerInterfaceImpl.java
```package de.root1.simon.codesample.server;

import de.root1.simon.SimonRemoteException;
import de.root1.simon.codesample.common.ClientCallbackInterface;
import de.root1.simon.codesample.common.ServerInterface;

public class ServerInterfaceImpl implements ServerInterface {

      private static final long serialVersionUID = 1L;

      public void login(ClientCallbackInterface clientCallback) throws SimonRemoteException {
            clientCallback.callback("This is the callback. " +
            "Your address is "+Simon.getRemoteInetAddress(clientCallback)+" "+
            "and your are connected from port      "+Simon.getRemotePort(clientCallback));
      }
}```

ClientCallbackImpl.java
```package de.root1.simon.codesample.client;

import de.root1.simon.SimonRemoteException;
import de.root1.simon.codesample.common.ClientCallbackInterface;

public class ClientCallbackImpl implements ClientCallbackInterface {

      private static final long serialVersionUID = 1L;

      public void callback(String text) throws SimonRemoteException {
            System.out.println("This message was received from the server: "+text);
      }
} ```

Server.java
```package de.root1.simon.codesample.server;

import de.root1.simon.Simon;

public class Server {

      public static void main(String[] args) {

            // create the serverobject
            ServerInterfaceImpl serverImpl = new ServerInterfaceImpl();

            // create the server's registry ...
            Simon.createRegistry(2000);

            // ... where we can bind the serverobject to
            Simon.bind("server", serverImpl);
            System.out.println("Server up and running!");
      }
} ```

Client.java
```package de.root1.simon.codesample.client;

import de.root1.simon.Simon;
import de.root1.simon.SimonRemoteException;
import de.root1.simon.codesample.common.ServerInterface;

public class Client {

      public static void main(String[] args) throws SimonRemoteException, ConnectException {
            // create a callback object
            ClientCallbackImpl clientCallbackImpl = new ClientCallbackImpl();

            // 'lookup' the server object
            ServerInterface server = (ServerInterface) Simon.lookup("127.0.0.1", 2000, "server");

            // use the serverobject as it would exist on your local machine
            server.login(clientCallbackImpl);
       
            // do some more stuff
       
            // and finally 'release' the serverobject to release the connection
            Simon.release(server);
      }
}```

So, mehr ist es im Prinzip nicht. Wer RMI kennt, findet sich hier schnell zurecht. Und wer RMI nicht kennt, und mit Interfaces umgehen kann, der findet sich ebenfalls recht schnell zurecht.

**Weitere Infos, Download, etc.**
Bei Fragen, Anregungen, Ideen und dergleichen, bitte besucht die Projektseite unter 

http://www.root1.de

Dort gibts nightly builds, Codesamples, Doku, ein Support-Forum und einen Bugtracker wo ihr Fehler berichten könnt und immer auf dem laufenden bleibt. 
Kleiner Hinweis: Die Seite ist zwar auf englisch, aber das Support-Forum ist zweisprachig. In der Regel bekommt ihr binnen 24h auch eine brauchbare Antwort von mir. 

Gruß,
Alex

Kleiner ergänzender Hinweis:

Simon version 0.2 steht vor der Türe. So langsam aber sicher sind die gröbsten Bugs beseitigt.

Wäre klasse wenn sich noch jemand findet der SIMON einsetzen will und mir Bugs berichten kann.

Hier mal der Link zur Roadmap: http://root1.de/mantis/roadmap_page.php
Und zum Changelog: http://root1.de/mantis/changelog_page.php

(hab erst vor kurzem mit dem Bugtracking begonnen, deshalb ist die dort geführte Changelog noch nicht so umfassend. Die “alte” Changelog gibts hier )

Gruß
Alex

Das klingt Interessant,
wie sieht es mit der Anbindung von anderen Sprachen aus, also z.B. eine Verbindung von C# oder PHP?

Naja, SIMON verwendet intern die java eigene Serialisierung um komplexe Objekte zu transferieren, wie eben RMI auch. Das Protokoll kann man in anderen Sprachen sicherlich nachbauen, sollte kein Problem sein. Aber wie und ob das mit der serialisierung klappt: Kein Plan.

Sicherlich könnte man “externalisieren”. Aber das hätte zur Folge, dass man alles, was nicht byte, integer, long, short, boolean (oder ein Array davon) oder String ist, immer externalisieren müsste.

Aber ich schau mal ob ich was zum Thema “Java serialisierung” und “interoperabilität mit anderen Sprachen” finde.

Gruß Alex

weil mir kommt diese Idee, da ich in meiner Diplomarbeit RMI verwendet und als Nachteil dafür die Bindung an Java aufgeführt habe.
Da es auch schön wäre diesen Server mit C# oder PHP anzusprechen.

Mal kurz gegoogelt: http://entwickler-forum.de/showthread.php?t=45273

Ne Möglichkeit wäre den serialisierer als Plugin zu verwenden, so dass man alternativ JSON verwenden kann, womit man dann die interoperabilität erreicht hätte. Dürfte zumindest auf Javaseite kein allzugroßes Thema sein.

Allerdings müsste sich jemand hinsetzen und den C# Teil dazu coden…Wer Lust dazu hat: Hab das Protokoll ausreichend gut dokumentiert.

  • Alex

Das klingt schonmal interessant, ich habe nächste woche noch Urlaub und komm die Woche danach erst wieder in die Firma. Sollte ich das System umbauen könnte da bestimmt ne PHP und C# Variante rauskommen :wink:

Wenn du da dann fragen hast oder so: meld dich einfach.

Das einbinden von JSON wird sicher n bisschen dauern.

Wo ich mir auch noch gedanken drüber machen muss/sollte, ist die verschlüsselung der Kommunikation.

  • Alex

Hab mal nen feature-request Eintrag im Bugtracker gemacht.
Denke es wäre “falsch” sich auf nur einen weiteren Serialisierer festzulegen.
Werde das ganze wohl eher versuchen als Plugin-Schnittstelle zu implementieren, so dass man sich vielleicht sogar das Protokoll selbst zurecht stricken kann.

—> http://root1.de/mantis/roadmap_page.php

super ich hoffe dass ich die Tage mal Zeit dafür hab und es mir genauer ansehen kann

Hab eben nochmal in den Code reingesehen um zu schauen ob ich’s nicht doch schon in release 0.2 reinpacken kann. Aber da gehört dann noch mehr geändert so dass ich 0.2 damit unnötig verzögern würde.

Wird also in release 0.3 kommen.

Servus,

bin gerade dabei das “Server im Netzwerk finden” Feature zu basteln. Wenn hier jemand Wünsche oder Anregungen hat: Bitte hier posten: http://root1.de/node/35

  • Alex

Was mir gerade aufgefallen ist, du solltest auf jeden Fall eine API Doc online stellen oder war ich nur zu blind die zu sehen?

Stimmt, die ist bisher nur im ZIP des Downloads enthalten. Werde das anpassen.

  • Alex

Was mir gerade auffällt, du bist mit Exceptions sehr sparsam.
Erstelle ich mit “Simon.createRegistry(InetAddress.getLocalHost(), 1000);” eine Registry kann ich nicht abfangen/abfragen ob schon eine existiert, nur au der Konsole wird das mal ausgegeben.

Bei der “individuellen” Registry: Ja. Da muss ich noch nachbessern.

Bei der “globalen” Registry (also createRegistry(int)) bekommst du eine IllegalStateException.

  • Alex

ja bei der einfachen hab ichs gemerkt, leider benutz ich ja die andere :smiley:

Was ich gerade nicht ganz versteh, warum gibt die “individuelle” Registry eine Registry zurück, die einfache Variante aber keine? Hat das einen tieferen Sinn?

[QUOTE=tuxedo]Stimmt, die ist bisher nur im ZIP des Downloads enthalten. Werde das anpassen.

  • Alex[/QUOTE]

fixed

Der Link “Documentation” führt jetzt zur API Doc.

Ergänzend liegt das API Doc Verzeichnis bei den Downloads (für die ganz blinden).

  • Alex

super :smiley: