Dienstag, 27. Juli 2010

Java RMI - Remote Method Invocation

Um Methoden einer Instanz fernaufrufbar zu machen, muß man die gewünschten Methoden in ein Interface auslagern, welches das Interface

java.rmi.Remote
erweitert. Anschließend muß man die jeweilige Instanz für Remotezugriff exportieren. Dies geschieht entweder auf direktem Weg mittels statischer Methode
java.rmi.server.UnicastRemoteObject.exportObject()
oder indirekt für alle erzeugten Instanzen der Klasse, wenn diese von
java.rmi.server.UnicastRemoteObject
erbt. Dabei muss man aber die Konstruktoren der Klasse als
throws RemoteException
deklarieren. Im implizit oder explizit erfolgenden Aufruf des Basiskonstruktors von
UnicastRemoteObject
geschieht dann der Export der Instanz.
Man kann sich den Export einer Instanz als Mapping vorstellen, bei dem zu jedem Remote Objekt als Schlüssel ein entsprechender Proxy in Stub-Gestalt als Wert hinterlegt wird. Bei Parameterübergaben an remote aufgerufenen Methoden (per Stub Instanzen) und bei entsprechenden Rückgabewerten werden alle Objekte vom Typ Remote durch Ihre Stubs ersetzt, sofern diese Remote Objekte implizit oder explizit exportiert wurden. Falls kein Export definert wurde, dann geschieht die ganz normale Serialisierung der Remote Objekte. Im Falle der Exportfreigabe der jeweiligen Remote-Instanzen werden die zugehörigen Stubs an ihrer Stelle serialisiert. Wenn ein Client mehrfach einen serialiserten Stub für dieselbe Remote-Instanz als Rückgabewert einer Methode erhält, dann sind diese Stub Objekte bzgl. des Vergleiches mittels

==
alle unterscheidlich, aber sie kommunizieren alle mit demselben Remote Objekt. Daher sollte man mit Stub Objekten immer nur mittels
.equals()
vergleichen.

Zitate:

  • Wenn eine Methode eine lokale Referenz auf ein exportiertes Remote-Objekt zurückgibt, gibt RMI nicht das (A.d.Ü: Remote-) Objekt zurück. Stattdessen ersetzt es (A.d.Ü: dieses) mit einem anderen Objekt (dem Remote Proxy für diesen Dienst) im Rückgabestrom.

    [Übersetzung von: "When a method returns a local reference to an exported remote object, RMI does not return that object. Instead, it substitutes another object (the remote proxy for that service) in the return stream."] aus http://java.sun.com/developer/onlineTraining/rmi/RMI.html#RemoteObjectParameters

Links:

Donnerstag, 22. Juli 2010

TCP Sockets

Ein TCP Socket ist ein vollduplex Stream Kanal der es Anwendungen erlaubt miteinander zu kommunizieren. Dabei ist der Name Socket von der bildlichen Vorstellung einer Steckdosenbuchse abgeleitet, bei der eine Öffnung dem Empfang und die Andere dem Senden von Daten dient.
In C wird ein Socket über einen Filedeskriptor angesprochen, welches eine natürliche Zahl vom Typ int ist. Man kann diesen Filedeskriptor sowohl zum Lesen als auch zum Schreiben benutzen, indem man ihn als Argument in den Funktionen der stdio Bibliothek angibt. Beim Lesen kommen die Daten vom verbundenen Remote-Socket und beim Schreiben gehen Sie an den verbundenen Remote-Socket. Man benutzt aber zum Lesen und Schreiben immer den Lokalen Socket, weil nur dieser im Betriebsystem real vorhanden ist. Nur semantisch liest und schreibt man vom bzw. zum Remote-Socket.
Eine bestehende Kommunikationsverbindung von zwei Anwendungen mittels Sockets läßt sich extern als ein Socketpaar definieren.
In Windows wird ein verbundener Socket mit dem Befehl

netstat -np tcp
angezeigt.

Dabei könnte man die Remoteadresse im lokalen Socket als Remote Socket interpretieren. Der Remote-Socket ist allerdings als virtuell anzusehen, da er nur auf dem Remote System existiert. In dieser Interpretation hätte man direkt die Darstellung der Verbindung als Socket-Paar, wobei jeder Socket mit seiner Absenderadresse dargestellt wird.
Die Erzeugung und Verbindung von Sockets unterscheidet sich nach der Art der Verbindungsherstellung:

Für Server gilt:


  1. int socket(PF_INET, SOCK_STREAM, 0) -- gibt Filedskriptor für erzeugten Socket zurück (bsp 7)
  2. int bind(7,<Adresse von Struktur wo lokale IP+Port steht>,<größe der Struktur>) -- Rückgabe Fehlercode
  3. int listen(7,<Anzahl erlaubter gleichzeitiger Verbindungsgesuche von Clients>) -- Rückgabe Fehlercode
  4. int accept(7,<Adresse von Struktur wo remote IP+Port gespeichert werden soll>,<größe der Struktur>) -- gibt Filedeskriptor von neuem in Verbindung stehendem Socket zurück (bsp 40)
In Punkt 2 wird dem erzeugten Socket die lokale Protokolladresse zugeordnet und dann mit listen auf Verbindungsgesuche von Clients gewartet. In Punkt 4 wird dann ein enstprechender Verbindungsgesuch von einem Client entgegengenommen, indem man die Protokolladresse des Clients abspeichern läßt und man erhält einen neuen Socket mit einem neuen Filedeskriptor, in welchem sowohl die lokale Server Adresse wie auch die remote Client Adresse festgehalten sind.
Für Clients gilt:


  1. int socket(PF_INET, SOCK_STREAM, 0) -- gibt Filedskriptor für erzeugten Socket zurück (bsp 10)
  2. int connect(10,<Adresse von Struktur wo remote IP+Port steht>,<größe der Struktur>) -- Rückgabe Fehlercode
In Punkt 2 wird eine beiliebige lokale Client Adresse vom Betriebsystem ausgesucht und eine Verbindung mit dem Server über die angegebene Remote-Server-Adresse hergestellt. Dabei wird kein neuer Filedeskriptor für den Client-Socket generiert, sondern nur die Felder dieses gesetzt.


Wenn in Java auf einem Server folgende Notation benutzt wird
Socket clientSocket = serverSocket.accept();
dann soll mit "clientSocket" nur zum Ausdruck gebracht werden, dass die Quelle und das Ziel von Lese-Schreib-Vorgängen ein Remote-Client Socket ist. Real wird aber nur der existierende lokale Socket benutzt, der bzgl. lokaler Adresse mit
serverSocket
übereinstimmt, jedoch noch zusätzlich eine gültige remote Adresse aufweist. Es ist diesbezüglich auch ein neu erzeugtes Socket-Objekt.

Montag, 5. Juli 2010

Logging in Java

Mittels des Apache Pakets log4j 1.2 kann man bequem Log Meldungen im Code unterbringen, die unterschiedlich gewichtet sind. Man definiert zunächst ein Log Objekt über den Aufruf der Fabrikmethode Logger.getLogger(EineKlasse.class). Die Methode Logger.getLogger(String) erwartet einen String Parameter, der indirekt über .getName() von Class geliefert wird. Das ist also äquivalent zu Logger.getLogger("class vollerPfadzuEineKlasse"). Dadurch wird ein Singleton Logger für die Klasse EineKlasse erzeugt.
Mit diesem Objekt kann man dann über verschiedene Level Meldungen rausschicken.
Die Level mit ihrer Priorität sind: DEBUG < INFO < WARN < ERROR < FATAL.

Die entsprechenden Methoden meinLogger.debug("Text ....") usw. . Einem Logger wird nun ein Level zur Startzeit zugeordnet (über Konfigurationsdatei), über den die im Code eingestreuten log Aufrufe aktiviert oder deaktiviert werden.
Alle log Meldungen, deren Level mindestens genauso hoch wie der aktuelle Log Level sind sind aktiv.
Wenn der Level des Loggers auf DEBUG eingestellt wird, dann sind alle Log Methodenaufrufe aktiv. Wenn der Level des Loggers auf FATAL eingestellt ist, dann werden nur die Methodenaufrufe .fatal() aktiv. Alle anderen log Aufrufe über .debug(), .info() , .warn() und .error() werden nicht verarbeitet. Dies ist zu vergleichen mit dem Makroprozessor in C, wo über Headervariablen einzelne Codeabschnitte für die Kompilierung ein oder ausgeschaltet werden. Nur geschieht das hier rein formal nicht in der Kompilierphase, sondern zur Laufzeit. Allerdings ist der Performance-Effekt laut Apache gleichzusetzen mit dem Ein- und Ausblenden zur Kompilierzeit.

Falls einem Logger kein Level zum Programmstart zugeordnet wird, dann erbt es den Level seines Vorfahrrens. Für diesen gilt das gleiche. Da der Rootlogger immer einen Level hat (DEBUG) besitzt im Umkehrschluss jeder Logger einen Level über diesen Vererbungsmechanismus.



Die Log Meldungen können nun wiederum an verschieden Orte ausgegeben werden. Dazu werden dem Logger ein oder mehrere Appender zugewiesen. Diese können Konsolen, Dateien, Socket-Verbindungen und beliebige andere Ziele sein. Zuweisung erfolgt über log4j.appender.AppenderName[.*]=Wert Zuweisungen.


Hier ein Beispiel:

log4j.appender.SessionLogAppender=de.xxx.util.log.JIDFileAppender
log4j.appender.SessionLogAppender.file='Logs/'yyyy-MM-dd/'Logs %s_'yyyy-MM-dd_HH-mm-ss'.log'
log4j.appender.SessionLogAppender.layout=org.apache.log4j.PatternLayout
log4j.appender.SessionLogAppender.layout.ConversionPattern=%-5p %d{yyyy-MM-dd HH:mm:ss,SSS} (%F:%L) %m%n 
 
Alle aktiven Log Meldungen vom Logger werden an jeden definierten Appender herausgeschickt und an die Appender seiner Vorfahren.
Dabei kann ein Logger durch setzen des additivity flag auf false das Erben von Appendern seiner Vorfahren verhindern.
Beispiel:
Logger a = Logger.getLogger("a");//Appender für Klassen mit Paketpräfix a
Logger b = Logger.getLogger("a.b");//Will Appender von a erben. additivy flag = true (Voreinstellung) 
Logger c = Logger.getLogger("a.c");//Will nur seine eigenen Appender und nicht die von a. Setzt dazu additivy flag = false 
 
Der Root Logger hat übrigens keinen Default Appender, sondern nur einen Default Debug Level (nämlich DEBUG).